@nebutra/agent-runtime 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +115 -0
- package/.turbo/turbo-test.log +44 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +253 -0
- package/LICENSE +676 -0
- package/README.md +50 -0
- package/dist/adapters/dispatcher-sse.d.ts +68 -0
- package/dist/adapters/dispatcher-sse.js +11 -0
- package/dist/adapters/dispatcher-sse.js.map +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mcp-catalog.d.ts +58 -0
- package/dist/adapters/mcp-catalog.js +9 -0
- package/dist/adapters/mcp-catalog.js.map +1 -0
- package/dist/adapters/prisma-rollout.d.ts +60 -0
- package/dist/adapters/prisma-rollout.js +7 -0
- package/dist/adapters/prisma-rollout.js.map +1 -0
- package/dist/chunk-24ZXP7FI.js +93 -0
- package/dist/chunk-24ZXP7FI.js.map +1 -0
- package/dist/chunk-2DA6Q6TN.js +126 -0
- package/dist/chunk-2DA6Q6TN.js.map +1 -0
- package/dist/chunk-37BBB2P2.js +73 -0
- package/dist/chunk-37BBB2P2.js.map +1 -0
- package/dist/chunk-57W3AR43.js +52 -0
- package/dist/chunk-57W3AR43.js.map +1 -0
- package/dist/chunk-5N4644PB.js +67 -0
- package/dist/chunk-5N4644PB.js.map +1 -0
- package/dist/chunk-5YS7WAPS.js +177 -0
- package/dist/chunk-5YS7WAPS.js.map +1 -0
- package/dist/chunk-6EGG2OZC.js +13 -0
- package/dist/chunk-6EGG2OZC.js.map +1 -0
- package/dist/chunk-7BUOF367.js +126 -0
- package/dist/chunk-7BUOF367.js.map +1 -0
- package/dist/chunk-BJBBR3QA.js +121 -0
- package/dist/chunk-BJBBR3QA.js.map +1 -0
- package/dist/chunk-CGRCUKGT.js +73 -0
- package/dist/chunk-CGRCUKGT.js.map +1 -0
- package/dist/chunk-FUG5DT2C.js +75 -0
- package/dist/chunk-FUG5DT2C.js.map +1 -0
- package/dist/chunk-LO24VOA3.js +199 -0
- package/dist/chunk-LO24VOA3.js.map +1 -0
- package/dist/chunk-MUF7ZZTO.js +57 -0
- package/dist/chunk-MUF7ZZTO.js.map +1 -0
- package/dist/chunk-NN7DATXA.js +46 -0
- package/dist/chunk-NN7DATXA.js.map +1 -0
- package/dist/chunk-PGGWSUTM.js +33 -0
- package/dist/chunk-PGGWSUTM.js.map +1 -0
- package/dist/chunk-RDKYDMXT.js +135 -0
- package/dist/chunk-RDKYDMXT.js.map +1 -0
- package/dist/chunk-YYFPDBJG.js +63 -0
- package/dist/chunk-YYFPDBJG.js.map +1 -0
- package/dist/chunk-ZMYX5VBU.js +135 -0
- package/dist/chunk-ZMYX5VBU.js.map +1 -0
- package/dist/chunk-ZTSKS42I.js +131 -0
- package/dist/chunk-ZTSKS42I.js.map +1 -0
- package/dist/commands.d.ts +74 -0
- package/dist/commands.js +10 -0
- package/dist/commands.js.map +1 -0
- package/dist/definitions.d.ts +94 -0
- package/dist/definitions.js +15 -0
- package/dist/definitions.js.map +1 -0
- package/dist/dispatcher.d.ts +50 -0
- package/dist/dispatcher.js +8 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/durable-turn.d.ts +58 -0
- package/dist/durable-turn.js +9 -0
- package/dist/durable-turn.js.map +1 -0
- package/dist/hook-pipeline.d.ts +114 -0
- package/dist/hook-pipeline.js +13 -0
- package/dist/hook-pipeline.js.map +1 -0
- package/dist/index.d.ts +1874 -0
- package/dist/index.js +3117 -0
- package/dist/index.js.map +1 -0
- package/dist/loop.d.ts +78 -0
- package/dist/loop.js +9 -0
- package/dist/loop.js.map +1 -0
- package/dist/mcp-bridge.d.ts +48 -0
- package/dist/mcp-bridge.js +8 -0
- package/dist/mcp-bridge.js.map +1 -0
- package/dist/model.d.ts +154 -0
- package/dist/model.js +9 -0
- package/dist/model.js.map +1 -0
- package/dist/policy.d.ts +130 -0
- package/dist/policy.js +23 -0
- package/dist/policy.js.map +1 -0
- package/dist/protocol.d.ts +170 -0
- package/dist/protocol.js +15 -0
- package/dist/protocol.js.map +1 -0
- package/dist/rollout-store-persistent.d.ts +48 -0
- package/dist/rollout-store-persistent.js +9 -0
- package/dist/rollout-store-persistent.js.map +1 -0
- package/dist/rollout.d.ts +82 -0
- package/dist/rollout.js +15 -0
- package/dist/rollout.js.map +1 -0
- package/dist/sandbox.d.ts +65 -0
- package/dist/sandbox.js +15 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/skills.d.ts +93 -0
- package/dist/skills.js +10 -0
- package/dist/skills.js.map +1 -0
- package/dist/subagents.d.ts +129 -0
- package/dist/subagents.js +21 -0
- package/dist/subagents.js.map +1 -0
- package/dist/tools.d.ts +77 -0
- package/dist/tools.js +9 -0
- package/dist/tools.js.map +1 -0
- package/package.json +74 -0
- package/src/adapters/dispatcher-sse.test.ts +218 -0
- package/src/adapters/dispatcher-sse.ts +222 -0
- package/src/adapters/index.ts +18 -0
- package/src/adapters/mcp-catalog.test.ts +213 -0
- package/src/adapters/mcp-catalog.ts +188 -0
- package/src/adapters/prisma-rollout.test.ts +153 -0
- package/src/adapters/prisma-rollout.ts +104 -0
- package/src/agent-runtime.test.ts +176 -0
- package/src/artifact-stream.test.ts +330 -0
- package/src/artifact-stream.ts +453 -0
- package/src/channel-gateway.test.ts +432 -0
- package/src/channel-gateway.ts +357 -0
- package/src/code-review.test.ts +501 -0
- package/src/code-review.ts +495 -0
- package/src/command-suggestions.test.ts +251 -0
- package/src/command-suggestions.ts +338 -0
- package/src/commands.test.ts +184 -0
- package/src/commands.ts +140 -0
- package/src/commit-message.test.ts +249 -0
- package/src/commit-message.ts +180 -0
- package/src/context-compaction.test.ts +522 -0
- package/src/context-compaction.ts +434 -0
- package/src/definitions.test.ts +78 -0
- package/src/definitions.ts +190 -0
- package/src/deployment-status.test.ts +215 -0
- package/src/deployment-status.ts +227 -0
- package/src/design-context.test.ts +195 -0
- package/src/design-context.ts +198 -0
- package/src/dispatcher.test.ts +234 -0
- package/src/dispatcher.ts +189 -0
- package/src/durable-turn.test.ts +209 -0
- package/src/durable-turn.ts +135 -0
- package/src/edit-planner.test.ts +204 -0
- package/src/edit-planner.ts +325 -0
- package/src/fuzzy-match.test.ts +311 -0
- package/src/fuzzy-match.ts +444 -0
- package/src/hook-pipeline.test.ts +279 -0
- package/src/hook-pipeline.ts +373 -0
- package/src/inbound-admission.test.ts +394 -0
- package/src/inbound-admission.ts +246 -0
- package/src/index.ts +47 -0
- package/src/loop.test.ts +161 -0
- package/src/loop.ts +211 -0
- package/src/mcp-bridge.test.ts +165 -0
- package/src/mcp-bridge.ts +76 -0
- package/src/memory-provider.test.ts +232 -0
- package/src/memory-provider.ts +257 -0
- package/src/model.ts +168 -0
- package/src/permission-ruleset.test.ts +301 -0
- package/src/permission-ruleset.ts +200 -0
- package/src/policy.ts +151 -0
- package/src/project-repo.test.ts +232 -0
- package/src/project-repo.ts +311 -0
- package/src/protocol.ts +159 -0
- package/src/rollout-store-persistent.test.ts +217 -0
- package/src/rollout-store-persistent.ts +166 -0
- package/src/rollout.ts +150 -0
- package/src/sandbox.ts +113 -0
- package/src/session-share.test.ts +360 -0
- package/src/session-share.ts +310 -0
- package/src/skill-distillation.test.ts +177 -0
- package/src/skill-distillation.ts +369 -0
- package/src/skills.test.ts +277 -0
- package/src/skills.ts +255 -0
- package/src/subagents.test.ts +290 -0
- package/src/subagents.ts +332 -0
- package/src/tools.ts +126 -0
- package/src/workbench.test.ts +0 -0
- package/src/workbench.ts +0 -0
- package/tsconfig.json +12 -0
- package/tsup.config.ts +33 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rollout.ts"],"sourcesContent":["/**\n * Event-sourced rollout / session trace (WRAP — capability #7).\n *\n * Faithful re-expression of the upstream rollout model: an append-only typed\n * event log, state derived by replay, a first-class context-compaction marker\n * (so replay stays bounded), and a per-item persistence policy that sanitizes\n * and size-caps outputs before they are durable.\n *\n * Multi-tenant adaptation: every rollout line carries `tenantId`; the store\n * interface is tenant-scoped so a Postgres/ClickHouse-backed implementation\n * (via `@nebutra/audit` providers / `@nebutra/db`) replaces the upstream\n * single-host jsonl + embedded-SQLite assumptions. Only an in-memory\n * reference store ships here.\n */\n\nimport type { ThreadEvent, ThreadItem, TurnConfig } from \"./model\";\n\n/** Cap mirrors the upstream persisted-output ceiling. */\nexport const PERSISTED_OUTPUT_MAX_BYTES = 10_000;\n\nexport type EventPersistenceMode = \"limited\" | \"extended\";\n\n/** One durable line in a thread's append-only log. */\nexport type RolloutLine =\n | {\n readonly tenantId: string;\n readonly threadId: string;\n readonly type: \"session_meta\";\n readonly config: TurnConfig;\n readonly at: string;\n }\n | {\n readonly tenantId: string;\n readonly threadId: string;\n readonly type: \"event\";\n readonly event: ThreadEvent;\n readonly at: string;\n }\n | {\n readonly tenantId: string;\n readonly threadId: string;\n readonly type: \"turn_context\";\n readonly config: TurnConfig;\n readonly at: string;\n }\n | {\n readonly tenantId: string;\n readonly threadId: string;\n readonly type: \"compacted\";\n readonly summary: string;\n readonly droppedThrough: string;\n readonly at: string;\n };\n\n/**\n * Persistence policy: decide per item whether it is durable, and sanitize +\n * size-cap its output first. Executive markers are always persisted.\n */\nexport function isPersisted(item: ThreadItem, mode: EventPersistenceMode): boolean {\n if (item.type === \"reasoning\") return mode === \"extended\";\n return true;\n}\n\n/** Truncate aggregated command output to the persisted ceiling. */\nexport function sanitizeForPersist(item: ThreadItem): ThreadItem {\n if (item.type !== \"command_execution\") return item;\n const bytes = Buffer.byteLength(item.aggregatedOutput, \"utf8\");\n if (bytes <= PERSISTED_OUTPUT_MAX_BYTES) return item;\n const truncated = Buffer.from(item.aggregatedOutput, \"utf8\")\n .subarray(0, PERSISTED_OUTPUT_MAX_BYTES)\n .toString(\"utf8\");\n return { ...item, aggregatedOutput: `${truncated}\\n…[truncated]` };\n}\n\n/** Tenant-scoped append-only store. Replace the impl, keep the interface. */\nexport interface RolloutStore {\n append(line: RolloutLine): Promise<void>;\n /** Lines for a thread, in append order. */\n read(tenantId: string, threadId: string): Promise<readonly RolloutLine[]>;\n}\n\n/** In-memory reference store (dev/test only — never multi-instance safe). */\nexport class InMemoryRolloutStore implements RolloutStore {\n readonly #lines = new Map<string, RolloutLine[]>();\n\n #key(tenantId: string, threadId: string): string {\n return `${tenantId}::${threadId}`;\n }\n\n async append(line: RolloutLine): Promise<void> {\n const key = this.#key(line.tenantId, line.threadId);\n const list = this.#lines.get(key) ?? [];\n list.push(line);\n this.#lines.set(key, list);\n }\n\n async read(tenantId: string, threadId: string): Promise<readonly RolloutLine[]> {\n return [...(this.#lines.get(this.#key(tenantId, threadId)) ?? [])];\n }\n}\n\n/** Derived thread state — the projection rebuilt by replaying the log. */\nexport interface ThreadProjection {\n readonly tenantId: string;\n readonly threadId: string;\n config?: TurnConfig;\n readonly items: ThreadItem[];\n /** Marks where a compaction summary replaced earlier history. */\n compactionSummary?: string;\n}\n\n/**\n * Replay an append-only log into derived state. A `compacted` line bounds\n * replay: items prior to it are represented by the summary, not re-applied.\n */\nexport function replay(lines: readonly RolloutLine[]): ThreadProjection | null {\n const first = lines[0];\n if (!first) return null;\n const projection: ThreadProjection = {\n tenantId: first.tenantId,\n threadId: first.threadId,\n items: [],\n };\n for (const line of lines) {\n switch (line.type) {\n case \"session_meta\":\n case \"turn_context\":\n projection.config = line.config;\n break;\n case \"compacted\":\n projection.compactionSummary = line.summary;\n projection.items.length = 0;\n break;\n case \"event\": {\n const e = line.event;\n if (e.type === \"item.completed\" || e.type === \"item.started\") {\n const idx = projection.items.findIndex((i) => i.id === e.item.id);\n if (idx >= 0) projection.items[idx] = e.item;\n else projection.items.push(e.item);\n } else if (e.type === \"item.updated\") {\n const idx = projection.items.findIndex((i) => i.id === e.item.id);\n if (idx >= 0) projection.items[idx] = e.item;\n else projection.items.push(e.item);\n }\n break;\n }\n }\n }\n return projection;\n}\n"],"mappings":";AAkBO,IAAM,6BAA6B;AAwCnC,SAAS,YAAY,MAAkB,MAAqC;AACjF,MAAI,KAAK,SAAS,YAAa,QAAO,SAAS;AAC/C,SAAO;AACT;AAGO,SAAS,mBAAmB,MAA8B;AAC/D,MAAI,KAAK,SAAS,oBAAqB,QAAO;AAC9C,QAAM,QAAQ,OAAO,WAAW,KAAK,kBAAkB,MAAM;AAC7D,MAAI,SAAS,2BAA4B,QAAO;AAChD,QAAM,YAAY,OAAO,KAAK,KAAK,kBAAkB,MAAM,EACxD,SAAS,GAAG,0BAA0B,EACtC,SAAS,MAAM;AAClB,SAAO,EAAE,GAAG,MAAM,kBAAkB,GAAG,SAAS;AAAA,mBAAiB;AACnE;AAUO,IAAM,uBAAN,MAAmD;AAAA,EAC/C,SAAS,oBAAI,IAA2B;AAAA,EAEjD,KAAK,UAAkB,UAA0B;AAC/C,WAAO,GAAG,QAAQ,KAAK,QAAQ;AAAA,EACjC;AAAA,EAEA,MAAM,OAAO,MAAkC;AAC7C,UAAM,MAAM,KAAK,KAAK,KAAK,UAAU,KAAK,QAAQ;AAClD,UAAM,OAAO,KAAK,OAAO,IAAI,GAAG,KAAK,CAAC;AACtC,SAAK,KAAK,IAAI;AACd,SAAK,OAAO,IAAI,KAAK,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAM,KAAK,UAAkB,UAAmD;AAC9E,WAAO,CAAC,GAAI,KAAK,OAAO,IAAI,KAAK,KAAK,UAAU,QAAQ,CAAC,KAAK,CAAC,CAAE;AAAA,EACnE;AACF;AAgBO,SAAS,OAAO,OAAwD;AAC7E,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,aAA+B;AAAA,IACnC,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,OAAO,CAAC;AAAA,EACV;AACA,aAAW,QAAQ,OAAO;AACxB,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AAAA,MACL,KAAK;AACH,mBAAW,SAAS,KAAK;AACzB;AAAA,MACF,KAAK;AACH,mBAAW,oBAAoB,KAAK;AACpC,mBAAW,MAAM,SAAS;AAC1B;AAAA,MACF,KAAK,SAAS;AACZ,cAAM,IAAI,KAAK;AACf,YAAI,EAAE,SAAS,oBAAoB,EAAE,SAAS,gBAAgB;AAC5D,gBAAM,MAAM,WAAW,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE;AAChE,cAAI,OAAO,EAAG,YAAW,MAAM,GAAG,IAAI,EAAE;AAAA,cACnC,YAAW,MAAM,KAAK,EAAE,IAAI;AAAA,QACnC,WAAW,EAAE,SAAS,gBAAgB;AACpC,gBAAM,MAAM,WAAW,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE;AAChE,cAAI,OAAO,EAAG,YAAW,MAAM,GAAG,IAAI,EAAE;AAAA,cACnC,YAAW,MAAM,KAAK,EAAE,IAAI;AAAA,QACnC;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/adapters/prisma-rollout.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var recordSchema = z.object({
|
|
4
|
+
tenantId: z.string().min(1, "tenantId is required"),
|
|
5
|
+
threadId: z.string().min(1, "threadId is required"),
|
|
6
|
+
seq: z.number().int().nonnegative(),
|
|
7
|
+
at: z.string().min(1),
|
|
8
|
+
payload: z.string()
|
|
9
|
+
});
|
|
10
|
+
var querySchema = z.object({
|
|
11
|
+
tenantId: z.string().min(1, "tenantId is required"),
|
|
12
|
+
threadId: z.string().min(1, "threadId is required")
|
|
13
|
+
});
|
|
14
|
+
async function resolve(resolver, tenantId) {
|
|
15
|
+
return typeof resolver === "function" ? resolver(tenantId) : resolver;
|
|
16
|
+
}
|
|
17
|
+
function createPrismaRolloutPersistence(delegate) {
|
|
18
|
+
return {
|
|
19
|
+
async put(record) {
|
|
20
|
+
const r = recordSchema.parse(record);
|
|
21
|
+
const at = new Date(r.at);
|
|
22
|
+
if (Number.isNaN(at.getTime())) {
|
|
23
|
+
throw new Error(`invalid timestamp: ${r.at}`);
|
|
24
|
+
}
|
|
25
|
+
const db = await resolve(delegate, r.tenantId);
|
|
26
|
+
await db.create({
|
|
27
|
+
data: {
|
|
28
|
+
tenantId: r.tenantId,
|
|
29
|
+
threadId: r.threadId,
|
|
30
|
+
seq: r.seq,
|
|
31
|
+
at,
|
|
32
|
+
payload: r.payload
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
async list(tenantId, threadId) {
|
|
37
|
+
const q = querySchema.parse({ tenantId, threadId });
|
|
38
|
+
const db = await resolve(delegate, q.tenantId);
|
|
39
|
+
const rows = await db.findMany({
|
|
40
|
+
where: { tenantId: q.tenantId, threadId: q.threadId },
|
|
41
|
+
orderBy: { seq: "asc" },
|
|
42
|
+
select: { seq: true, payload: true }
|
|
43
|
+
});
|
|
44
|
+
return rows.map((row) => ({ seq: row.seq, payload: row.payload }));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
createPrismaRolloutPersistence
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-57W3AR43.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/prisma-rollout.ts"],"sourcesContent":["/**\n * Durable rollout-store backend: `RolloutPersistencePort` over Prisma.\n *\n * This is the fail-loud system-of-record adapter (table `agent_rollout_lines`,\n * migration `20260519000000_add_agent_rollout_store`). Unlike a best-effort\n * audit log it never swallows a failed write — a rejected `put` propagates so\n * the caller's \"never silently drop\" contract holds.\n *\n * Reusability: it depends only on a minimal injected delegate (the\n * `PrismaAuditDelegate` pattern), NOT on the generated Prisma client or\n * `@nebutra/db`. Any tenant-scoped Prisma client (`getTenantDb(orgId)`)\n * structurally satisfies it, so this package stays dependency-light.\n */\n\nimport { z } from \"zod\";\nimport type { RolloutPersistencePort } from \"../rollout-store-persistent\";\n\n/**\n * Minimal structural slice of `prisma.agentRolloutLine`. Decoupled from the\n * generated client on purpose — only these two operations are needed.\n */\nexport interface PrismaRolloutDelegate {\n create(args: {\n data: {\n tenantId: string;\n threadId: string;\n seq: number;\n at: Date;\n payload: string;\n };\n }): Promise<unknown>;\n findMany(args: {\n where: { tenantId: string; threadId: string };\n orderBy: { seq: \"asc\" };\n select: { seq: true; payload: true };\n }): Promise<{ seq: number; payload: string }[]>;\n}\n\n/** Resolves the delegate per call so a request-scoped (RLS) client can be used. */\nexport type RolloutDelegateResolver =\n | PrismaRolloutDelegate\n | ((tenantId: string) => PrismaRolloutDelegate | Promise<PrismaRolloutDelegate>);\n\nconst recordSchema = z.object({\n tenantId: z.string().min(1, \"tenantId is required\"),\n threadId: z.string().min(1, \"threadId is required\"),\n seq: z.number().int().nonnegative(),\n at: z.string().min(1),\n payload: z.string(),\n});\n\nconst querySchema = z.object({\n tenantId: z.string().min(1, \"tenantId is required\"),\n threadId: z.string().min(1, \"threadId is required\"),\n});\n\nasync function resolve(\n resolver: RolloutDelegateResolver,\n tenantId: string,\n): Promise<PrismaRolloutDelegate> {\n return typeof resolver === \"function\" ? resolver(tenantId) : resolver;\n}\n\n/**\n * Build a {@link RolloutPersistencePort} backed by Prisma.\n *\n * @param delegate a `PrismaRolloutDelegate`, or a resolver mapping a tenantId\n * to one (use the latter to obtain a per-tenant RLS client per call).\n */\nexport function createPrismaRolloutPersistence(\n delegate: RolloutDelegateResolver,\n): RolloutPersistencePort {\n return {\n async put(record): Promise<void> {\n const r = recordSchema.parse(record);\n const at = new Date(r.at);\n if (Number.isNaN(at.getTime())) {\n throw new Error(`invalid timestamp: ${r.at}`);\n }\n const db = await resolve(delegate, r.tenantId);\n // Fail-loud: any persistence rejection propagates to the caller.\n await db.create({\n data: {\n tenantId: r.tenantId,\n threadId: r.threadId,\n seq: r.seq,\n at,\n payload: r.payload,\n },\n });\n },\n\n async list(tenantId, threadId): Promise<{ seq: number; payload: string }[]> {\n const q = querySchema.parse({ tenantId, threadId });\n const db = await resolve(delegate, q.tenantId);\n const rows = await db.findMany({\n where: { tenantId: q.tenantId, threadId: q.threadId },\n orderBy: { seq: \"asc\" },\n select: { seq: true, payload: true },\n });\n return rows.map((row) => ({ seq: row.seq, payload: row.payload }));\n },\n };\n}\n"],"mappings":";AAcA,SAAS,SAAS;AA6BlB,IAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AAAA,EAClD,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AAAA,EAClD,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAClC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACpB,SAAS,EAAE,OAAO;AACpB,CAAC;AAED,IAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AAAA,EAClD,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AACpD,CAAC;AAED,eAAe,QACb,UACA,UACgC;AAChC,SAAO,OAAO,aAAa,aAAa,SAAS,QAAQ,IAAI;AAC/D;AAQO,SAAS,+BACd,UACwB;AACxB,SAAO;AAAA,IACL,MAAM,IAAI,QAAuB;AAC/B,YAAM,IAAI,aAAa,MAAM,MAAM;AACnC,YAAM,KAAK,IAAI,KAAK,EAAE,EAAE;AACxB,UAAI,OAAO,MAAM,GAAG,QAAQ,CAAC,GAAG;AAC9B,cAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE,EAAE;AAAA,MAC9C;AACA,YAAM,KAAK,MAAM,QAAQ,UAAU,EAAE,QAAQ;AAE7C,YAAM,GAAG,OAAO;AAAA,QACd,MAAM;AAAA,UACJ,UAAU,EAAE;AAAA,UACZ,UAAU,EAAE;AAAA,UACZ,KAAK,EAAE;AAAA,UACP;AAAA,UACA,SAAS,EAAE;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,KAAK,UAAU,UAAuD;AAC1E,YAAM,IAAI,YAAY,MAAM,EAAE,UAAU,SAAS,CAAC;AAClD,YAAM,KAAK,MAAM,QAAQ,UAAU,EAAE,QAAQ;AAC7C,YAAM,OAAO,MAAM,GAAG,SAAS;AAAA,QAC7B,OAAO,EAAE,UAAU,EAAE,UAAU,UAAU,EAAE,SAAS;AAAA,QACpD,SAAS,EAAE,KAAK,MAAM;AAAA,QACtB,QAAQ,EAAE,KAAK,MAAM,SAAS,KAAK;AAAA,MACrC,CAAC;AACD,aAAO,KAAK,IAAI,CAAC,SAAS,EAAE,KAAK,IAAI,KAAK,SAAS,IAAI,QAAQ,EAAE;AAAA,IACnE;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isTurnTerminal
|
|
3
|
+
} from "./chunk-6EGG2OZC.js";
|
|
4
|
+
import {
|
|
5
|
+
replay
|
|
6
|
+
} from "./chunk-37BBB2P2.js";
|
|
7
|
+
|
|
8
|
+
// src/durable-turn.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
var DURABLE_TURN_JOB = "agent-runtime.durable-turn";
|
|
11
|
+
var contextSchema = z.object({
|
|
12
|
+
tenantId: z.string().min(1),
|
|
13
|
+
threadId: z.string().min(1)
|
|
14
|
+
});
|
|
15
|
+
var jobPayloadSchema = z.object({
|
|
16
|
+
tenantId: z.string().min(1),
|
|
17
|
+
threadId: z.string().min(1),
|
|
18
|
+
input: z.string()
|
|
19
|
+
});
|
|
20
|
+
function turnIdFor(tenantId, threadId) {
|
|
21
|
+
return `${DURABLE_TURN_JOB}:${tenantId}:${threadId}`;
|
|
22
|
+
}
|
|
23
|
+
function recordedEvents(lines) {
|
|
24
|
+
return lines.flatMap((line) => line.type === "event" ? [line.event] : []);
|
|
25
|
+
}
|
|
26
|
+
function createDurableTurn(deps) {
|
|
27
|
+
const { store, queue, runner } = deps;
|
|
28
|
+
queue.registerHandler(DURABLE_TURN_JOB, async (payload) => {
|
|
29
|
+
const { tenantId, threadId, input } = jobPayloadSchema.parse(payload);
|
|
30
|
+
const gen = runner(input, { tenantId, threadId });
|
|
31
|
+
for await (const _event of gen) {
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
async function start(input, ctx) {
|
|
35
|
+
const { tenantId, threadId } = contextSchema.parse(ctx);
|
|
36
|
+
const turnId = turnIdFor(tenantId, threadId);
|
|
37
|
+
await queue.enqueue({
|
|
38
|
+
name: DURABLE_TURN_JOB,
|
|
39
|
+
jobId: turnId,
|
|
40
|
+
payload: { tenantId, threadId, input }
|
|
41
|
+
});
|
|
42
|
+
return { turnId };
|
|
43
|
+
}
|
|
44
|
+
async function* resume(tenantId, threadId) {
|
|
45
|
+
const scope = contextSchema.parse({ tenantId, threadId });
|
|
46
|
+
const lines = await store.read(scope.tenantId, scope.threadId);
|
|
47
|
+
const projection = replay(lines);
|
|
48
|
+
const events = recordedEvents(lines);
|
|
49
|
+
const last = events[events.length - 1];
|
|
50
|
+
if (projection && last && isTurnTerminal(last)) {
|
|
51
|
+
for (const event of events) yield event;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const input = "";
|
|
55
|
+
const gen = runner(input, {
|
|
56
|
+
tenantId: scope.tenantId,
|
|
57
|
+
threadId: scope.threadId
|
|
58
|
+
});
|
|
59
|
+
for await (const event of gen) yield event;
|
|
60
|
+
}
|
|
61
|
+
return { start, resume };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
createDurableTurn
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=chunk-5N4644PB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/durable-turn.ts"],"sourcesContent":["/**\n * Durable / resumable turn (WRAP — capability #10).\n *\n * Wraps the existing {@link runTurn} async generator so a turn survives a\n * process crash. `start` enqueues a tenant+thread-scoped durable job whose\n * handler drains `runTurn` (the loop runner already appends every event to the\n * tenant-scoped {@link RolloutStore}); the durable layer guarantees the job is\n * replayable if the worker dies mid-turn. `resume` inspects the persisted\n * rollout: if the last recorded event is non-terminal the turn is re-driven to\n * completion, otherwise the recorded terminal stream is replayed.\n *\n * Queue binding is interface-only — callers wire `@nebutra/queue` by\n * implementing {@link DurableTurnQueuePort}; this package never depends on it.\n * Tenant scoping is mandatory and fail-closed: a missing tenant or thread\n * throws, and resume can never read another tenant's rollout.\n */\n\nimport { z } from \"zod\";\nimport type { ThreadEvent } from \"./model\";\nimport { isTurnTerminal } from \"./model\";\nimport { type RolloutLine, type RolloutStore, replay } from \"./rollout\";\n\n/** A durable job handed to the queue. Mirrors `@nebutra/queue` job shape. */\nexport interface DurableTurnJob {\n /** Logical job name — used for handler routing. */\n readonly name: string;\n /** Tenant+thread-scoped id; the queue must de-dupe / make idempotent on it. */\n readonly jobId: string;\n /** Opaque Zod-validated payload (kept narrow on purpose). */\n readonly payload: unknown;\n}\n\n/**\n * Minimal injectable queue seam (enqueue + handler registration). Mirrors\n * `@nebutra/queue`'s `defineQueueJob` / `createJobRouter` contract without a\n * hard dependency, so this package stays provider-agnostic.\n */\nexport interface DurableTurnQueuePort {\n enqueue(job: DurableTurnJob): Promise<void>;\n registerHandler(name: string, handler: (payload: unknown) => Promise<void>): void;\n}\n\n/** Drives one turn, yielding the event stream (persists internally). */\nexport type DurableTurnRunner = (input: string, ctx: TurnContext) => AsyncGenerator<ThreadEvent>;\n\nexport interface TurnContext {\n readonly tenantId: string;\n readonly threadId: string;\n}\n\nexport interface CreateDurableTurnDeps {\n readonly store: RolloutStore;\n readonly queue: DurableTurnQueuePort;\n readonly runner: DurableTurnRunner;\n}\n\nexport interface DurableTurn {\n start(input: string, ctx: TurnContext): Promise<{ readonly turnId: string }>;\n resume(tenantId: string, threadId: string): AsyncGenerator<ThreadEvent>;\n}\n\nconst DURABLE_TURN_JOB = \"agent-runtime.durable-turn\";\n\nconst contextSchema = z.object({\n tenantId: z.string().min(1),\n threadId: z.string().min(1),\n});\n\nconst jobPayloadSchema = z.object({\n tenantId: z.string().min(1),\n threadId: z.string().min(1),\n input: z.string(),\n});\n\n/** Tenant+thread-scoped, stable job id (idempotency key on the queue). */\nfunction turnIdFor(tenantId: string, threadId: string): string {\n return `${DURABLE_TURN_JOB}:${tenantId}:${threadId}`;\n}\n\n/** Recorded `ThreadEvent`s in append order, tenant-scoped via the store read. */\nfunction recordedEvents(lines: readonly RolloutLine[]): ThreadEvent[] {\n return lines.flatMap((line) => (line.type === \"event\" ? [line.event] : []));\n}\n\nexport function createDurableTurn(deps: CreateDurableTurnDeps): DurableTurn {\n const { store, queue, runner } = deps;\n\n // Register the durable handler once: drain the runner so every event is\n // persisted by the loop runner. Crash before/within = job stays replayable.\n queue.registerHandler(DURABLE_TURN_JOB, async (payload: unknown) => {\n const { tenantId, threadId, input } = jobPayloadSchema.parse(payload);\n const gen = runner(input, { tenantId, threadId });\n for await (const _event of gen) {\n // The runner persists each event; nothing to do but exhaust it.\n }\n });\n\n async function start(input: string, ctx: TurnContext): Promise<{ readonly turnId: string }> {\n const { tenantId, threadId } = contextSchema.parse(ctx);\n const turnId = turnIdFor(tenantId, threadId);\n await queue.enqueue({\n name: DURABLE_TURN_JOB,\n jobId: turnId,\n payload: { tenantId, threadId, input },\n });\n return { turnId };\n }\n\n async function* resume(tenantId: string, threadId: string): AsyncGenerator<ThreadEvent> {\n const scope = contextSchema.parse({ tenantId, threadId });\n // Tenant-scoped read — cross-tenant resume is structurally impossible.\n const lines = await store.read(scope.tenantId, scope.threadId);\n const projection = replay(lines);\n const events = recordedEvents(lines);\n const last = events[events.length - 1];\n\n if (projection && last && isTurnTerminal(last)) {\n // Completed turn — replay the recorded stream, append nothing.\n for (const event of events) yield event;\n return;\n }\n\n // Unfinished (crash) or never-started — re-drive to completion. The runner\n // appends fresh lines; we recover the original input from the rollout if\n // present, else an empty resume prompt (turn is replay-driven thereafter).\n const input = \"\";\n const gen = runner(input, {\n tenantId: scope.tenantId,\n threadId: scope.threadId,\n });\n for await (const event of gen) yield event;\n }\n\n return { start, resume };\n}\n"],"mappings":";;;;;;;;AAiBA,SAAS,SAAS;AA4ClB,IAAM,mBAAmB;AAEzB,IAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAC5B,CAAC;AAED,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,OAAO,EAAE,OAAO;AAClB,CAAC;AAGD,SAAS,UAAU,UAAkB,UAA0B;AAC7D,SAAO,GAAG,gBAAgB,IAAI,QAAQ,IAAI,QAAQ;AACpD;AAGA,SAAS,eAAe,OAA8C;AACpE,SAAO,MAAM,QAAQ,CAAC,SAAU,KAAK,SAAS,UAAU,CAAC,KAAK,KAAK,IAAI,CAAC,CAAE;AAC5E;AAEO,SAAS,kBAAkB,MAA0C;AAC1E,QAAM,EAAE,OAAO,OAAO,OAAO,IAAI;AAIjC,QAAM,gBAAgB,kBAAkB,OAAO,YAAqB;AAClE,UAAM,EAAE,UAAU,UAAU,MAAM,IAAI,iBAAiB,MAAM,OAAO;AACpE,UAAM,MAAM,OAAO,OAAO,EAAE,UAAU,SAAS,CAAC;AAChD,qBAAiB,UAAU,KAAK;AAAA,IAEhC;AAAA,EACF,CAAC;AAED,iBAAe,MAAM,OAAe,KAAwD;AAC1F,UAAM,EAAE,UAAU,SAAS,IAAI,cAAc,MAAM,GAAG;AACtD,UAAM,SAAS,UAAU,UAAU,QAAQ;AAC3C,UAAM,MAAM,QAAQ;AAAA,MAClB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS,EAAE,UAAU,UAAU,MAAM;AAAA,IACvC,CAAC;AACD,WAAO,EAAE,OAAO;AAAA,EAClB;AAEA,kBAAgB,OAAO,UAAkB,UAA+C;AACtF,UAAM,QAAQ,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;AAExD,UAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,UAAU,MAAM,QAAQ;AAC7D,UAAM,aAAa,OAAO,KAAK;AAC/B,UAAM,SAAS,eAAe,KAAK;AACnC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AAErC,QAAI,cAAc,QAAQ,eAAe,IAAI,GAAG;AAE9C,iBAAW,SAAS,OAAQ,OAAM;AAClC;AAAA,IACF;AAKA,UAAM,QAAQ;AACd,UAAM,MAAM,OAAO,OAAO;AAAA,MACxB,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,IAClB,CAAC;AACD,qBAAiB,SAAS,IAAK,OAAM;AAAA,EACvC;AAEA,SAAO,EAAE,OAAO,OAAO;AACzB;","names":[]}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/subagents.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
function resolveSubagentTools(fm, allTools) {
|
|
4
|
+
const deny = new Set(fm.disallowedTools);
|
|
5
|
+
const allow = fm.allowedTools;
|
|
6
|
+
if (allow.length === 0) {
|
|
7
|
+
return allTools.filter((t) => !deny.has(t));
|
|
8
|
+
}
|
|
9
|
+
const allowSet = new Set(allow);
|
|
10
|
+
return allTools.filter((t) => allowSet.has(t) && !deny.has(t));
|
|
11
|
+
}
|
|
12
|
+
function isIncompleteToolCall(item) {
|
|
13
|
+
if (item.type === "mcp_tool_call" || item.type === "command_execution") {
|
|
14
|
+
return item.status === "in_progress";
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
function filterIncompleteToolCalls(items) {
|
|
19
|
+
return items.filter((it) => !isIncompleteToolCall(it));
|
|
20
|
+
}
|
|
21
|
+
function directiveItem(idSeed, brief) {
|
|
22
|
+
return { id: `brief_${idSeed}`, type: "agent_message", text: brief };
|
|
23
|
+
}
|
|
24
|
+
function prepareDispatch(input) {
|
|
25
|
+
const { definition, mode, parentItems, brief, ctx, allTools } = input;
|
|
26
|
+
if (!ctx.tenantId) throw new Error("DispatchCtx.tenantId is required");
|
|
27
|
+
if (definition.tenantId !== ctx.tenantId) {
|
|
28
|
+
throw new Error("subagent definition belongs to a different tenant (fail closed)");
|
|
29
|
+
}
|
|
30
|
+
const trimmedBrief = brief.trim();
|
|
31
|
+
const fm = definition.frontmatter;
|
|
32
|
+
const toolScope = resolveSubagentTools(fm, allTools ?? fm.allowedTools);
|
|
33
|
+
let initialContext;
|
|
34
|
+
if (mode === "spawn") {
|
|
35
|
+
if (!trimmedBrief) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"spawn dispatch requires a non-empty brief \u2014 caller must fully brief the child"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
initialContext = [directiveItem(definition.slug, trimmedBrief)];
|
|
41
|
+
} else {
|
|
42
|
+
const inherited = parentItems ? filterIncompleteToolCalls(parentItems) : [];
|
|
43
|
+
initialContext = trimmedBrief ? [...inherited, directiveItem(definition.slug, trimmedBrief)] : [...inherited];
|
|
44
|
+
}
|
|
45
|
+
const modelOverride = fm.model && fm.model !== "inherit" ? fm.model : void 0;
|
|
46
|
+
return {
|
|
47
|
+
initialContext,
|
|
48
|
+
toolScope,
|
|
49
|
+
systemPromptRef: definition.bodyRef,
|
|
50
|
+
...modelOverride ? { modelOverride } : {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
var TASK_KINDS = ["subagent", "deferred"];
|
|
54
|
+
var TASK_STATUSES = ["pending", "running", "completed", "failed", "killed"];
|
|
55
|
+
var LEGAL = {
|
|
56
|
+
pending: ["running", "killed", "failed"],
|
|
57
|
+
running: ["completed", "failed", "killed"],
|
|
58
|
+
completed: [],
|
|
59
|
+
failed: [],
|
|
60
|
+
killed: []
|
|
61
|
+
};
|
|
62
|
+
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "killed"]);
|
|
63
|
+
var __seq = 0;
|
|
64
|
+
function nextTaskId() {
|
|
65
|
+
__seq += 1;
|
|
66
|
+
return `task_${Date.now().toString(36)}_${__seq.toString(36)}`;
|
|
67
|
+
}
|
|
68
|
+
function requireTenant(tenantId) {
|
|
69
|
+
if (!tenantId) throw new Error("tenantId is required (fail closed)");
|
|
70
|
+
}
|
|
71
|
+
var TaskRegistry = class {
|
|
72
|
+
#tasks = /* @__PURE__ */ new Map();
|
|
73
|
+
create(tenantId, kind) {
|
|
74
|
+
requireTenant(tenantId);
|
|
75
|
+
const taskId = nextTaskId();
|
|
76
|
+
this.#tasks.set(taskId, {
|
|
77
|
+
taskId,
|
|
78
|
+
tenantId,
|
|
79
|
+
status: "pending",
|
|
80
|
+
notified: false,
|
|
81
|
+
kind,
|
|
82
|
+
createdAtMs: Date.now()
|
|
83
|
+
});
|
|
84
|
+
return taskId;
|
|
85
|
+
}
|
|
86
|
+
/** Tenant-checked read. Returns undefined for unknown or cross-tenant ids. */
|
|
87
|
+
get(taskId, tenantId) {
|
|
88
|
+
requireTenant(tenantId);
|
|
89
|
+
const rec = this.#tasks.get(taskId);
|
|
90
|
+
if (!rec || rec.tenantId !== tenantId) return void 0;
|
|
91
|
+
return rec;
|
|
92
|
+
}
|
|
93
|
+
#ownedOrThrow(taskId, tenantId) {
|
|
94
|
+
requireTenant(tenantId);
|
|
95
|
+
const rec = this.#tasks.get(taskId);
|
|
96
|
+
if (!rec) throw new Error(`task ${taskId} not found`);
|
|
97
|
+
if (rec.tenantId !== tenantId) {
|
|
98
|
+
throw new Error("cross-tenant task access denied (fail closed)");
|
|
99
|
+
}
|
|
100
|
+
return rec;
|
|
101
|
+
}
|
|
102
|
+
transition(taskId, to, tenantId) {
|
|
103
|
+
const rec = this.#ownedOrThrow(taskId, tenantId);
|
|
104
|
+
const allowed = LEGAL[rec.status];
|
|
105
|
+
if (!allowed.includes(to)) {
|
|
106
|
+
throw new Error(`illegal task transition ${rec.status} \u2192 ${to}`);
|
|
107
|
+
}
|
|
108
|
+
const next = { ...rec, status: to };
|
|
109
|
+
this.#tasks.set(taskId, next);
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
/** Unified kill — drives any non-terminal task to `killed`. */
|
|
113
|
+
stop(taskId, tenantId) {
|
|
114
|
+
const rec = this.#ownedOrThrow(taskId, tenantId);
|
|
115
|
+
if (TERMINAL.has(rec.status)) return rec;
|
|
116
|
+
return this.transition(taskId, "killed", tenantId);
|
|
117
|
+
}
|
|
118
|
+
markNotified(taskId, tenantId) {
|
|
119
|
+
const rec = this.#ownedOrThrow(taskId, tenantId);
|
|
120
|
+
const next = { ...rec, notified: true };
|
|
121
|
+
this.#tasks.set(taskId, next);
|
|
122
|
+
return next;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function assertNotPeeking(task) {
|
|
126
|
+
if (!TERMINAL.has(task.status)) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`cannot peek at in-flight task ${task.taskId} (status=${task.status}); await settlement`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
var NO_OUTPUT_MARKER = {
|
|
133
|
+
id: "subagent_no_output",
|
|
134
|
+
type: "error",
|
|
135
|
+
message: "subagent completed but returned no output"
|
|
136
|
+
};
|
|
137
|
+
var threadItemShape = z.object({ id: z.string(), type: z.string() });
|
|
138
|
+
function normalizeContent(content) {
|
|
139
|
+
for (const item of content) threadItemShape.parse(item);
|
|
140
|
+
return content.length === 0 ? [NO_OUTPUT_MARKER] : content;
|
|
141
|
+
}
|
|
142
|
+
function countToolUses(content) {
|
|
143
|
+
return content.filter((i) => i.type === "mcp_tool_call" || i.type === "command_execution").length;
|
|
144
|
+
}
|
|
145
|
+
function makeDeferred(taskId, channel) {
|
|
146
|
+
if (!channel) throw new Error("deferred envelope requires a delivery channel");
|
|
147
|
+
return { kind: "deferred", taskId, channel };
|
|
148
|
+
}
|
|
149
|
+
function settleDeferred(target, content, usage) {
|
|
150
|
+
const { taskId, tenantId, registry } = target;
|
|
151
|
+
const rec = registry.get(taskId, tenantId);
|
|
152
|
+
if (!rec) throw new Error("cannot settle unknown or cross-tenant task (fail closed)");
|
|
153
|
+
if (!TERMINAL.has(rec.status)) {
|
|
154
|
+
registry.transition(taskId, "completed", tenantId);
|
|
155
|
+
}
|
|
156
|
+
const normalized = normalizeContent(content);
|
|
157
|
+
return {
|
|
158
|
+
kind: "sync",
|
|
159
|
+
content: normalized,
|
|
160
|
+
usage,
|
|
161
|
+
agentId: taskId,
|
|
162
|
+
toolUses: countToolUses(normalized),
|
|
163
|
+
durationMs: Math.max(0, Date.now() - rec.createdAtMs)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
resolveSubagentTools,
|
|
169
|
+
prepareDispatch,
|
|
170
|
+
TASK_KINDS,
|
|
171
|
+
TASK_STATUSES,
|
|
172
|
+
TaskRegistry,
|
|
173
|
+
assertNotPeeking,
|
|
174
|
+
makeDeferred,
|
|
175
|
+
settleDeferred
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=chunk-5YS7WAPS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/subagents.ts"],"sourcesContent":["/**\n * Subagent / task dispatch (WRAP — isolated-child-agent model, the delta).\n *\n * Faithful re-expression of the source harness's child-agent dispatch grammar:\n * a registry decides WHICH definition runs, a context-boundary contract decides\n * WITH WHAT inherited context it starts, and a typed terminal envelope decides\n * HOW its result re-enters the parent. The child loop, child policy, and\n * rollout store live elsewhere in agent-runtime and are intentionally NOT\n * reimplemented here — this layer is pure decision + data + state machine.\n *\n * Multi-tenant: every task operation is keyed by `tenantId` and fails closed.\n * Pure data/logic — no FS scan, no shell, no network.\n *\n * NOTE on dropped task kinds: the source harness models several local execution\n * task kinds (local_bash / monitor_mcp / worktree). Those are deliberately NOT\n * represented here: this package never executes locally — execution is delegated\n * via the external-sandbox seam — so only the agent-facing `subagent` kind (and\n * its `deferred` sibling) is modelled.\n */\n\nimport { z } from \"zod\";\nimport type { Definition, Frontmatter } from \"./definitions\";\nimport type { ThreadItem, TurnUsage } from \"./model\";\n\n/* ───────────────────────── Subagent definition ─────────────────────────── */\n\n/** A subagent definition is just a `Definition` (same loader as skills/commands). */\nexport type SubagentRecord = Definition;\n\n/**\n * Effective tool set for a subagent: `allowedTools` minus `disallowedTools`,\n * intersected with the runtime tool universe so a definition can never widen\n * its scope past what the host exposes. When `allowedTools` is empty the\n * contract is \"all tools except the denied ones\" (all-except-deny).\n *\n * Pure — order follows `allTools` for determinism.\n */\nexport function resolveSubagentTools(fm: Frontmatter, allTools: readonly string[]): string[] {\n const deny = new Set(fm.disallowedTools);\n const allow = fm.allowedTools;\n if (allow.length === 0) {\n return allTools.filter((t) => !deny.has(t));\n }\n const allowSet = new Set(allow);\n return allTools.filter((t) => allowSet.has(t) && !deny.has(t));\n}\n\n/* ───────────────────── Context-boundary contract ───────────────────────── */\n\nexport type DispatchMode = \"spawn\" | \"fork\";\n\nexport interface DispatchCtx {\n readonly tenantId: string;\n}\n\nexport interface PrepareDispatchInput {\n readonly definition: SubagentRecord;\n readonly mode: DispatchMode;\n readonly parentItems?: readonly ThreadItem[];\n /** Caller's directive. Mandatory & non-empty in `spawn` mode. */\n readonly brief: string;\n readonly ctx: DispatchCtx;\n /** Runtime tool universe; defaults to the definition's own allow list. */\n readonly allTools?: readonly string[];\n}\n\nexport interface PreparedDispatch {\n readonly initialContext: readonly ThreadItem[];\n readonly toolScope: readonly string[];\n /** Opaque reference to the system prompt body (resolved by the child loop). */\n readonly systemPromptRef: string;\n readonly modelOverride?: string;\n}\n\n/** True for a trailing tool-call item that never received a terminal result. */\nfunction isIncompleteToolCall(item: ThreadItem): boolean {\n if (item.type === \"mcp_tool_call\" || item.type === \"command_execution\") {\n return item.status === \"in_progress\";\n }\n return false;\n}\n\n/**\n * Drop trailing incomplete tool-call items so a forked child never inherits a\n * dangling tool_use without its result. Pure — input array is never mutated.\n */\nfunction filterIncompleteToolCalls(items: readonly ThreadItem[]): ThreadItem[] {\n return items.filter((it) => !isIncompleteToolCall(it));\n}\n\nfunction directiveItem(idSeed: string, brief: string): ThreadItem {\n return { id: `brief_${idSeed}`, type: \"agent_message\", text: brief };\n}\n\n/**\n * Decide the child's starting context boundary.\n *\n * - `spawn`: zero inherited context. The caller MUST fully brief the child;\n * an empty brief is a programming error and throws.\n * - `fork`: inherit a defensive copy of `parentItems` with incomplete tool\n * calls filtered out, then append the brief as a directive.\n *\n * `parentItems` is never mutated. Cross-tenant dispatch fails closed.\n */\nexport function prepareDispatch(input: PrepareDispatchInput): PreparedDispatch {\n const { definition, mode, parentItems, brief, ctx, allTools } = input;\n if (!ctx.tenantId) throw new Error(\"DispatchCtx.tenantId is required\");\n if (definition.tenantId !== ctx.tenantId) {\n throw new Error(\"subagent definition belongs to a different tenant (fail closed)\");\n }\n\n const trimmedBrief = brief.trim();\n const fm = definition.frontmatter;\n const toolScope = resolveSubagentTools(fm, allTools ?? fm.allowedTools);\n\n let initialContext: ThreadItem[];\n if (mode === \"spawn\") {\n if (!trimmedBrief) {\n throw new Error(\n \"spawn dispatch requires a non-empty brief — caller must fully brief the child\",\n );\n }\n initialContext = [directiveItem(definition.slug, trimmedBrief)];\n } else {\n const inherited = parentItems ? filterIncompleteToolCalls(parentItems) : [];\n initialContext = trimmedBrief\n ? [...inherited, directiveItem(definition.slug, trimmedBrief)]\n : [...inherited];\n }\n\n const modelOverride = fm.model && fm.model !== \"inherit\" ? fm.model : undefined;\n\n return {\n initialContext,\n toolScope,\n systemPromptRef: definition.bodyRef,\n ...(modelOverride ? { modelOverride } : {}),\n };\n}\n\n/* ─────────────────── Uniform Task lifecycle registry ───────────────────── */\n\nexport const TASK_KINDS = [\"subagent\", \"deferred\"] as const;\nexport type TaskKind = (typeof TASK_KINDS)[number];\n\nexport const TASK_STATUSES = [\"pending\", \"running\", \"completed\", \"failed\", \"killed\"] as const;\nexport type TaskStatus = (typeof TASK_STATUSES)[number];\n\nexport interface TaskRecord {\n readonly taskId: string;\n readonly tenantId: string;\n readonly status: TaskStatus;\n readonly notified: boolean;\n readonly kind: TaskKind;\n readonly createdAtMs: number;\n}\n\n/** Legal status transitions. Any pair absent here is rejected. */\nconst LEGAL: Readonly<Record<TaskStatus, readonly TaskStatus[]>> = {\n pending: [\"running\", \"killed\", \"failed\"],\n running: [\"completed\", \"failed\", \"killed\"],\n completed: [],\n failed: [],\n killed: [],\n};\n\nconst TERMINAL: ReadonlySet<TaskStatus> = new Set([\"completed\", \"failed\", \"killed\"]);\n\nlet __seq = 0;\nfunction nextTaskId(): string {\n __seq += 1;\n return `task_${Date.now().toString(36)}_${__seq.toString(36)}`;\n}\n\nfunction requireTenant(tenantId: string): void {\n if (!tenantId) throw new Error(\"tenantId is required (fail closed)\");\n}\n\n/**\n * Tenant-scoped task registry. Every mutating op verifies the caller's\n * tenant against the stored record; a mismatch is treated as \"not found\"\n * for reads and throws for mutations — cross-tenant access is impossible.\n */\nexport class TaskRegistry {\n readonly #tasks = new Map<string, TaskRecord>();\n\n create(tenantId: string, kind: TaskKind): string {\n requireTenant(tenantId);\n const taskId = nextTaskId();\n this.#tasks.set(taskId, {\n taskId,\n tenantId,\n status: \"pending\",\n notified: false,\n kind,\n createdAtMs: Date.now(),\n });\n return taskId;\n }\n\n /** Tenant-checked read. Returns undefined for unknown or cross-tenant ids. */\n get(taskId: string, tenantId: string): TaskRecord | undefined {\n requireTenant(tenantId);\n const rec = this.#tasks.get(taskId);\n if (!rec || rec.tenantId !== tenantId) return undefined;\n return rec;\n }\n\n #ownedOrThrow(taskId: string, tenantId: string): TaskRecord {\n requireTenant(tenantId);\n const rec = this.#tasks.get(taskId);\n if (!rec) throw new Error(`task ${taskId} not found`);\n if (rec.tenantId !== tenantId) {\n throw new Error(\"cross-tenant task access denied (fail closed)\");\n }\n return rec;\n }\n\n transition(taskId: string, to: TaskStatus, tenantId: string): TaskRecord {\n const rec = this.#ownedOrThrow(taskId, tenantId);\n const allowed = LEGAL[rec.status];\n if (!allowed.includes(to)) {\n throw new Error(`illegal task transition ${rec.status} → ${to}`);\n }\n const next: TaskRecord = { ...rec, status: to };\n this.#tasks.set(taskId, next);\n return next;\n }\n\n /** Unified kill — drives any non-terminal task to `killed`. */\n stop(taskId: string, tenantId: string): TaskRecord {\n const rec = this.#ownedOrThrow(taskId, tenantId);\n if (TERMINAL.has(rec.status)) return rec;\n return this.transition(taskId, \"killed\", tenantId);\n }\n\n markNotified(taskId: string, tenantId: string): TaskRecord {\n const rec = this.#ownedOrThrow(taskId, tenantId);\n const next: TaskRecord = { ...rec, notified: true };\n this.#tasks.set(taskId, next);\n return next;\n }\n}\n\n/**\n * No-peek / no-race guard. Throws if a parent tries to read an in-flight\n * deferred task's transcript before it has settled.\n */\nexport function assertNotPeeking(task: TaskRecord): void {\n if (!TERMINAL.has(task.status)) {\n throw new Error(\n `cannot peek at in-flight task ${task.taskId} (status=${task.status}); await settlement`,\n );\n }\n}\n\n/* ─────────────── Typed terminal-envelope result contract ────────────────── */\n\nexport interface SyncEnvelope {\n readonly kind: \"sync\";\n readonly content: readonly ThreadItem[];\n readonly usage: TurnUsage;\n readonly agentId: string;\n readonly toolUses: number;\n readonly durationMs: number;\n}\n\nexport interface DeferredEnvelope {\n readonly kind: \"deferred\";\n readonly taskId: string;\n readonly channel: string;\n}\n\nexport type DispatchEnvelope = SyncEnvelope | DeferredEnvelope;\n\nconst NO_OUTPUT_MARKER: ThreadItem = {\n id: \"subagent_no_output\",\n type: \"error\",\n message: \"subagent completed but returned no output\",\n};\n\nconst threadItemShape = z.object({ id: z.string(), type: z.string() });\n\n/** Empty content is never silently empty — normalize to one explicit marker. */\nfunction normalizeContent(content: readonly ThreadItem[]): readonly ThreadItem[] {\n for (const item of content) threadItemShape.parse(item);\n return content.length === 0 ? [NO_OUTPUT_MARKER] : content;\n}\n\nfunction countToolUses(content: readonly ThreadItem[]): number {\n return content.filter((i) => i.type === \"mcp_tool_call\" || i.type === \"command_execution\").length;\n}\n\n/** Build a fire-and-forget deferred envelope for a registered task. */\nexport function makeDeferred(taskId: string, channel: string): DeferredEnvelope {\n if (!channel) throw new Error(\"deferred envelope requires a delivery channel\");\n return { kind: \"deferred\", taskId, channel };\n}\n\nexport interface SettleDeferredTarget {\n readonly taskId: string;\n readonly channel: string;\n readonly tenantId: string;\n readonly registry: TaskRegistry;\n}\n\n/**\n * Settle a previously-deferred task: drive it to `completed` (tenant-checked)\n * and produce the eventual sync-shaped result. Empty content is normalized to\n * the explicit no-output marker.\n */\nexport function settleDeferred(\n target: SettleDeferredTarget,\n content: readonly ThreadItem[],\n usage: TurnUsage,\n): SyncEnvelope {\n const { taskId, tenantId, registry } = target;\n const rec = registry.get(taskId, tenantId);\n if (!rec) throw new Error(\"cannot settle unknown or cross-tenant task (fail closed)\");\n if (!TERMINAL.has(rec.status)) {\n registry.transition(taskId, \"completed\", tenantId);\n }\n const normalized = normalizeContent(content);\n return {\n kind: \"sync\",\n content: normalized,\n usage,\n agentId: taskId,\n toolUses: countToolUses(normalized),\n durationMs: Math.max(0, Date.now() - rec.createdAtMs),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/model.ts
|
|
2
|
+
function mergeTurnConfig(base, overrides) {
|
|
3
|
+
return overrides ? { ...base, ...overrides } : { ...base };
|
|
4
|
+
}
|
|
5
|
+
function isTurnTerminal(event) {
|
|
6
|
+
return event.type === "turn.completed" || event.type === "turn.failed";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
mergeTurnConfig,
|
|
11
|
+
isTurnTerminal
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=chunk-6EGG2OZC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/model.ts"],"sourcesContent":["/**\n * Thread / Turn / Item model + event lifecycle (WRAP — capability #6).\n *\n * Faithful re-expression of the upstream agent-runtime item taxonomy and event\n * stream (generated TS types in the source SDK, themselves derived from the\n * Rust `exec_events` definitions). Renamed to neutral domain terms and made\n * tenant-scoped. No enforcement here — pure data + state machine.\n *\n * Hierarchy: Thread -> Turn -> Item.\n * Thread : a durable, tenant-scoped conversation (resumable by replay).\n * Turn : one user-input -> model-completion cycle.\n * Item : the atomic recorded unit inside a turn.\n */\n\n/** Lifecycle of a stateful item that runs over time. */\nexport type RunStatus = \"in_progress\" | \"completed\" | \"failed\";\n\n/** Terminal status for an applied patch (no `in_progress` terminal form). */\nexport type PatchApplyStatus = \"completed\" | \"failed\";\n\n/** Kind of a single file mutation within a patch. */\nexport type PatchChangeKind = \"add\" | \"delete\" | \"update\";\n\nexport interface FileUpdateChange {\n readonly path: string;\n readonly kind: PatchChangeKind;\n}\n\ninterface ItemBase {\n /** Stable identifier, unique within the owning thread. */\n readonly id: string;\n}\n\n/** Natural-language (or structured-JSON) response from the agent. */\nexport interface AgentMessageItem extends ItemBase {\n readonly type: \"agent_message\";\n readonly text: string;\n}\n\n/** The agent's reasoning summary. */\nexport interface ReasoningItem extends ItemBase {\n readonly type: \"reasoning\";\n readonly text: string;\n}\n\n/**\n * A command the agent asked to run. The runtime itself never executes it —\n * see {@link ./sandbox} for the external-sandbox delegation seam.\n */\nexport interface CommandExecutionItem extends ItemBase {\n readonly type: \"command_execution\";\n readonly command: string;\n /** Aggregated stdout+stderr captured by the external executor. */\n readonly aggregatedOutput: string;\n /** Present once the command exits. */\n readonly exitCode?: number;\n readonly status: RunStatus;\n}\n\n/** A patch produced by the agent. Emitted once it succeeds or fails. */\nexport interface FileChangeItem extends ItemBase {\n readonly type: \"file_change\";\n readonly changes: readonly FileUpdateChange[];\n readonly status: PatchApplyStatus;\n}\n\n/** A call to a tool exposed via an MCP server. */\nexport interface McpToolCallItem extends ItemBase {\n readonly type: \"mcp_tool_call\";\n readonly server: string;\n readonly tool: string;\n readonly arguments: unknown;\n readonly result?: { readonly content: unknown; readonly structuredContent?: unknown };\n readonly error?: { readonly message: string };\n readonly status: RunStatus;\n}\n\n/** A web-search request issued by the agent. */\nexport interface WebSearchItem extends ItemBase {\n readonly type: \"web_search\";\n readonly query: string;\n}\n\nexport interface TodoEntry {\n readonly text: string;\n readonly completed: boolean;\n}\n\n/** The agent's running to-do list for the turn. */\nexport interface TodoListItem extends ItemBase {\n readonly type: \"todo_list\";\n readonly items: readonly TodoEntry[];\n}\n\n/** A non-fatal error surfaced as an item (the turn may continue). */\nexport interface ErrorItem extends ItemBase {\n readonly type: \"error\";\n readonly message: string;\n}\n\n/** Canonical closed union of thread items. */\nexport type ThreadItem =\n | AgentMessageItem\n | ReasoningItem\n | CommandExecutionItem\n | FileChangeItem\n | McpToolCallItem\n | WebSearchItem\n | TodoListItem\n | ErrorItem;\n\nexport type ThreadItemType = ThreadItem[\"type\"];\n\n/** Token accounting for a completed turn. */\nexport interface TurnUsage {\n readonly inputTokens: number;\n readonly cachedInputTokens: number;\n readonly outputTokens: number;\n readonly reasoningOutputTokens: number;\n}\n\n/** Fatal error payload emitted by the stream. */\nexport interface ThreadError {\n readonly message: string;\n}\n\n/**\n * Event lifecycle observed by a client:\n * thread.started -> turn.started -> (item.started -> item.updated* ->\n * item.completed)* -> turn.completed | turn.failed, with a terminal `error`.\n */\nexport type ThreadEvent =\n | { readonly type: \"thread.started\"; readonly threadId: string }\n | { readonly type: \"turn.started\" }\n | { readonly type: \"turn.completed\"; readonly usage: TurnUsage }\n | { readonly type: \"turn.failed\"; readonly error: ThreadError }\n | { readonly type: \"item.started\"; readonly item: ThreadItem }\n | { readonly type: \"item.updated\"; readonly item: ThreadItem }\n | { readonly type: \"item.completed\"; readonly item: ThreadItem }\n | { readonly type: \"error\"; readonly message: string };\n\nexport type ThreadEventType = ThreadEvent[\"type\"];\n\n/**\n * Per-turn config snapshot. Immutable within a turn; rebindable between turns.\n * Tenant defaults are merged with per-request overrides via {@link mergeTurnConfig}.\n */\nexport interface TurnConfig {\n readonly model: string;\n readonly provider: string;\n /** Stringly-typed approval-policy tag — see {@link ./policy}. */\n readonly approvalPolicy: string;\n /** Stringly-typed capability-policy tag — see {@link ./policy}. */\n readonly capabilityPolicy: string;\n readonly reasoningEffort?: \"low\" | \"medium\" | \"high\";\n}\n\nexport type TurnConfigOverrides = Partial<TurnConfig>;\n\n/** Freeze a tenant-default config against per-turn overrides. */\nexport function mergeTurnConfig(base: TurnConfig, overrides?: TurnConfigOverrides): TurnConfig {\n return overrides ? { ...base, ...overrides } : { ...base };\n}\n\n/** Whether an event is a terminal turn outcome. */\nexport function isTurnTerminal(event: ThreadEvent): boolean {\n return event.type === \"turn.completed\" || event.type === \"turn.failed\";\n}\n"],"mappings":";AAgKO,SAAS,gBAAgB,MAAkB,WAA6C;AAC7F,SAAO,YAAY,EAAE,GAAG,MAAM,GAAG,UAAU,IAAI,EAAE,GAAG,KAAK;AAC3D;AAGO,SAAS,eAAe,OAA6B;AAC1D,SAAO,MAAM,SAAS,oBAAoB,MAAM,SAAS;AAC3D;","names":[]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/definitions.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var SOURCE_TIERS = [
|
|
4
|
+
"bundled",
|
|
5
|
+
"builtin",
|
|
6
|
+
"workspace",
|
|
7
|
+
"workflow",
|
|
8
|
+
"plugin",
|
|
9
|
+
"dynamic",
|
|
10
|
+
"policy"
|
|
11
|
+
];
|
|
12
|
+
var tierRank = new Map(SOURCE_TIERS.map((t, i) => [t, i]));
|
|
13
|
+
var frontmatterSchema = z.object({
|
|
14
|
+
name: z.string().min(1),
|
|
15
|
+
description: z.string().default(""),
|
|
16
|
+
/** Tool allowlist this definition self-declares. */
|
|
17
|
+
allowedTools: z.array(z.string()).default([]),
|
|
18
|
+
/** Tool denylist (subagents). */
|
|
19
|
+
disallowedTools: z.array(z.string()).default([]),
|
|
20
|
+
argumentHint: z.string().optional(),
|
|
21
|
+
argNames: z.array(z.string()).default([]),
|
|
22
|
+
whenToUse: z.string().optional(),
|
|
23
|
+
version: z.string().optional(),
|
|
24
|
+
/** "inherit" | a model alias. */
|
|
25
|
+
model: z.string().optional(),
|
|
26
|
+
effort: z.enum(["low", "medium", "high"]).optional(),
|
|
27
|
+
/** Model may auto-invoke this. */
|
|
28
|
+
modelInvocable: z.boolean().default(true),
|
|
29
|
+
/** A human may invoke this. */
|
|
30
|
+
userInvocable: z.boolean().default(true),
|
|
31
|
+
/** inline = expand into current turn; fork = isolated child. */
|
|
32
|
+
executionMode: z.enum(["inline", "fork"]).default("inline"),
|
|
33
|
+
agent: z.string().optional(),
|
|
34
|
+
/** Path-activation globs (surface only after a matching file is touched). */
|
|
35
|
+
paths: z.array(z.string()).default([])
|
|
36
|
+
});
|
|
37
|
+
var FRONTMATTER_FENCE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
38
|
+
var KEY_ALIASES = {
|
|
39
|
+
"allowed-tools": "allowedTools",
|
|
40
|
+
"disallowed-tools": "disallowedTools",
|
|
41
|
+
"argument-hint": "argumentHint",
|
|
42
|
+
arguments: "argNames",
|
|
43
|
+
when_to_use: "whenToUse",
|
|
44
|
+
"disable-model-invocation": "modelInvocable",
|
|
45
|
+
// inverted below
|
|
46
|
+
"user-invocable": "userInvocable",
|
|
47
|
+
context: "executionMode"
|
|
48
|
+
};
|
|
49
|
+
function coerceScalar(raw) {
|
|
50
|
+
const v = raw.trim();
|
|
51
|
+
if (v === "true") return true;
|
|
52
|
+
if (v === "false") return false;
|
|
53
|
+
if (v.startsWith("[") && v.endsWith("]")) {
|
|
54
|
+
return v.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
55
|
+
}
|
|
56
|
+
return v.replace(/^["']|["']$/g, "");
|
|
57
|
+
}
|
|
58
|
+
function parseFrontmatter(raw) {
|
|
59
|
+
const m = FRONTMATTER_FENCE.exec(raw);
|
|
60
|
+
if (!m) throw new Error("definition is missing a '--- \u2026 ---' frontmatter block");
|
|
61
|
+
const block = m[1] ?? "";
|
|
62
|
+
const body = raw.slice(m[0].length);
|
|
63
|
+
const bag = {};
|
|
64
|
+
for (const line of block.split(/\r?\n/)) {
|
|
65
|
+
if (!line.trim() || /^\s*#/.test(line)) continue;
|
|
66
|
+
const idx = line.indexOf(":");
|
|
67
|
+
if (idx < 0) continue;
|
|
68
|
+
const rawKey = line.slice(0, idx).trim();
|
|
69
|
+
const value = coerceScalar(line.slice(idx + 1));
|
|
70
|
+
const key = KEY_ALIASES[rawKey] ?? rawKey;
|
|
71
|
+
if (rawKey === "disable-model-invocation") {
|
|
72
|
+
bag.modelInvocable = value === true ? false : value === false ? true : true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (rawKey === "context") {
|
|
76
|
+
bag.executionMode = value === "fork" ? "fork" : "inline";
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
bag[key] = value;
|
|
80
|
+
}
|
|
81
|
+
return { frontmatter: frontmatterSchema.parse(bag), body };
|
|
82
|
+
}
|
|
83
|
+
var DefinitionResolver = class _DefinitionResolver {
|
|
84
|
+
#all;
|
|
85
|
+
constructor(definitions) {
|
|
86
|
+
this.#all = [...definitions];
|
|
87
|
+
}
|
|
88
|
+
/** Entitlement gate. */
|
|
89
|
+
static available(def, plan) {
|
|
90
|
+
if (!def.availabilityPlans || def.availabilityPlans.length === 0) return true;
|
|
91
|
+
return plan != null && def.availabilityPlans.includes(plan);
|
|
92
|
+
}
|
|
93
|
+
resolve(ctx) {
|
|
94
|
+
if (!ctx.tenantId) throw new Error("ResolveContext.tenantId is required");
|
|
95
|
+
const winner = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const def of this.#all) {
|
|
97
|
+
if (def.tenantId !== ctx.tenantId) continue;
|
|
98
|
+
if (def.enabled === false) continue;
|
|
99
|
+
if (!_DefinitionResolver.available(def, ctx.plan)) continue;
|
|
100
|
+
const cur = winner.get(def.slug);
|
|
101
|
+
if (!cur || tierRank.get(def.sourceTier) >= tierRank.get(cur.sourceTier)) {
|
|
102
|
+
winner.set(def.slug, def);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return [...winner.values()];
|
|
106
|
+
}
|
|
107
|
+
/** Resolve a single slug (highest-precedence, gated). */
|
|
108
|
+
resolveOne(slug, ctx) {
|
|
109
|
+
return this.resolve(ctx).find((d) => d.slug === slug);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
function substituteArguments(template, args, variables = {}) {
|
|
113
|
+
return template.replace(
|
|
114
|
+
/\$\{(\w+)\}/g,
|
|
115
|
+
(_, k) => k in args ? args[k] : k in variables ? variables[k] : ""
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
SOURCE_TIERS,
|
|
121
|
+
frontmatterSchema,
|
|
122
|
+
parseFrontmatter,
|
|
123
|
+
DefinitionResolver,
|
|
124
|
+
substituteArguments
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-7BUOF367.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/definitions.ts"],"sourcesContent":["/**\n * Shared definition kernel (WRAP — Claude-Code-class harness, the delta).\n *\n * Faithful re-expression of the source harness's layered-merge + precedence +\n * dual-gate resolver and its frontmatter→record parser. One implementation\n * backs the command registry, the skill catalog, and the subagent-definition\n * registry (the source uses the same loader for all three).\n *\n * Multi-tenant: every resolution is keyed by `tenantId`; tiers from one tenant\n * never merge into another. Pure data/logic — no FS scan, no shell, no TUI.\n */\n\nimport { z } from \"zod\";\n\n/**\n * Source-tier precedence, low → high authority. A later tier overrides an\n * earlier one on slug collision (mirrors the upstream merge order:\n * bundled → builtin → workspace → workflow → plugin → dynamic, with\n * org policy as the final, non-overridable authority).\n */\nexport const SOURCE_TIERS = [\n \"bundled\",\n \"builtin\",\n \"workspace\",\n \"workflow\",\n \"plugin\",\n \"dynamic\",\n \"policy\",\n] as const;\nexport type SourceTier = (typeof SOURCE_TIERS)[number];\n\nconst tierRank = new Map<SourceTier, number>(SOURCE_TIERS.map((t, i) => [t, i]));\n\n/** Frontmatter shared by skills / commands / subagents (faithful subset). */\nexport const frontmatterSchema = z.object({\n name: z.string().min(1),\n description: z.string().default(\"\"),\n /** Tool allowlist this definition self-declares. */\n allowedTools: z.array(z.string()).default([]),\n /** Tool denylist (subagents). */\n disallowedTools: z.array(z.string()).default([]),\n argumentHint: z.string().optional(),\n argNames: z.array(z.string()).default([]),\n whenToUse: z.string().optional(),\n version: z.string().optional(),\n /** \"inherit\" | a model alias. */\n model: z.string().optional(),\n effort: z.enum([\"low\", \"medium\", \"high\"]).optional(),\n /** Model may auto-invoke this. */\n modelInvocable: z.boolean().default(true),\n /** A human may invoke this. */\n userInvocable: z.boolean().default(true),\n /** inline = expand into current turn; fork = isolated child. */\n executionMode: z.enum([\"inline\", \"fork\"]).default(\"inline\"),\n agent: z.string().optional(),\n /** Path-activation globs (surface only after a matching file is touched). */\n paths: z.array(z.string()).default([]),\n});\nexport type Frontmatter = z.infer<typeof frontmatterSchema>;\n\nconst FRONTMATTER_FENCE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/;\n\n/** Aliases mapping kebab/snake frontmatter keys → schema keys. */\nconst KEY_ALIASES: Record<string, keyof Frontmatter> = {\n \"allowed-tools\": \"allowedTools\",\n \"disallowed-tools\": \"disallowedTools\",\n \"argument-hint\": \"argumentHint\",\n arguments: \"argNames\",\n when_to_use: \"whenToUse\",\n \"disable-model-invocation\": \"modelInvocable\", // inverted below\n \"user-invocable\": \"userInvocable\",\n context: \"executionMode\",\n};\n\nfunction coerceScalar(raw: string): string | boolean | string[] {\n const v = raw.trim();\n if (v === \"true\") return true;\n if (v === \"false\") return false;\n if (v.startsWith(\"[\") && v.endsWith(\"]\")) {\n return v\n .slice(1, -1)\n .split(\",\")\n .map((s) => s.trim().replace(/^[\"']|[\"']$/g, \"\"))\n .filter(Boolean);\n }\n return v.replace(/^[\"']|[\"']$/g, \"\");\n}\n\n/**\n * Parse a `--- … ---` frontmatter block + remaining body. Dependency-light\n * line parser (the package only depends on zod) — sufficient for the simple\n * key: value / list frontmatter the source uses. Throws on missing block.\n */\nexport function parseFrontmatter(raw: string): { frontmatter: Frontmatter; body: string } {\n const m = FRONTMATTER_FENCE.exec(raw);\n if (!m) throw new Error(\"definition is missing a '--- … ---' frontmatter block\");\n const block = m[1] ?? \"\";\n const body = raw.slice(m[0].length);\n\n const bag: Record<string, unknown> = {};\n for (const line of block.split(/\\r?\\n/)) {\n if (!line.trim() || /^\\s*#/.test(line)) continue;\n const idx = line.indexOf(\":\");\n if (idx < 0) continue;\n const rawKey = line.slice(0, idx).trim();\n const value = coerceScalar(line.slice(idx + 1));\n const key = KEY_ALIASES[rawKey] ?? (rawKey as keyof Frontmatter);\n if (rawKey === \"disable-model-invocation\") {\n bag.modelInvocable = value === true ? false : value === false ? true : true;\n continue;\n }\n if (rawKey === \"context\") {\n bag.executionMode = value === \"fork\" ? \"fork\" : \"inline\";\n continue;\n }\n bag[key] = value;\n }\n return { frontmatter: frontmatterSchema.parse(bag), body };\n}\n\n/** A resolvable definition of any kind (skill / command / subagent). */\nexport interface Definition {\n readonly slug: string;\n readonly tenantId: string;\n readonly sourceTier: SourceTier;\n readonly frontmatter: Frontmatter;\n readonly bodyRef: string;\n /** Entitlement gate — who may have this at all. */\n readonly availabilityPlans?: readonly string[];\n /** Runtime/feature toggle, recomputed per request. */\n readonly enabled?: boolean;\n}\n\nexport interface ResolveContext {\n readonly tenantId: string;\n readonly plan?: string;\n /** Names already loaded this session (suppressed from re-listing). */\n readonly loadedSlugs?: ReadonlySet<string>;\n}\n\n/**\n * Tenant-scoped layered resolver. Merges definitions by slug with tier\n * precedence and applies the dual gate (availability ∧ enabled), recomputed\n * per call so entitlement/flag changes apply live. Cross-tenant inputs are\n * rejected — fail closed.\n */\nexport class DefinitionResolver<T extends Definition = Definition> {\n readonly #all: readonly T[];\n\n constructor(definitions: readonly T[]) {\n this.#all = [...definitions];\n }\n\n /** Entitlement gate. */\n static available(def: Definition, plan?: string): boolean {\n if (!def.availabilityPlans || def.availabilityPlans.length === 0) return true;\n return plan != null && def.availabilityPlans.includes(plan);\n }\n\n resolve(ctx: ResolveContext): T[] {\n if (!ctx.tenantId) throw new Error(\"ResolveContext.tenantId is required\");\n const winner = new Map<string, T>();\n for (const def of this.#all) {\n if (def.tenantId !== ctx.tenantId) continue; // tenant isolation, fail-closed\n if (def.enabled === false) continue;\n if (!DefinitionResolver.available(def, ctx.plan)) continue;\n const cur = winner.get(def.slug);\n if (!cur || tierRank.get(def.sourceTier)! >= tierRank.get(cur.sourceTier)!) {\n winner.set(def.slug, def);\n }\n }\n return [...winner.values()];\n }\n\n /** Resolve a single slug (highest-precedence, gated). */\n resolveOne(slug: string, ctx: ResolveContext): T | undefined {\n return this.resolve(ctx).find((d) => d.slug === slug);\n }\n}\n\n/** Substitute `${name}` / positional `$1` args + session/dir variables. */\nexport function substituteArguments(\n template: string,\n args: Record<string, string>,\n variables: Record<string, string> = {},\n): string {\n return template.replace(/\\$\\{(\\w+)\\}/g, (_, k: string) =>\n k in args ? args[k]! : k in variables ? variables[k]! : \"\",\n );\n}\n"],"mappings":"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","names":[]}
|