Promote AgentCompany-New to the repository root and retire the old app
The legacy application - its src, tests, landing site, promo assets, docs, release tooling, CI workflows, and root configs - leaves the tree; it stays reachable in history. Everything under AgentCompany-New moves up unchanged: same package name, so the production userData/database path is untouched, and e2e/build scripts already resolved paths relative to the project. The developer cred.txt stays gitignored at its new location. Full verify:ci passes from the new root.
This commit is contained in:
1 parent
2350f744e3
commit
07d0b4587d
1904 files changed
+1133
-282075
No files matched your search
@@ -0,0 +1,358 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import { normalize } from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { BindSkillInput, ImportSkillDirectoryInput, ResolveAgentSkillsInput, RunSkillTestsInput } from "../../shared/commands";
|
||||
import { SLO_SIGNAL_KEY } from "../../shared/domain";
|
||||
import { createId } from "../../shared/ids";
|
||||
import { AuditService } from "./audit-service";
|
||||
import { OperationsService } from "./operations-service";
|
||||
import { copySkillRevision, recursiveSkillChecksum } from "./skill-artifacts";
|
||||
import { loadSkillManifest, type SkillManifest } from "./skill-manifest";
|
||||
|
||||
function slugify(value: string): string {
|
||||
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
export class SkillService {
|
||||
private readonly audit: AuditService;
|
||||
private readonly operations: OperationsService;
|
||||
|
||||
constructor(private readonly db: DatabaseSync, private readonly artifactRoot: string) {
|
||||
this.audit = new AuditService(db);
|
||||
this.operations = new OperationsService(db);
|
||||
}
|
||||
|
||||
importDirectory(input: ImportSkillDirectoryInput): string {
|
||||
const manifest = loadSkillManifest(input.directoryPath, input.versionSemver);
|
||||
const slug = slugify(manifest.name);
|
||||
if (!slug) throw new Error("Skill name could not be derived.");
|
||||
const now = new Date().toISOString();
|
||||
const skillId = createId("skl");
|
||||
const revisionId = createId("skr");
|
||||
const checksum = recursiveSkillChecksum(input.directoryPath);
|
||||
const copiedFiles = copySkillRevision(input.directoryPath, this.artifactRoot, revisionId);
|
||||
const copiedEntrypoint = copiedFiles.find((file) => file.relativePath === manifest.entrypoint);
|
||||
if (!copiedEntrypoint) throw new Error("Skill manifest entrypoint was not found.");
|
||||
|
||||
this.db.prepare(`
|
||||
insert into skill_definitions(id, org_id, slug, name, owner_type, status, created_at)
|
||||
values (?, ?, ?, ?, ?, ?, ?)
|
||||
on conflict(org_id, slug) do update set name = excluded.name, status = 'active'
|
||||
`).run(skillId, input.orgId, slug, manifest.name, "organization", "active", now);
|
||||
|
||||
const existing = this.db.prepare("select id from skill_definitions where org_id = ? and slug = ?")
|
||||
.get(input.orgId, slug) as { id: string };
|
||||
|
||||
this.db.prepare(`
|
||||
insert into skill_revisions(
|
||||
id, skill_id, version_semver, manifest_json, source_type, source_ref, published_at, immutable_checksum
|
||||
) values (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
revisionId,
|
||||
existing.id,
|
||||
manifest.version,
|
||||
JSON.stringify(manifest),
|
||||
"local_directory",
|
||||
input.directoryPath,
|
||||
now,
|
||||
checksum,
|
||||
);
|
||||
for (const file of copiedFiles) {
|
||||
this.db.prepare(`
|
||||
insert into skill_artifacts(id, revision_id, artifact_type, object_key, sha256, size_bytes)
|
||||
values (?, ?, ?, ?, ?, ?)
|
||||
`).run(createId("ska"), revisionId, artifactType(file.relativePath, manifest), file.objectKey, file.sha256, file.sizeBytes);
|
||||
}
|
||||
for (const testPath of manifest.tests) {
|
||||
this.db.prepare(`
|
||||
insert into skill_tests(id, revision_id, test_type, expected_output_ref, last_result, last_run_at)
|
||||
values (?, ?, ?, ?, ?, ?)
|
||||
`).run(createId("skt"), revisionId, "manifest", testPath, null, null);
|
||||
}
|
||||
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
entityType: "skill_revision",
|
||||
entityId: revisionId,
|
||||
actorType: "system",
|
||||
actorId: null,
|
||||
action: "published",
|
||||
payload: { slug, version: manifest.version },
|
||||
});
|
||||
return revisionId;
|
||||
}
|
||||
|
||||
bind(input: BindSkillInput): string {
|
||||
this.verifyBindingTargets(input);
|
||||
const existing = this.db.prepare("select id from skill_bindings where agent_id = ? and skill_id = ?")
|
||||
.get(input.agentId, input.skillId) as { id: string } | undefined;
|
||||
const id = existing?.id ?? createId("skb");
|
||||
if (existing) {
|
||||
this.db.prepare(`
|
||||
update skill_bindings
|
||||
set pinned_revision_id = ?, binding_mode = ?, priority = ?, enabled = ?
|
||||
where id = ? and org_id = ?
|
||||
`).run(input.revisionId, "pinned", 100, 1, id, input.orgId);
|
||||
} else {
|
||||
this.db.prepare(`
|
||||
insert into skill_bindings(id, org_id, agent_id, skill_id, pinned_revision_id, binding_mode, priority, enabled)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, input.orgId, input.agentId, input.skillId, input.revisionId, "pinned", 100, 1);
|
||||
}
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
entityType: "skill_binding",
|
||||
entityId: id,
|
||||
actorType: "system",
|
||||
actorId: null,
|
||||
action: "bound",
|
||||
payload: { agentId: input.agentId, skillId: input.skillId, revisionId: input.revisionId },
|
||||
});
|
||||
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 existingSnapshot = this.db.prepare("select id from skill_runtime_snapshots where agent_id = ? and run_id = ?")
|
||||
.get(input.agentId, input.runId) as { id: string } | undefined;
|
||||
if (existingSnapshot) throw new Error("Skill runtime snapshot already exists for this run.");
|
||||
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 in ('entrypoint', '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 },
|
||||
});
|
||||
this.operations.recordSignal({
|
||||
orgId: input.orgId,
|
||||
signalKey: SLO_SIGNAL_KEY.SKILL_RESOLVE_SUCCESS,
|
||||
source: "skill_resolver",
|
||||
success: true,
|
||||
durationMs: null,
|
||||
detail: { agentId: input.agentId, runId: input.runId, revisionIds },
|
||||
});
|
||||
return snapshotId;
|
||||
}
|
||||
|
||||
runTests(input: RunSkillTestsInput): string {
|
||||
const revision = this.db.prepare(`
|
||||
select sr.id, sr.version_semver, sd.slug
|
||||
from skill_revisions sr
|
||||
join skill_definitions sd on sd.id = sr.skill_id
|
||||
where sr.id = ? and sd.org_id = ?
|
||||
`).get(input.revisionId, input.orgId) as SkillRevisionTestRow | undefined;
|
||||
if (!revision) throw new Error("Skill revision was not found.");
|
||||
|
||||
const tests = this.db.prepare(`
|
||||
select id, test_type, expected_output_ref
|
||||
from skill_tests
|
||||
where revision_id = ?
|
||||
order by expected_output_ref asc
|
||||
`).all(input.revisionId) as unknown as SkillTestRow[];
|
||||
const artifacts = this.db.prepare(`
|
||||
select artifact_type, object_key, sha256, size_bytes
|
||||
from skill_artifacts
|
||||
where revision_id = ?
|
||||
order by artifact_type asc, object_key asc
|
||||
`).all(input.revisionId) as unknown as SkillArtifactRow[];
|
||||
const structureFailure = verifyRevisionStructure(artifacts);
|
||||
if (structureFailure && tests.length === 0) throw new Error(structureFailure.failure ?? "Skill revision structure check failed.");
|
||||
const now = new Date().toISOString();
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const test of tests) {
|
||||
const result = structureFailure ?? runManifestArtifactTest(test, artifacts);
|
||||
if (result.status === "passed") passed += 1;
|
||||
if (result.status === "failed") failed += 1;
|
||||
this.db.prepare("update skill_tests set last_result = ?, last_run_at = ? where id = ?")
|
||||
.run(JSON.stringify(result), now, test.id);
|
||||
}
|
||||
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
entityType: "skill_revision",
|
||||
entityId: revision.id,
|
||||
actorType: "system",
|
||||
actorId: null,
|
||||
action: "tests_run",
|
||||
payload: { slug: revision.slug, version: revision.version_semver, total: tests.length, passed, failed },
|
||||
});
|
||||
return revision.id;
|
||||
}
|
||||
|
||||
private verifyBindingTargets(input: BindSkillInput): void {
|
||||
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 skill = this.db.prepare("select id from skill_definitions where id = ? and org_id = ?")
|
||||
.get(input.skillId, input.orgId) as { id: string } | undefined;
|
||||
if (!skill) throw new Error("Skill was not found.");
|
||||
const revision = this.db.prepare("select id from skill_revisions where id = ? and skill_id = ?")
|
||||
.get(input.revisionId, input.skillId) as { id: string } | undefined;
|
||||
if (!revision) throw new Error("Skill revision was not found.");
|
||||
}
|
||||
}
|
||||
|
||||
function artifactType(relativePath: string, manifest: SkillManifest): string {
|
||||
if (relativePath === "skill.manifest.json") return "manifest";
|
||||
if (relativePath === manifest.entrypoint) return "entrypoint";
|
||||
if (manifest.tests.includes(relativePath)) return "test";
|
||||
return "asset";
|
||||
}
|
||||
|
||||
interface RuntimeSkillRow {
|
||||
revision_id: string;
|
||||
version_semver: string;
|
||||
manifest_json: string;
|
||||
object_key: string;
|
||||
}
|
||||
|
||||
interface SkillRevisionTestRow {
|
||||
id: string;
|
||||
version_semver: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface SkillTestRow {
|
||||
id: string;
|
||||
test_type: string;
|
||||
expected_output_ref: string;
|
||||
}
|
||||
|
||||
interface SkillArtifactRow {
|
||||
artifact_type: string;
|
||||
object_key: string;
|
||||
sha256: string;
|
||||
size_bytes: number;
|
||||
}
|
||||
|
||||
interface SkillTestResult {
|
||||
status: "passed" | "failed";
|
||||
testType: string;
|
||||
expectedOutputRef: string;
|
||||
checks: Array<{ name: string; status: "passed" | "failed"; detail: string }>;
|
||||
artifactSha256?: string;
|
||||
failure?: string;
|
||||
}
|
||||
|
||||
function verifyRevisionStructure(artifacts: SkillArtifactRow[]): SkillTestResult | null {
|
||||
const requiredTypes = ["manifest", "entrypoint"];
|
||||
for (const type of requiredTypes) {
|
||||
if (!artifacts.some((artifact) => artifact.artifact_type === type)) {
|
||||
return failedSkillTest("revision_structure", type, `Skill revision artifact is missing: ${type}`);
|
||||
}
|
||||
}
|
||||
for (const artifact of artifacts) {
|
||||
try {
|
||||
verifyArtifactIntegrity(artifact);
|
||||
} catch (error) {
|
||||
return failedSkillTest("artifact_integrity", artifact.object_key, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function runManifestArtifactTest(test: SkillTestRow, artifacts: SkillArtifactRow[]): SkillTestResult {
|
||||
if (test.test_type !== "manifest") {
|
||||
return failedSkillTest(test.test_type, test.expected_output_ref, `Unsupported skill test type: ${test.test_type}`);
|
||||
}
|
||||
const testArtifact = artifacts.find((artifact) => (
|
||||
artifact.artifact_type === "test" && artifactMatchesExpectedRef(artifact.object_key, test.expected_output_ref)
|
||||
));
|
||||
if (!testArtifact) return failedSkillTest(test.test_type, test.expected_output_ref, "Skill test artifact was not found.");
|
||||
|
||||
try {
|
||||
const content = verifyArtifactIntegrity(testArtifact).toString("utf8");
|
||||
if (!content.trim()) throw new Error("Skill test artifact is empty.");
|
||||
return {
|
||||
status: "passed",
|
||||
testType: test.test_type,
|
||||
expectedOutputRef: test.expected_output_ref,
|
||||
artifactSha256: testArtifact.sha256,
|
||||
checks: [
|
||||
{ name: "manifest_test_artifact", status: "passed", detail: "Artifact exists, checksum matches, and content is non-empty." },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return failedSkillTest(test.test_type, test.expected_output_ref, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
function verifyArtifactIntegrity(artifact: SkillArtifactRow): Buffer {
|
||||
if (!existsSync(artifact.object_key)) throw new Error(`Skill artifact is missing: ${artifact.object_key}`);
|
||||
const body = readFileSync(artifact.object_key);
|
||||
const sha256 = createHash("sha256").update(body).digest("hex");
|
||||
if (sha256 !== artifact.sha256) throw new Error(`Skill artifact checksum mismatch: ${artifact.object_key}`);
|
||||
if (body.byteLength !== artifact.size_bytes) throw new Error(`Skill artifact size mismatch: ${artifact.object_key}`);
|
||||
return body;
|
||||
}
|
||||
|
||||
function artifactMatchesExpectedRef(objectKey: string, expectedRef: string): boolean {
|
||||
const expected = normalizeSkillPath(expectedRef);
|
||||
const actual = normalizeSkillPath(objectKey);
|
||||
return actual === expected || actual.endsWith(`/${expected}`);
|
||||
}
|
||||
|
||||
function normalizeSkillPath(value: string): string {
|
||||
return normalize(value).replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
function failedSkillTest(testType: string, expectedOutputRef: string, failure: string): SkillTestResult {
|
||||
return {
|
||||
status: "failed",
|
||||
testType,
|
||||
expectedOutputRef,
|
||||
checks: [{ name: "skill_test", status: "failed", detail: failure }],
|
||||
failure,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user