Add skill runtime snapshots
This commit is contained in:
1 parent
c694b7fc2c
commit
0e920b409a
7 files changed
+98
-2
No files matched your search
@@ -65,6 +65,7 @@ export function registerIpcHandlers(deps: IpcDeps): void {
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.importSkillDirectory, (_event, input) => mutate(() => skills.importDirectory(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.bindSkill, (_event, input) => mutate(() => skills.bind(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.resolveAgentSkills, (_event, input) => mutate(() => skills.resolveRuntimeSnapshot(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.createSocialAccount, (_event, input) => mutate(() => social.createAccount(input)));
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.launchSocialLogin, (_event, input) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { basename, join } from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { BindSkillInput, ImportSkillDirectoryInput } from "../../shared/commands";
|
||||
import type { BindSkillInput, ImportSkillDirectoryInput, ResolveAgentSkillsInput } from "../../shared/commands";
|
||||
import { createId } from "../../shared/ids";
|
||||
import { AuditService } from "./audit-service";
|
||||
|
||||
@@ -127,4 +127,71 @@ export class SkillService {
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
resolveRuntimeSnapshot(input: ResolveAgentSkillsInput): string {
|
||||
const agent = this.db.prepare("select id from agents where id = ? and org_id = ?")
|
||||
.get(input.agentId, input.orgId) as { id: string } | undefined;
|
||||
if (!agent) throw new Error("Agent was not found.");
|
||||
const rows = this.db.prepare(`
|
||||
select
|
||||
sr.id as revision_id,
|
||||
sr.version_semver,
|
||||
sr.manifest_json,
|
||||
sa.object_key
|
||||
from skill_bindings sb
|
||||
join skill_revisions sr on sr.id = sb.pinned_revision_id
|
||||
join skill_artifacts sa on sa.revision_id = sr.id and sa.artifact_type = 'markdown'
|
||||
where sb.org_id = ? and sb.agent_id = ? and sb.enabled = 1
|
||||
order by sb.priority asc, sr.published_at asc
|
||||
`).all(input.orgId, input.agentId) as unknown as RuntimeSkillRow[];
|
||||
if (rows.length === 0) throw new Error("Agent has no enabled skills.");
|
||||
|
||||
const compiledParts: string[] = [];
|
||||
const tools = new Set<string>();
|
||||
const revisionIds: string[] = [];
|
||||
for (const row of rows) {
|
||||
if (!existsSync(row.object_key)) throw new Error(`Skill artifact is missing: ${row.object_key}`);
|
||||
const manifest = JSON.parse(row.manifest_json) as SkillManifest;
|
||||
for (const tool of manifest.requiredTools) tools.add(tool);
|
||||
revisionIds.push(row.revision_id);
|
||||
compiledParts.push([
|
||||
`# Skill: ${manifest.name}`,
|
||||
`Version: ${row.version_semver}`,
|
||||
readFileSync(row.object_key, "utf8").trim(),
|
||||
].join("\n\n"));
|
||||
}
|
||||
|
||||
const snapshotId = createId("srs");
|
||||
this.db.prepare(`
|
||||
insert into skill_runtime_snapshots(
|
||||
id, agent_id, run_id, compiled_prompt, tools_manifest, source_revision_ids, created_at
|
||||
)
|
||||
values (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
snapshotId,
|
||||
input.agentId,
|
||||
input.runId,
|
||||
compiledParts.join("\n\n---\n\n"),
|
||||
JSON.stringify({ requiredTools: [...tools].sort() }),
|
||||
JSON.stringify(revisionIds),
|
||||
new Date().toISOString(),
|
||||
);
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
entityType: "skill_runtime_snapshot",
|
||||
entityId: snapshotId,
|
||||
actorType: "system",
|
||||
actorId: null,
|
||||
action: "resolved",
|
||||
payload: { agentId: input.agentId, runId: input.runId, revisionIds },
|
||||
});
|
||||
return snapshotId;
|
||||
}
|
||||
}
|
||||
|
||||
interface RuntimeSkillRow {
|
||||
revision_id: string;
|
||||
version_semver: string;
|
||||
manifest_json: string;
|
||||
object_key: string;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ const api: DesktopApi = {
|
||||
createProviderResponse: (input) => invoke(IPC_CHANNELS.createProviderResponse, input),
|
||||
importSkillDirectory: (input) => invoke(IPC_CHANNELS.importSkillDirectory, input),
|
||||
bindSkill: (input) => invoke(IPC_CHANNELS.bindSkill, input),
|
||||
resolveAgentSkills: (input) => invoke(IPC_CHANNELS.resolveAgentSkills, input),
|
||||
createSocialAccount: (input) => invoke(IPC_CHANNELS.createSocialAccount, input),
|
||||
launchSocialLogin: (input) => invoke(IPC_CHANNELS.launchSocialLogin, input),
|
||||
importFile: (input) => invoke(IPC_CHANNELS.importFile, input),
|
||||
|
||||
@@ -92,6 +92,12 @@ export interface BindSkillInput {
|
||||
revisionId: string;
|
||||
}
|
||||
|
||||
export interface ResolveAgentSkillsInput {
|
||||
orgId: string;
|
||||
agentId: string;
|
||||
runId: string;
|
||||
}
|
||||
|
||||
export interface CreateSocialAccountInput {
|
||||
orgId: string;
|
||||
platform: SocialPlatform;
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
ImportFileInput,
|
||||
ImportSkillDirectoryInput,
|
||||
LaunchSocialLoginInput,
|
||||
ResolveAgentSkillsInput,
|
||||
SaveProviderCredentialInput,
|
||||
SendMessageInput,
|
||||
UpdateTaskStatusInput,
|
||||
@@ -34,6 +35,7 @@ export interface DesktopApi {
|
||||
createProviderResponse(input: CreateProviderResponseInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
importSkillDirectory(input: ImportSkillDirectoryInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
bindSkill(input: BindSkillInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
resolveAgentSkills(input: ResolveAgentSkillsInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
createSocialAccount(input: CreateSocialAccountInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
launchSocialLogin(input: LaunchSocialLoginInput): Promise<DesktopResult<null>>;
|
||||
importFile(input: ImportFileInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
|
||||
@@ -11,6 +11,7 @@ export const IPC_CHANNELS = {
|
||||
createProviderResponse: "agentcompany:create-provider-response",
|
||||
importSkillDirectory: "agentcompany:import-skill-directory",
|
||||
bindSkill: "agentcompany:bind-skill",
|
||||
resolveAgentSkills: "agentcompany:resolve-agent-skills",
|
||||
createSocialAccount: "agentcompany:create-social-account",
|
||||
launchSocialLogin: "agentcompany:launch-social-login",
|
||||
importFile: "agentcompany:import-file",
|
||||
|
||||
@@ -76,6 +76,8 @@ describe("domain services", () => {
|
||||
mkdirSync(skillDir, { recursive: true });
|
||||
writeFileSync(join(skillDir, "SKILL.md"), "---\nname: review-routes\n---\n# Review Routes\n");
|
||||
let orgId = "";
|
||||
let agentId = "";
|
||||
let revisionId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
@@ -84,13 +86,29 @@ describe("domain services", () => {
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
skills.importDirectory({ orgId, directoryPath: skillDir, versionSemver: "1.0.0" });
|
||||
agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada",
|
||||
role: "Architecture reviewer",
|
||||
department: "Engineering",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
});
|
||||
revisionId = skills.importDirectory({ orgId, directoryPath: skillDir, versionSemver: "1.0.0" });
|
||||
const skill = connection.db.prepare("select id from skill_definitions where org_id = ? and slug = ?")
|
||||
.get(orgId, "review-routes") as { id: string };
|
||||
skills.bind({ orgId, agentId, skillId: skill.id, revisionId });
|
||||
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: "run-review-1" });
|
||||
});
|
||||
|
||||
const state = snapshot.load();
|
||||
expect(state.skillDefinitions[0]?.slug).toBe("review-routes");
|
||||
expect(state.skillRevisions[0]?.versionSemver).toBe("1.0.0");
|
||||
expect(state.skillRevisions[0]?.immutableChecksum).toHaveLength(64);
|
||||
expect(state.skillRuntimeSnapshots[0]?.sourceRevisionIds).toContain(revisionId);
|
||||
expect(state.skillRuntimeSnapshots[0]?.compiledPrompt).toContain("# Review Routes");
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user