@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.
Files changed (179) hide show
  1. package/.turbo/turbo-build.log +115 -0
  2. package/.turbo/turbo-test.log +44 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/CHANGELOG.md +253 -0
  5. package/LICENSE +676 -0
  6. package/README.md +50 -0
  7. package/dist/adapters/dispatcher-sse.d.ts +68 -0
  8. package/dist/adapters/dispatcher-sse.js +11 -0
  9. package/dist/adapters/dispatcher-sse.js.map +1 -0
  10. package/dist/adapters/index.d.ts +12 -0
  11. package/dist/adapters/index.js +21 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/mcp-catalog.d.ts +58 -0
  14. package/dist/adapters/mcp-catalog.js +9 -0
  15. package/dist/adapters/mcp-catalog.js.map +1 -0
  16. package/dist/adapters/prisma-rollout.d.ts +60 -0
  17. package/dist/adapters/prisma-rollout.js +7 -0
  18. package/dist/adapters/prisma-rollout.js.map +1 -0
  19. package/dist/chunk-24ZXP7FI.js +93 -0
  20. package/dist/chunk-24ZXP7FI.js.map +1 -0
  21. package/dist/chunk-2DA6Q6TN.js +126 -0
  22. package/dist/chunk-2DA6Q6TN.js.map +1 -0
  23. package/dist/chunk-37BBB2P2.js +73 -0
  24. package/dist/chunk-37BBB2P2.js.map +1 -0
  25. package/dist/chunk-57W3AR43.js +52 -0
  26. package/dist/chunk-57W3AR43.js.map +1 -0
  27. package/dist/chunk-5N4644PB.js +67 -0
  28. package/dist/chunk-5N4644PB.js.map +1 -0
  29. package/dist/chunk-5YS7WAPS.js +177 -0
  30. package/dist/chunk-5YS7WAPS.js.map +1 -0
  31. package/dist/chunk-6EGG2OZC.js +13 -0
  32. package/dist/chunk-6EGG2OZC.js.map +1 -0
  33. package/dist/chunk-7BUOF367.js +126 -0
  34. package/dist/chunk-7BUOF367.js.map +1 -0
  35. package/dist/chunk-BJBBR3QA.js +121 -0
  36. package/dist/chunk-BJBBR3QA.js.map +1 -0
  37. package/dist/chunk-CGRCUKGT.js +73 -0
  38. package/dist/chunk-CGRCUKGT.js.map +1 -0
  39. package/dist/chunk-FUG5DT2C.js +75 -0
  40. package/dist/chunk-FUG5DT2C.js.map +1 -0
  41. package/dist/chunk-LO24VOA3.js +199 -0
  42. package/dist/chunk-LO24VOA3.js.map +1 -0
  43. package/dist/chunk-MUF7ZZTO.js +57 -0
  44. package/dist/chunk-MUF7ZZTO.js.map +1 -0
  45. package/dist/chunk-NN7DATXA.js +46 -0
  46. package/dist/chunk-NN7DATXA.js.map +1 -0
  47. package/dist/chunk-PGGWSUTM.js +33 -0
  48. package/dist/chunk-PGGWSUTM.js.map +1 -0
  49. package/dist/chunk-RDKYDMXT.js +135 -0
  50. package/dist/chunk-RDKYDMXT.js.map +1 -0
  51. package/dist/chunk-YYFPDBJG.js +63 -0
  52. package/dist/chunk-YYFPDBJG.js.map +1 -0
  53. package/dist/chunk-ZMYX5VBU.js +135 -0
  54. package/dist/chunk-ZMYX5VBU.js.map +1 -0
  55. package/dist/chunk-ZTSKS42I.js +131 -0
  56. package/dist/chunk-ZTSKS42I.js.map +1 -0
  57. package/dist/commands.d.ts +74 -0
  58. package/dist/commands.js +10 -0
  59. package/dist/commands.js.map +1 -0
  60. package/dist/definitions.d.ts +94 -0
  61. package/dist/definitions.js +15 -0
  62. package/dist/definitions.js.map +1 -0
  63. package/dist/dispatcher.d.ts +50 -0
  64. package/dist/dispatcher.js +8 -0
  65. package/dist/dispatcher.js.map +1 -0
  66. package/dist/durable-turn.d.ts +58 -0
  67. package/dist/durable-turn.js +9 -0
  68. package/dist/durable-turn.js.map +1 -0
  69. package/dist/hook-pipeline.d.ts +114 -0
  70. package/dist/hook-pipeline.js +13 -0
  71. package/dist/hook-pipeline.js.map +1 -0
  72. package/dist/index.d.ts +1874 -0
  73. package/dist/index.js +3117 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/loop.d.ts +78 -0
  76. package/dist/loop.js +9 -0
  77. package/dist/loop.js.map +1 -0
  78. package/dist/mcp-bridge.d.ts +48 -0
  79. package/dist/mcp-bridge.js +8 -0
  80. package/dist/mcp-bridge.js.map +1 -0
  81. package/dist/model.d.ts +154 -0
  82. package/dist/model.js +9 -0
  83. package/dist/model.js.map +1 -0
  84. package/dist/policy.d.ts +130 -0
  85. package/dist/policy.js +23 -0
  86. package/dist/policy.js.map +1 -0
  87. package/dist/protocol.d.ts +170 -0
  88. package/dist/protocol.js +15 -0
  89. package/dist/protocol.js.map +1 -0
  90. package/dist/rollout-store-persistent.d.ts +48 -0
  91. package/dist/rollout-store-persistent.js +9 -0
  92. package/dist/rollout-store-persistent.js.map +1 -0
  93. package/dist/rollout.d.ts +82 -0
  94. package/dist/rollout.js +15 -0
  95. package/dist/rollout.js.map +1 -0
  96. package/dist/sandbox.d.ts +65 -0
  97. package/dist/sandbox.js +15 -0
  98. package/dist/sandbox.js.map +1 -0
  99. package/dist/skills.d.ts +93 -0
  100. package/dist/skills.js +10 -0
  101. package/dist/skills.js.map +1 -0
  102. package/dist/subagents.d.ts +129 -0
  103. package/dist/subagents.js +21 -0
  104. package/dist/subagents.js.map +1 -0
  105. package/dist/tools.d.ts +77 -0
  106. package/dist/tools.js +9 -0
  107. package/dist/tools.js.map +1 -0
  108. package/package.json +74 -0
  109. package/src/adapters/dispatcher-sse.test.ts +218 -0
  110. package/src/adapters/dispatcher-sse.ts +222 -0
  111. package/src/adapters/index.ts +18 -0
  112. package/src/adapters/mcp-catalog.test.ts +213 -0
  113. package/src/adapters/mcp-catalog.ts +188 -0
  114. package/src/adapters/prisma-rollout.test.ts +153 -0
  115. package/src/adapters/prisma-rollout.ts +104 -0
  116. package/src/agent-runtime.test.ts +176 -0
  117. package/src/artifact-stream.test.ts +330 -0
  118. package/src/artifact-stream.ts +453 -0
  119. package/src/channel-gateway.test.ts +432 -0
  120. package/src/channel-gateway.ts +357 -0
  121. package/src/code-review.test.ts +501 -0
  122. package/src/code-review.ts +495 -0
  123. package/src/command-suggestions.test.ts +251 -0
  124. package/src/command-suggestions.ts +338 -0
  125. package/src/commands.test.ts +184 -0
  126. package/src/commands.ts +140 -0
  127. package/src/commit-message.test.ts +249 -0
  128. package/src/commit-message.ts +180 -0
  129. package/src/context-compaction.test.ts +522 -0
  130. package/src/context-compaction.ts +434 -0
  131. package/src/definitions.test.ts +78 -0
  132. package/src/definitions.ts +190 -0
  133. package/src/deployment-status.test.ts +215 -0
  134. package/src/deployment-status.ts +227 -0
  135. package/src/design-context.test.ts +195 -0
  136. package/src/design-context.ts +198 -0
  137. package/src/dispatcher.test.ts +234 -0
  138. package/src/dispatcher.ts +189 -0
  139. package/src/durable-turn.test.ts +209 -0
  140. package/src/durable-turn.ts +135 -0
  141. package/src/edit-planner.test.ts +204 -0
  142. package/src/edit-planner.ts +325 -0
  143. package/src/fuzzy-match.test.ts +311 -0
  144. package/src/fuzzy-match.ts +444 -0
  145. package/src/hook-pipeline.test.ts +279 -0
  146. package/src/hook-pipeline.ts +373 -0
  147. package/src/inbound-admission.test.ts +394 -0
  148. package/src/inbound-admission.ts +246 -0
  149. package/src/index.ts +47 -0
  150. package/src/loop.test.ts +161 -0
  151. package/src/loop.ts +211 -0
  152. package/src/mcp-bridge.test.ts +165 -0
  153. package/src/mcp-bridge.ts +76 -0
  154. package/src/memory-provider.test.ts +232 -0
  155. package/src/memory-provider.ts +257 -0
  156. package/src/model.ts +168 -0
  157. package/src/permission-ruleset.test.ts +301 -0
  158. package/src/permission-ruleset.ts +200 -0
  159. package/src/policy.ts +151 -0
  160. package/src/project-repo.test.ts +232 -0
  161. package/src/project-repo.ts +311 -0
  162. package/src/protocol.ts +159 -0
  163. package/src/rollout-store-persistent.test.ts +217 -0
  164. package/src/rollout-store-persistent.ts +166 -0
  165. package/src/rollout.ts +150 -0
  166. package/src/sandbox.ts +113 -0
  167. package/src/session-share.test.ts +360 -0
  168. package/src/session-share.ts +310 -0
  169. package/src/skill-distillation.test.ts +177 -0
  170. package/src/skill-distillation.ts +369 -0
  171. package/src/skills.test.ts +277 -0
  172. package/src/skills.ts +255 -0
  173. package/src/subagents.test.ts +290 -0
  174. package/src/subagents.ts +332 -0
  175. package/src/tools.ts +126 -0
  176. package/src/workbench.test.ts +0 -0
  177. package/src/workbench.ts +0 -0
  178. package/tsconfig.json +12 -0
  179. package/tsup.config.ts +33 -0
@@ -0,0 +1,46 @@
1
+ // src/tools.ts
2
+ var ToolRegistry = class {
3
+ #tools = /* @__PURE__ */ new Map();
4
+ #hooks;
5
+ constructor(hooks = {}) {
6
+ this.#hooks = hooks;
7
+ }
8
+ register(definition, handler, origin = { kind: "native" }) {
9
+ if (this.#tools.has(definition.name)) {
10
+ throw new Error(`Tool already registered: ${definition.name}`);
11
+ }
12
+ this.#tools.set(definition.name, {
13
+ reg: { definition, origin },
14
+ handler
15
+ });
16
+ }
17
+ list() {
18
+ return [...this.#tools.values()].map((e) => e.reg);
19
+ }
20
+ async dispatch(name, rawInput, ctx) {
21
+ const entry = this.#tools.get(name);
22
+ if (!entry) throw new Error(`Unknown tool: ${name}`);
23
+ const input = entry.reg.definition.inputSchema.parse(rawInput);
24
+ await this.#hooks.preToolUse?.(name, input, ctx);
25
+ const output = await entry.handler(input, ctx);
26
+ const validated = entry.reg.definition.outputSchema ? entry.reg.definition.outputSchema.parse(output) : output;
27
+ await this.#hooks.postToolUse?.(name, validated, ctx);
28
+ return validated;
29
+ }
30
+ };
31
+ function adaptMcpTool(server, definition, client) {
32
+ return {
33
+ definition,
34
+ origin: { kind: "mcp", server },
35
+ handler: async (input, ctx) => client.executeTool(definition.name, input, {
36
+ requestId: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}`,
37
+ tenantId: ctx.tenantId
38
+ })
39
+ };
40
+ }
41
+
42
+ export {
43
+ ToolRegistry,
44
+ adaptMcpTool
45
+ };
46
+ //# sourceMappingURL=chunk-NN7DATXA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools.ts"],"sourcesContent":["/**\n * Tool / MCP abstraction (WRAP — capability #9).\n *\n * Faithful re-expression of the upstream uniform tool model: one\n * `ToolDefinition` interface, JSON-schema in/out, a deferred-load flag, and\n * MCP-as-adapter behind the same interface (MCP tools and native tools are\n * indistinguishable at dispatch). Activates `@nebutra/mcp` primitives rather\n * than re-porting them.\n */\n\nimport type { z } from \"zod\";\n\n/** A tool definition — pure data; transport-agnostic. */\nexport interface ToolDefinition<I = unknown, O = unknown> {\n readonly name: string;\n readonly description: string;\n readonly inputSchema: z.ZodType<I>;\n readonly outputSchema?: z.ZodType<O>;\n /**\n * When true, the full schema is withheld until a `tool_search` discovery\n * step lazily loads it (parity with the upstream deferred-tool pattern).\n */\n readonly deferLoading?: boolean;\n}\n\n/** Source provenance — native vs. adapted from an MCP server. */\nexport type ToolOrigin =\n | { readonly kind: \"native\" }\n | { readonly kind: \"mcp\"; readonly server: string };\n\nexport interface RegisteredTool {\n readonly definition: ToolDefinition;\n readonly origin: ToolOrigin;\n}\n\n/** A tool invocation routed through the registry. */\nexport interface ToolDispatchContext {\n /** Mandatory tenant scope — every dispatch is tenant-bound. */\n readonly tenantId: string;\n readonly threadId: string;\n}\n\nexport type ToolHandler<I = unknown, O = unknown> = (\n input: I,\n ctx: ToolDispatchContext,\n) => Promise<O>;\n\n/** Pre/post dispatch hooks (parity with upstream orchestrator hooks). */\nexport interface ToolHooks {\n readonly preToolUse?: (name: string, input: unknown, ctx: ToolDispatchContext) => Promise<void>;\n readonly postToolUse?: (name: string, output: unknown, ctx: ToolDispatchContext) => Promise<void>;\n}\n\n/**\n * registry -> router -> orchestrator pipeline. MCP tools register via\n * {@link adaptMcpTool} and are dispatched identically to native tools.\n */\nexport class ToolRegistry {\n readonly #tools = new Map<string, { reg: RegisteredTool; handler: ToolHandler }>();\n readonly #hooks: ToolHooks;\n\n constructor(hooks: ToolHooks = {}) {\n this.#hooks = hooks;\n }\n\n register<I, O>(\n definition: ToolDefinition<I, O>,\n handler: ToolHandler<I, O>,\n origin: ToolOrigin = { kind: \"native\" },\n ): void {\n if (this.#tools.has(definition.name)) {\n throw new Error(`Tool already registered: ${definition.name}`);\n }\n this.#tools.set(definition.name, {\n reg: { definition: definition as ToolDefinition, origin },\n handler: handler as ToolHandler,\n });\n }\n\n list(): readonly RegisteredTool[] {\n return [...this.#tools.values()].map((e) => e.reg);\n }\n\n async dispatch(name: string, rawInput: unknown, ctx: ToolDispatchContext): Promise<unknown> {\n const entry = this.#tools.get(name);\n if (!entry) throw new Error(`Unknown tool: ${name}`);\n const input = entry.reg.definition.inputSchema.parse(rawInput);\n await this.#hooks.preToolUse?.(name, input, ctx);\n const output = await entry.handler(input, ctx);\n const validated = entry.reg.definition.outputSchema\n ? entry.reg.definition.outputSchema.parse(output)\n : output;\n await this.#hooks.postToolUse?.(name, validated, ctx);\n return validated;\n }\n}\n\n/** Minimal shape of an MCP client (satisfied by `@nebutra/mcp`'s `mcpClient`). */\nexport interface McpClientLike {\n executeTool(\n name: string,\n args: unknown,\n ctx: { requestId: string; tenantId: string },\n ): Promise<unknown>;\n}\n\n/**\n * Adapt an external MCP tool into the uniform tool model. The resulting\n * handler delegates to the MCP client; callers cannot tell it apart from a\n * native tool.\n */\nexport function adaptMcpTool(\n server: string,\n definition: ToolDefinition,\n client: McpClientLike,\n): { definition: ToolDefinition; handler: ToolHandler; origin: ToolOrigin } {\n return {\n definition,\n origin: { kind: \"mcp\", server },\n handler: async (input, ctx) =>\n client.executeTool(definition.name, input, {\n requestId: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}`,\n tenantId: ctx.tenantId,\n }),\n };\n}\n"],"mappings":";AAyDO,IAAM,eAAN,MAAmB;AAAA,EACf,SAAS,oBAAI,IAA2D;AAAA,EACxE;AAAA,EAET,YAAY,QAAmB,CAAC,GAAG;AACjC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,SACE,YACA,SACA,SAAqB,EAAE,MAAM,SAAS,GAChC;AACN,QAAI,KAAK,OAAO,IAAI,WAAW,IAAI,GAAG;AACpC,YAAM,IAAI,MAAM,4BAA4B,WAAW,IAAI,EAAE;AAAA,IAC/D;AACA,SAAK,OAAO,IAAI,WAAW,MAAM;AAAA,MAC/B,KAAK,EAAE,YAA0C,OAAO;AAAA,MACxD;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAkC;AAChC,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,EACnD;AAAA,EAEA,MAAM,SAAS,MAAc,UAAmB,KAA4C;AAC1F,UAAM,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClC,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iBAAiB,IAAI,EAAE;AACnD,UAAM,QAAQ,MAAM,IAAI,WAAW,YAAY,MAAM,QAAQ;AAC7D,UAAM,KAAK,OAAO,aAAa,MAAM,OAAO,GAAG;AAC/C,UAAM,SAAS,MAAM,MAAM,QAAQ,OAAO,GAAG;AAC7C,UAAM,YAAY,MAAM,IAAI,WAAW,eACnC,MAAM,IAAI,WAAW,aAAa,MAAM,MAAM,IAC9C;AACJ,UAAM,KAAK,OAAO,cAAc,MAAM,WAAW,GAAG;AACpD,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,aACd,QACA,YACA,QAC0E;AAC1E,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,EAAE,MAAM,OAAO,OAAO;AAAA,IAC9B,SAAS,OAAO,OAAO,QACrB,OAAO,YAAY,WAAW,MAAM,OAAO;AAAA,MACzC,WAAW,WAAW,QAAQ,aAAa,KAAK,GAAG,KAAK,IAAI,CAAC;AAAA,MAC7D,UAAU,IAAI;AAAA,IAChB,CAAC;AAAA,EACL;AACF;","names":[]}
@@ -0,0 +1,33 @@
1
+ import {
2
+ adaptMcpTool
3
+ } from "./chunk-NN7DATXA.js";
4
+
5
+ // src/mcp-bridge.ts
6
+ import { z } from "zod";
7
+ var ctxSchema = z.object({
8
+ tenantId: z.string().trim().min(1, "tenantId is required (fail-closed)"),
9
+ plan: z.string().min(1).optional()
10
+ });
11
+ async function activateMcpTools(registry, catalog, client, ctx) {
12
+ const scope = ctxSchema.parse(ctx);
13
+ const entries = await catalog.listTools(
14
+ scope.plan === void 0 ? { tenantId: scope.tenantId } : { tenantId: scope.tenantId, plan: scope.plan }
15
+ );
16
+ const registered = [];
17
+ const skipped = [];
18
+ for (const { server, definition } of entries) {
19
+ if (registry.list().some((r) => r.definition.name === definition.name)) {
20
+ skipped.push(definition.name);
21
+ continue;
22
+ }
23
+ const adapted = adaptMcpTool(server, definition, client);
24
+ registry.register(adapted.definition, adapted.handler, adapted.origin);
25
+ registered.push(definition.name);
26
+ }
27
+ return { registered, skipped };
28
+ }
29
+
30
+ export {
31
+ activateMcpTools
32
+ };
33
+ //# sourceMappingURL=chunk-PGGWSUTM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp-bridge.ts"],"sourcesContent":["/**\n * MCP activation bridge (WRAP — capability #9, activation seam).\n *\n * Concrete bridge that registers external MCP-server tools into the runtime's\n * uniform tool model. It does NOT import `@nebutra/mcp` (WIP / do-not-import):\n * instead it defines minimal injectable ports so callers wire `@nebutra/mcp`\n * (`serverRegistry` + `mcpClient`) without this package taking a hard dep.\n *\n * Tenant-scoped by construction: the catalog port is always queried with the\n * caller's `{ tenantId, plan }`, so a tenant only ever sees its own MCP\n * servers/tools. Fail-closed on missing tenant.\n */\n\nimport { z } from \"zod\";\n\nimport { adaptMcpTool, type McpClientLike, type ToolDefinition, type ToolRegistry } from \"./tools\";\n\n/**\n * Port over an MCP server catalog (satisfied by `@nebutra/mcp`'s\n * `serverRegistry` + plan middleware). Returns only the tools visible to the\n * given tenant/plan — visibility/plan-gating is the port's responsibility.\n */\nexport interface McpServerCatalogPort {\n listTools(ctx: {\n tenantId: string;\n plan?: string;\n }): Promise<{ server: string; definition: ToolDefinition }[]>;\n}\n\nconst ctxSchema = z.object({\n tenantId: z.string().trim().min(1, \"tenantId is required (fail-closed)\"),\n plan: z.string().min(1).optional(),\n});\n\nexport interface ActivateMcpToolsResult {\n /** Tool names newly registered into the registry, in catalog order. */\n readonly registered: readonly string[];\n /** Tool names skipped because the name was already registered. */\n readonly skipped: readonly string[];\n}\n\n/**\n * List the tenant/plan-visible MCP tools, adapt each via {@link adaptMcpTool},\n * and register them into the {@link ToolRegistry}. A tool whose name is already\n * registered is skipped (reported, never thrown). Empty/blank tenantId fails\n * closed before any catalog call.\n */\nexport async function activateMcpTools(\n registry: ToolRegistry,\n catalog: McpServerCatalogPort,\n client: McpClientLike,\n ctx: { tenantId: string; plan?: string },\n): Promise<ActivateMcpToolsResult> {\n const scope = ctxSchema.parse(ctx);\n\n const entries = await catalog.listTools(\n scope.plan === undefined\n ? { tenantId: scope.tenantId }\n : { tenantId: scope.tenantId, plan: scope.plan },\n );\n\n const registered: string[] = [];\n const skipped: string[] = [];\n\n for (const { server, definition } of entries) {\n if (registry.list().some((r) => r.definition.name === definition.name)) {\n skipped.push(definition.name);\n continue;\n }\n const adapted = adaptMcpTool(server, definition, client);\n registry.register(adapted.definition, adapted.handler, adapted.origin);\n registered.push(definition.name);\n }\n\n return { registered, skipped };\n}\n"],"mappings":";;;;;AAaA,SAAS,SAAS;AAgBlB,IAAM,YAAY,EAAE,OAAO;AAAA,EACzB,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,oCAAoC;AAAA,EACvE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AACnC,CAAC;AAeD,eAAsB,iBACpB,UACA,SACA,QACA,KACiC;AACjC,QAAM,QAAQ,UAAU,MAAM,GAAG;AAEjC,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,MAAM,SAAS,SACX,EAAE,UAAU,MAAM,SAAS,IAC3B,EAAE,UAAU,MAAM,UAAU,MAAM,MAAM,KAAK;AAAA,EACnD;AAEA,QAAM,aAAuB,CAAC;AAC9B,QAAM,UAAoB,CAAC;AAE3B,aAAW,EAAE,QAAQ,WAAW,KAAK,SAAS;AAC5C,QAAI,SAAS,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,WAAW,IAAI,GAAG;AACtE,cAAQ,KAAK,WAAW,IAAI;AAC5B;AAAA,IACF;AACA,UAAM,UAAU,aAAa,QAAQ,YAAY,MAAM;AACvD,aAAS,SAAS,QAAQ,YAAY,QAAQ,SAAS,QAAQ,MAAM;AACrE,eAAW,KAAK,WAAW,IAAI;AAAA,EACjC;AAEA,SAAO,EAAE,YAAY,QAAQ;AAC/B;","names":[]}
@@ -0,0 +1,135 @@
1
+ import {
2
+ METHOD_REGISTRY,
3
+ NOTIFICATIONS,
4
+ requestEnvelopeSchema,
5
+ resolveScope,
6
+ scopeKey
7
+ } from "./chunk-FUG5DT2C.js";
8
+
9
+ // src/dispatcher.ts
10
+ var ERR_INVALID_REQUEST = -32600;
11
+ var ERR_METHOD_NOT_FOUND = -32601;
12
+ var ERR_INVALID_PARAMS = -32602;
13
+ var ERR_INTERNAL = -32603;
14
+ function success(id, result) {
15
+ return { jsonrpc: "2.0", id, result };
16
+ }
17
+ function failure(id, code, message) {
18
+ return { jsonrpc: "2.0", id, error: { code, message } };
19
+ }
20
+ var NOTIFICATION_NAMES = new Set(NOTIFICATIONS);
21
+ var METHOD_SPEC_BY_WIRE = new Map(
22
+ Object.values(METHOD_REGISTRY).map((spec) => [spec.method, spec])
23
+ );
24
+ function extractId(raw) {
25
+ if (raw && typeof raw === "object" && "id" in raw) {
26
+ const id = raw.id;
27
+ if (typeof id === "string" || typeof id === "number") return id;
28
+ }
29
+ return null;
30
+ }
31
+ var ProtocolDispatcher = class {
32
+ #handlers = /* @__PURE__ */ new Map();
33
+ #notificationListeners = /* @__PURE__ */ new Map();
34
+ /** Per-scope-key serial promise chains (tail-of-lane). */
35
+ #lanes = /* @__PURE__ */ new Map();
36
+ /** Register a handler for a wire method name (e.g. `"turn/start"`). */
37
+ register(methodName, handler) {
38
+ this.#handlers.set(methodName, handler);
39
+ return this;
40
+ }
41
+ /** Subscribe to a server→client notification. */
42
+ onNotification(name, listener) {
43
+ let set = this.#notificationListeners.get(name);
44
+ if (!set) {
45
+ set = /* @__PURE__ */ new Set();
46
+ this.#notificationListeners.set(name, set);
47
+ }
48
+ set.add(listener);
49
+ return () => {
50
+ set?.delete(listener);
51
+ };
52
+ }
53
+ /**
54
+ * Emit a server→client notification. Validates the name against the
55
+ * `NOTIFICATIONS` allow-list (fail-closed: unknown name throws).
56
+ */
57
+ emitNotification(name, payload) {
58
+ if (!NOTIFICATION_NAMES.has(name)) {
59
+ throw new Error(`Unknown notification name: ${String(name)}`);
60
+ }
61
+ const listeners = this.#notificationListeners.get(name);
62
+ if (!listeners) return;
63
+ for (const listener of listeners) listener(payload);
64
+ }
65
+ /**
66
+ * Dispatch a single raw envelope. Never throws — every failure path maps
67
+ * to a JSON-RPC error response.
68
+ */
69
+ async dispatch(rawEnvelope) {
70
+ const parsed = requestEnvelopeSchema.safeParse(rawEnvelope);
71
+ if (!parsed.success) {
72
+ return failure(
73
+ extractId(rawEnvelope),
74
+ ERR_INVALID_REQUEST,
75
+ "Invalid request envelope (tenantId is mandatory)"
76
+ );
77
+ }
78
+ const envelope = parsed.data;
79
+ const spec = METHOD_SPEC_BY_WIRE.get(envelope.method);
80
+ if (!spec) {
81
+ return failure(envelope.id, ERR_METHOD_NOT_FOUND, `Method not found: ${envelope.method}`);
82
+ }
83
+ const handler = this.#handlers.get(envelope.method);
84
+ if (!handler) {
85
+ return failure(
86
+ envelope.id,
87
+ ERR_METHOD_NOT_FOUND,
88
+ `No handler registered for method: ${envelope.method}`
89
+ );
90
+ }
91
+ const params = envelope.params && typeof envelope.params === "object" ? envelope.params : {};
92
+ let scope;
93
+ try {
94
+ scope = resolveScope(spec, envelope.tenantId, params);
95
+ } catch {
96
+ return failure(
97
+ envelope.id,
98
+ ERR_INVALID_PARAMS,
99
+ `Method ${envelope.method} requires a threadId for scoping`
100
+ );
101
+ }
102
+ const key = scopeKey(scope);
103
+ return this.#runSerialized(key, async () => {
104
+ try {
105
+ const result = await handler(envelope.params, scope);
106
+ return success(envelope.id, result);
107
+ } catch (error) {
108
+ const message = error instanceof Error ? error.message : "Internal handler error";
109
+ return failure(envelope.id, ERR_INTERNAL, message);
110
+ }
111
+ });
112
+ }
113
+ /**
114
+ * Run `task` after every previously-enqueued task on the same scope key.
115
+ * A failing/slow task never poisons or blocks the lane permanently.
116
+ */
117
+ #runSerialized(key, task) {
118
+ const previous = this.#lanes.get(key) ?? Promise.resolve();
119
+ const run = previous.then(task, task);
120
+ const tail = run.then(
121
+ () => void 0,
122
+ () => void 0
123
+ );
124
+ this.#lanes.set(key, tail);
125
+ void tail.then(() => {
126
+ if (this.#lanes.get(key) === tail) this.#lanes.delete(key);
127
+ });
128
+ return run;
129
+ }
130
+ };
131
+
132
+ export {
133
+ ProtocolDispatcher
134
+ };
135
+ //# sourceMappingURL=chunk-RDKYDMXT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dispatcher.ts"],"sourcesContent":["/**\n * Transport-agnostic JSON-RPC dispatcher for the app-server protocol.\n *\n * Pure and testable: no sockets. WS/SSE/stdio adapters wrap this dispatcher\n * and are intentionally out of scope here.\n *\n * Faithful concurrency model (see `protocol.ts`): requests sharing a\n * `scopeKey` are serialized FIFO; distinct scope keys run concurrently.\n * Because every scope is tenant-prefixed, cross-tenant requests can never\n * share a serial lane (fail-closed multi-tenancy by construction).\n */\n\nimport {\n METHOD_REGISTRY,\n type MethodSpec,\n NOTIFICATIONS,\n type NotificationName,\n requestEnvelopeSchema,\n resolveScope,\n type SerializationScope,\n scopeKey,\n} from \"./protocol\";\n\n// ── Public types ─────────────────────────────────────────────────────────────\n\nexport type MethodHandler = (params: unknown, scope: SerializationScope) => Promise<unknown>;\n\nexport type NotificationListener = (payload: unknown) => void;\n\ninterface JsonRpcSuccess {\n readonly jsonrpc: \"2.0\";\n readonly id: string | number | null;\n readonly result: unknown;\n}\n\ninterface JsonRpcFailure {\n readonly jsonrpc: \"2.0\";\n readonly id: string | number | null;\n readonly error: { readonly code: number; readonly message: string };\n}\n\nexport type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;\n\n// ── JSON-RPC error codes (spec-aligned) ──────────────────────────────────────\n\nconst ERR_INVALID_REQUEST = -32600;\nconst ERR_METHOD_NOT_FOUND = -32601;\nconst ERR_INVALID_PARAMS = -32602;\nconst ERR_INTERNAL = -32603;\n\nfunction success(id: string | number | null, result: unknown): JsonRpcSuccess {\n return { jsonrpc: \"2.0\", id, result };\n}\n\nfunction failure(id: string | number | null, code: number, message: string): JsonRpcFailure {\n return { jsonrpc: \"2.0\", id, error: { code, message } };\n}\n\nconst NOTIFICATION_NAMES: ReadonlySet<string> = new Set(NOTIFICATIONS);\n\nconst METHOD_SPEC_BY_WIRE: ReadonlyMap<string, MethodSpec> = new Map(\n Object.values(METHOD_REGISTRY).map((spec) => [spec.method, spec]),\n);\n\n/** Recover a best-effort id from an unvalidated payload for error responses. */\nfunction extractId(raw: unknown): string | number | null {\n if (raw && typeof raw === \"object\" && \"id\" in raw) {\n const id = (raw as { id: unknown }).id;\n if (typeof id === \"string\" || typeof id === \"number\") return id;\n }\n return null;\n}\n\nexport class ProtocolDispatcher {\n readonly #handlers = new Map<string, MethodHandler>();\n readonly #notificationListeners = new Map<NotificationName, Set<NotificationListener>>();\n /** Per-scope-key serial promise chains (tail-of-lane). */\n readonly #lanes = new Map<string, Promise<void>>();\n\n /** Register a handler for a wire method name (e.g. `\"turn/start\"`). */\n register(methodName: string, handler: MethodHandler): this {\n this.#handlers.set(methodName, handler);\n return this;\n }\n\n /** Subscribe to a server→client notification. */\n onNotification(name: NotificationName, listener: NotificationListener): () => void {\n let set = this.#notificationListeners.get(name);\n if (!set) {\n set = new Set();\n this.#notificationListeners.set(name, set);\n }\n set.add(listener);\n return () => {\n set?.delete(listener);\n };\n }\n\n /**\n * Emit a server→client notification. Validates the name against the\n * `NOTIFICATIONS` allow-list (fail-closed: unknown name throws).\n */\n emitNotification(name: NotificationName, payload: unknown): void {\n if (!NOTIFICATION_NAMES.has(name)) {\n throw new Error(`Unknown notification name: ${String(name)}`);\n }\n const listeners = this.#notificationListeners.get(name);\n if (!listeners) return;\n for (const listener of listeners) listener(payload);\n }\n\n /**\n * Dispatch a single raw envelope. Never throws — every failure path maps\n * to a JSON-RPC error response.\n */\n async dispatch(rawEnvelope: unknown): Promise<JsonRpcResponse> {\n const parsed = requestEnvelopeSchema.safeParse(rawEnvelope);\n if (!parsed.success) {\n return failure(\n extractId(rawEnvelope),\n ERR_INVALID_REQUEST,\n \"Invalid request envelope (tenantId is mandatory)\",\n );\n }\n const envelope = parsed.data;\n\n const spec = METHOD_SPEC_BY_WIRE.get(envelope.method);\n if (!spec) {\n return failure(envelope.id, ERR_METHOD_NOT_FOUND, `Method not found: ${envelope.method}`);\n }\n\n const handler = this.#handlers.get(envelope.method);\n if (!handler) {\n return failure(\n envelope.id,\n ERR_METHOD_NOT_FOUND,\n `No handler registered for method: ${envelope.method}`,\n );\n }\n\n const params: { threadId?: string } =\n envelope.params && typeof envelope.params === \"object\"\n ? (envelope.params as { threadId?: string })\n : {};\n\n let scope: SerializationScope;\n try {\n scope = resolveScope(spec, envelope.tenantId, params);\n } catch {\n return failure(\n envelope.id,\n ERR_INVALID_PARAMS,\n `Method ${envelope.method} requires a threadId for scoping`,\n );\n }\n\n const key = scopeKey(scope);\n return this.#runSerialized(key, async () => {\n try {\n const result = await handler(envelope.params, scope);\n return success(envelope.id, result);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Internal handler error\";\n return failure(envelope.id, ERR_INTERNAL, message);\n }\n });\n }\n\n /**\n * Run `task` after every previously-enqueued task on the same scope key.\n * A failing/slow task never poisons or blocks the lane permanently.\n */\n #runSerialized<T>(key: string, task: () => Promise<T>): Promise<T> {\n const previous = this.#lanes.get(key) ?? Promise.resolve();\n const run = previous.then(task, task);\n // Keep the lane's tail; swallow rejections in the chain link so a thrown\n // task does not break FIFO ordering for the next request.\n const tail = run.then(\n () => undefined,\n () => undefined,\n );\n this.#lanes.set(key, tail);\n // Garbage-collect the lane once drained to avoid unbounded key growth.\n void tail.then(() => {\n if (this.#lanes.get(key) === tail) this.#lanes.delete(key);\n });\n return run;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,63 @@
1
+ import {
2
+ DefinitionResolver,
3
+ substituteArguments
4
+ } from "./chunk-7BUOF367.js";
5
+
6
+ // src/commands.ts
7
+ function bindArgs(input, argNames) {
8
+ const tokens = input.trim().length > 0 ? input.trim().split(/\s+/) : [];
9
+ const args = {};
10
+ if (argNames.length === 0) {
11
+ args.ARGUMENTS = input.trim();
12
+ args["1"] = input.trim();
13
+ return args;
14
+ }
15
+ argNames.forEach((name, i) => {
16
+ if (i === argNames.length - 1) {
17
+ args[name] = tokens.slice(i).join(" ");
18
+ } else {
19
+ args[name] = tokens[i] ?? "";
20
+ }
21
+ });
22
+ return args;
23
+ }
24
+ async function expandCommand(cmd, argInput, variables, bodyLoader) {
25
+ const body = await bodyLoader(cmd);
26
+ const args = bindArgs(argInput, cmd.frontmatter.argNames);
27
+ const content = substituteArguments(body, args, { ...variables });
28
+ const model = cmd.frontmatter.model;
29
+ const modelOverride = model && model !== "inherit" ? model : void 0;
30
+ return Object.freeze({
31
+ contentBlocks: Object.freeze([
32
+ Object.freeze({ role: "user", content })
33
+ ]),
34
+ allowedTools: Object.freeze([...cmd.frontmatter.allowedTools]),
35
+ modelOverride,
36
+ effort: cmd.frontmatter.effort,
37
+ executionMode: cmd.frontmatter.executionMode
38
+ });
39
+ }
40
+ var CommandRegistry = class {
41
+ #resolver;
42
+ constructor(commands) {
43
+ this.#resolver = new DefinitionResolver(commands);
44
+ }
45
+ /** Commands a human may type as a slash command. */
46
+ listForUser(ctx) {
47
+ return this.#resolver.resolve(ctx).filter((c) => c.frontmatter.userInvocable !== false);
48
+ }
49
+ /** Commands the model may auto-trigger. */
50
+ listForModel(ctx) {
51
+ return this.#resolver.resolve(ctx).filter((c) => c.frontmatter.modelInvocable !== false);
52
+ }
53
+ /** Highest-precedence gated record for a slug (no invocability filter). */
54
+ resolveOne(slug, ctx) {
55
+ return this.#resolver.resolveOne(slug, ctx);
56
+ }
57
+ };
58
+
59
+ export {
60
+ expandCommand,
61
+ CommandRegistry
62
+ };
63
+ //# sourceMappingURL=chunk-YYFPDBJG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands.ts"],"sourcesContent":["/**\n * Slash-command registry — faithful re-expression of the unified command model.\n *\n * Semantic model:\n * - A command and a skill are the SAME underlying type. `CommandRecord` is just\n * a `Definition`. The only differences are carried in the record itself:\n * `sourceTier`, and the `userInvocable` / `modelInvocable` frontmatter flags.\n * - Slash invocation (a human types `/deploy`) and model invocation (the model\n * auto-triggers) are two FRONT-DOORS to ONE expansion path.\n * - The registry IS a `DefinitionResolver<CommandRecord>` (layered tier merge +\n * precedence + dual gate are provided by definitions.ts — reused, not rebuilt).\n * `CommandRegistry` is a thin wrapper exposing the two front-doors; both\n * recompute per call so entitlement/flag changes apply live.\n *\n * Scope note: the source harness also has `local` / `local-jsx` command kinds\n * that run TUI-side JSX. Those are intentionally DROPPED here — a server runtime\n * only needs the prompt-expansion path. This module models that path only.\n *\n * Multi-tenant: `tenantId` is mandatory at every entry point; an empty tenant\n * throws (fail-closed); cross-tenant resolution is impossible (the underlying\n * resolver rejects foreign-tenant records).\n */\n\nimport {\n type Definition,\n DefinitionResolver,\n type ResolveContext,\n substituteArguments,\n} from \"./definitions\";\n\n/**\n * A command IS a Definition. No new fields — the distinction between \"command\"\n * and \"skill\" is purely the `sourceTier` + invocability flags already present.\n */\nexport type CommandRecord = Definition;\n\n/** Lazily loads a command's body text (FS / blob / remote — caller's choice). */\nexport type BodyLoader = (cmd: CommandRecord) => Promise<string>;\n\n/** One content block destined for the model turn. */\nexport interface ContentBlock {\n readonly role: \"user\";\n readonly content: string;\n}\n\n/** Result of expanding a command — declarative only; no enforcement here. */\nexport interface ExpandedCommand {\n /** Prompt blocks to splice into the turn. */\n readonly contentBlocks: readonly ContentBlock[];\n /** Tools the command self-declares for the permission context. */\n readonly allowedTools: readonly string[];\n /** Model alias to switch to, if the command pins one (not \"inherit\"). */\n readonly modelOverride?: string | undefined;\n /** Reasoning effort hint, if declared. */\n readonly effort?: \"low\" | \"medium\" | \"high\" | undefined;\n /** inline = expand into current turn; fork = isolated child. */\n readonly executionMode: \"inline\" | \"fork\";\n}\n\n/**\n * Split a positional argument string across declared arg names. Extra tokens\n * beyond the last declared name are folded into that last argument (matches the\n * source's \"rest goes to the last positional\" behaviour). With no declared\n * names the whole string is exposed as `${ARGUMENTS}` / `$1`.\n */\nfunction bindArgs(input: string, argNames: readonly string[]): Record<string, string> {\n const tokens = input.trim().length > 0 ? input.trim().split(/\\s+/) : [];\n const args: Record<string, string> = {};\n if (argNames.length === 0) {\n args.ARGUMENTS = input.trim();\n args[\"1\"] = input.trim();\n return args;\n }\n argNames.forEach((name, i) => {\n if (i === argNames.length - 1) {\n args[name] = tokens.slice(i).join(\" \");\n } else {\n args[name] = tokens[i] ?? \"\";\n }\n });\n return args;\n}\n\n/**\n * Pure expansion. Loads the body lazily (exactly once), substitutes named args\n * and a tenant-safe variable map (only the explicitly injected `variables` —\n * never `process.env`), and returns the declarative execution context.\n */\nexport async function expandCommand(\n cmd: CommandRecord,\n argInput: string,\n variables: Readonly<Record<string, string>>,\n bodyLoader: BodyLoader,\n): Promise<ExpandedCommand> {\n const body = await bodyLoader(cmd);\n const args = bindArgs(argInput, cmd.frontmatter.argNames);\n // Only injected variables are visible — host env is deliberately excluded.\n const content = substituteArguments(body, args, { ...variables });\n\n const model = cmd.frontmatter.model;\n const modelOverride = model && model !== \"inherit\" ? model : undefined;\n\n return Object.freeze({\n contentBlocks: Object.freeze([\n Object.freeze({ role: \"user\" as const, content }),\n ]) as readonly ContentBlock[],\n allowedTools: Object.freeze([...cmd.frontmatter.allowedTools]),\n modelOverride,\n effort: cmd.frontmatter.effort,\n executionMode: cmd.frontmatter.executionMode,\n });\n}\n\n/**\n * Thin tenant-scoped wrapper over `DefinitionResolver<CommandRecord>`. Exposes\n * the two front-doors. Tier merge / precedence / dual gate / tenant isolation\n * are delegated to the resolver — this class only filters by invocability.\n */\nexport class CommandRegistry {\n readonly #resolver: DefinitionResolver<CommandRecord>;\n\n constructor(commands: readonly CommandRecord[]) {\n this.#resolver = new DefinitionResolver<CommandRecord>(commands);\n }\n\n /** Commands a human may type as a slash command. */\n listForUser(ctx: ResolveContext): CommandRecord[] {\n return this.#resolver.resolve(ctx).filter((c) => c.frontmatter.userInvocable !== false);\n }\n\n /** Commands the model may auto-trigger. */\n listForModel(ctx: ResolveContext): CommandRecord[] {\n return this.#resolver.resolve(ctx).filter((c) => c.frontmatter.modelInvocable !== false);\n }\n\n /** Highest-precedence gated record for a slug (no invocability filter). */\n resolveOne(slug: string, ctx: ResolveContext): CommandRecord | undefined {\n return this.#resolver.resolveOne(slug, ctx);\n }\n}\n"],"mappings":";;;;;;AAiEA,SAAS,SAAS,OAAe,UAAqD;AACpF,QAAM,SAAS,MAAM,KAAK,EAAE,SAAS,IAAI,MAAM,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AACtE,QAAM,OAA+B,CAAC;AACtC,MAAI,SAAS,WAAW,GAAG;AACzB,SAAK,YAAY,MAAM,KAAK;AAC5B,SAAK,GAAG,IAAI,MAAM,KAAK;AACvB,WAAO;AAAA,EACT;AACA,WAAS,QAAQ,CAAC,MAAM,MAAM;AAC5B,QAAI,MAAM,SAAS,SAAS,GAAG;AAC7B,WAAK,IAAI,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,GAAG;AAAA,IACvC,OAAO;AACL,WAAK,IAAI,IAAI,OAAO,CAAC,KAAK;AAAA,IAC5B;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAOA,eAAsB,cACpB,KACA,UACA,WACA,YAC0B;AAC1B,QAAM,OAAO,MAAM,WAAW,GAAG;AACjC,QAAM,OAAO,SAAS,UAAU,IAAI,YAAY,QAAQ;AAExD,QAAM,UAAU,oBAAoB,MAAM,MAAM,EAAE,GAAG,UAAU,CAAC;AAEhE,QAAM,QAAQ,IAAI,YAAY;AAC9B,QAAM,gBAAgB,SAAS,UAAU,YAAY,QAAQ;AAE7D,SAAO,OAAO,OAAO;AAAA,IACnB,eAAe,OAAO,OAAO;AAAA,MAC3B,OAAO,OAAO,EAAE,MAAM,QAAiB,QAAQ,CAAC;AAAA,IAClD,CAAC;AAAA,IACD,cAAc,OAAO,OAAO,CAAC,GAAG,IAAI,YAAY,YAAY,CAAC;AAAA,IAC7D;AAAA,IACA,QAAQ,IAAI,YAAY;AAAA,IACxB,eAAe,IAAI,YAAY;AAAA,EACjC,CAAC;AACH;AAOO,IAAM,kBAAN,MAAsB;AAAA,EAClB;AAAA,EAET,YAAY,UAAoC;AAC9C,SAAK,YAAY,IAAI,mBAAkC,QAAQ;AAAA,EACjE;AAAA;AAAA,EAGA,YAAY,KAAsC;AAChD,WAAO,KAAK,UAAU,QAAQ,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,kBAAkB,KAAK;AAAA,EACxF;AAAA;AAAA,EAGA,aAAa,KAAsC;AACjD,WAAO,KAAK,UAAU,QAAQ,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,mBAAmB,KAAK;AAAA,EACzF;AAAA;AAAA,EAGA,WAAW,MAAc,KAAgD;AACvE,WAAO,KAAK,UAAU,WAAW,MAAM,GAAG;AAAA,EAC5C;AACF;","names":[]}
@@ -0,0 +1,135 @@
1
+ import {
2
+ substituteArguments
3
+ } from "./chunk-7BUOF367.js";
4
+
5
+ // src/skills.ts
6
+ import { z } from "zod";
7
+ var FIRST_PARTY_TIERS = /* @__PURE__ */ new Set(["bundled", "builtin"]);
8
+ var isFirstParty = (s) => FIRST_PARTY_TIERS.has(s.sourceTier);
9
+ var listingOptionsSchema = z.object({
10
+ maxListingDescChars: z.number().int().positive().default(250),
11
+ contextWindowTokens: z.number().int().positive().default(2e5),
12
+ budgetPercent: z.number().positive().max(1).default(0.01),
13
+ touchedPaths: z.array(z.string()).default([])
14
+ });
15
+ var estimateTokens = (text) => Math.ceil(text.length / 4);
16
+ function globToRegExp(glob) {
17
+ let out = "^";
18
+ for (let i = 0; i < glob.length; i++) {
19
+ const c = glob[i];
20
+ if (c === "*") {
21
+ if (glob[i + 1] === "*") {
22
+ out += ".*";
23
+ i++;
24
+ } else {
25
+ out += "[^/]*";
26
+ }
27
+ } else if (".+?^${}()|[]\\".includes(c)) {
28
+ out += `\\${c}`;
29
+ } else {
30
+ out += c;
31
+ }
32
+ }
33
+ return new RegExp(`${out}$`);
34
+ }
35
+ var pathMatches = (globs, touched) => globs.some((g) => {
36
+ const re = globToRegExp(g);
37
+ return touched.some((p) => re.test(p));
38
+ });
39
+ function orderSkills(skills) {
40
+ return [...skills].sort((a, b) => {
41
+ const fa = isFirstParty(a) ? 0 : 1;
42
+ const fb = isFirstParty(b) ? 0 : 1;
43
+ if (fa !== fb) return fa - fb;
44
+ return a.slug.localeCompare(b.slug);
45
+ });
46
+ }
47
+ var truncate = (s, max) => s.length <= max ? s : s.slice(0, max);
48
+ function renderEntry(e) {
49
+ const head = e.whenToUse ? `- ${e.name} \u2014 ${e.whenToUse}` : `- ${e.name}`;
50
+ return e.description ? `${head}
51
+ ${e.description}` : head;
52
+ }
53
+ function buildSkillListing(resolver, ctx, options = {}) {
54
+ if (!ctx.tenantId) throw new Error("buildSkillListing: tenantId is required");
55
+ const opts = listingOptionsSchema.parse(options);
56
+ const loaded = ctx.loadedSlugs ?? /* @__PURE__ */ new Set();
57
+ const visible = resolver.resolve(ctx).filter((s) => !loaded.has(s.slug)).filter((s) => {
58
+ const paths = s.frontmatter.paths;
59
+ return paths.length === 0 || pathMatches(paths, opts.touchedPaths);
60
+ });
61
+ const budget = Math.max(1, Math.floor(opts.contextWindowTokens * opts.budgetPercent));
62
+ const entries = [];
63
+ let used = 0;
64
+ for (const s of orderSkills(visible)) {
65
+ const fp = isFirstParty(s);
66
+ const fm = s.frontmatter;
67
+ const fullDesc = fp ? fm.description : truncate(fm.description, opts.maxListingDescChars);
68
+ if (fp) {
69
+ const entry = {
70
+ name: fm.name,
71
+ description: fm.description,
72
+ whenToUse: fm.whenToUse,
73
+ degraded: false,
74
+ firstParty: true
75
+ };
76
+ entries.push(entry);
77
+ used += estimateTokens(renderEntry(entry));
78
+ continue;
79
+ }
80
+ const full = {
81
+ name: fm.name,
82
+ description: fullDesc,
83
+ whenToUse: fm.whenToUse,
84
+ degraded: false,
85
+ firstParty: false
86
+ };
87
+ const fullCost = estimateTokens(renderEntry(full));
88
+ if (used + fullCost <= budget) {
89
+ entries.push(full);
90
+ used += fullCost;
91
+ continue;
92
+ }
93
+ const nameOnly = {
94
+ name: fm.name,
95
+ description: "",
96
+ whenToUse: void 0,
97
+ degraded: true,
98
+ firstParty: false
99
+ };
100
+ const nameCost = estimateTokens(renderEntry(nameOnly));
101
+ if (used + nameCost <= budget) {
102
+ entries.push(nameOnly);
103
+ used += nameCost;
104
+ }
105
+ }
106
+ const text = entries.map(renderEntry).join("\n");
107
+ return { entries, text };
108
+ }
109
+ async function expandSkill(skill, ctx) {
110
+ if (!ctx.tenantId) throw new Error("expandSkill: tenantId is required");
111
+ if (skill.tenantId !== ctx.tenantId) {
112
+ throw new Error("expandSkill: cross-tenant skill access rejected");
113
+ }
114
+ const rawBody = await ctx.bodyLoader(skill.bodyRef);
115
+ const body = substituteArguments(rawBody, ctx.args, ctx.vars);
116
+ const baseLine = `Base directory for this skill: ${ctx.skillResourceBase(skill.slug)}`;
117
+ const messages = [
118
+ { role: "system", content: baseLine },
119
+ { role: "user", content: body }
120
+ ];
121
+ const allowedTools = [...skill.frontmatter.allowedTools];
122
+ return {
123
+ messages,
124
+ allowedTools,
125
+ modelOverride: skill.frontmatter.model,
126
+ effort: skill.frontmatter.effort,
127
+ executionMode: skill.frontmatter.executionMode
128
+ };
129
+ }
130
+
131
+ export {
132
+ buildSkillListing,
133
+ expandSkill
134
+ };
135
+ //# sourceMappingURL=chunk-ZMYX5VBU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/skills.ts"],"sourcesContent":["/**\n * Skills system (WRAP — progressive-disclosure capability).\n *\n * Faithful re-expression of the source harness's two-phase skill model:\n *\n * Phase 1 — Listing (cheap, always in context): a budget-bounded catalog\n * exposing only {name, description, whenToUse} per skill. First-party\n * skills (bundled/builtin tier) are never truncated and never dropped;\n * lower-tier skills degrade to names-only, then drop, as the context\n * budget is exhausted. Path-activated skills stay hidden until a touched\n * file matches one of their globs.\n *\n * Phase 2 — Invocation (lazy, on demand): expand the chosen skill's body\n * into turn messages, with arg/var substitution and a merged tool\n * allowlist contributed to the permission context.\n *\n * A skill IS a `Definition` — the same tenant-scoped layered resolver backs\n * the command registry and the subagent registry (see ./definitions).\n *\n * SECURITY: this re-expression intentionally drops the source's body-side\n * command-substitution (the upstream \"!`…`\" inline-shell facility). Skill\n * bodies are inert text; they NEVER trigger host shell execution here. All\n * entry points require a non-empty `tenantId` and fail closed; cross-tenant\n * inputs are rejected by the underlying `DefinitionResolver`. Pure data/logic\n * — no host filesystem access (resource base is an injected indirection).\n */\n\nimport { z } from \"zod\";\nimport {\n type Definition,\n type DefinitionResolver,\n type ResolveContext,\n substituteArguments,\n} from \"./definitions\";\n\n/** A skill is just a resolvable `Definition`. */\nexport type SkillRecord = Definition;\n\n/** Tiers treated as first-party (never truncated, never dropped). */\nconst FIRST_PARTY_TIERS: ReadonlySet<string> = new Set([\"bundled\", \"builtin\"]);\n\nconst isFirstParty = (s: SkillRecord): boolean => FIRST_PARTY_TIERS.has(s.sourceTier);\n\n// ── Phase 1: listing ─────────────────────────────────────────────────────────\n\n/** One catalog entry — only the cheap, always-in-context surface. */\nexport interface SkillListingEntry {\n readonly name: string;\n readonly description: string;\n readonly whenToUse?: string | undefined;\n /** True when this entry was degraded to names-only by the budget. */\n readonly degraded: boolean;\n readonly firstParty: boolean;\n}\n\nexport interface SkillListing {\n readonly entries: readonly SkillListingEntry[];\n readonly text: string;\n}\n\nconst listingOptionsSchema = z.object({\n maxListingDescChars: z.number().int().positive().default(250),\n contextWindowTokens: z.number().int().positive().default(200_000),\n budgetPercent: z.number().positive().max(1).default(0.01),\n touchedPaths: z.array(z.string()).default([]),\n});\nexport type SkillListingOptions = z.input<typeof listingOptionsSchema>;\n\n/** Cheap, deterministic token estimate (≈4 chars/token). */\nconst estimateTokens = (text: string): number => Math.ceil(text.length / 4);\n\n/** Minimal glob: `**` = any chars, `*` = any chars except `/`, else exact. */\nfunction globToRegExp(glob: string): RegExp {\n let out = \"^\";\n for (let i = 0; i < glob.length; i++) {\n const c = glob[i]!;\n if (c === \"*\") {\n if (glob[i + 1] === \"*\") {\n out += \".*\";\n i++;\n } else {\n out += \"[^/]*\";\n }\n } else if (\".+?^${}()|[]\\\\\".includes(c)) {\n out += `\\\\${c}`;\n } else {\n out += c;\n }\n }\n return new RegExp(`${out}$`);\n}\n\nconst pathMatches = (globs: readonly string[], touched: readonly string[]): boolean =>\n globs.some((g) => {\n const re = globToRegExp(g);\n return touched.some((p) => re.test(p));\n });\n\n/** Stable order: first-party first, then by slug — deterministic output. */\nfunction orderSkills(skills: readonly SkillRecord[]): SkillRecord[] {\n return [...skills].sort((a, b) => {\n const fa = isFirstParty(a) ? 0 : 1;\n const fb = isFirstParty(b) ? 0 : 1;\n if (fa !== fb) return fa - fb;\n return a.slug.localeCompare(b.slug);\n });\n}\n\nconst truncate = (s: string, max: number): string => (s.length <= max ? s : s.slice(0, max));\n\nfunction renderEntry(e: SkillListingEntry): string {\n const head = e.whenToUse ? `- ${e.name} — ${e.whenToUse}` : `- ${e.name}`;\n return e.description ? `${head}\\n ${e.description}` : head;\n}\n\n/**\n * Phase 1 — build the always-in-context skill catalog. Pure & deterministic.\n *\n * Budget = `contextWindowTokens * budgetPercent`. First-party skills are\n * emitted in full and do NOT consume budget against being dropped. Each\n * remaining skill is tried at full description; if it would overflow it is\n * degraded to names-only; if even that overflows it is dropped. Skills in\n * `ctx.loadedSlugs` and unactivated path-gated skills are suppressed.\n */\nexport function buildSkillListing(\n resolver: DefinitionResolver<SkillRecord>,\n ctx: ResolveContext,\n options: SkillListingOptions = {},\n): SkillListing {\n if (!ctx.tenantId) throw new Error(\"buildSkillListing: tenantId is required\");\n const opts = listingOptionsSchema.parse(options);\n\n const loaded = ctx.loadedSlugs ?? new Set<string>();\n const visible = resolver\n .resolve(ctx)\n .filter((s) => !loaded.has(s.slug))\n .filter((s) => {\n const paths = s.frontmatter.paths;\n return paths.length === 0 || pathMatches(paths, opts.touchedPaths);\n });\n\n const budget = Math.max(1, Math.floor(opts.contextWindowTokens * opts.budgetPercent));\n\n const entries: SkillListingEntry[] = [];\n let used = 0;\n\n for (const s of orderSkills(visible)) {\n const fp = isFirstParty(s);\n const fm = s.frontmatter;\n const fullDesc = fp ? fm.description : truncate(fm.description, opts.maxListingDescChars);\n\n if (fp) {\n const entry: SkillListingEntry = {\n name: fm.name,\n description: fm.description,\n whenToUse: fm.whenToUse,\n degraded: false,\n firstParty: true,\n };\n entries.push(entry);\n used += estimateTokens(renderEntry(entry));\n continue;\n }\n\n const full: SkillListingEntry = {\n name: fm.name,\n description: fullDesc,\n whenToUse: fm.whenToUse,\n degraded: false,\n firstParty: false,\n };\n const fullCost = estimateTokens(renderEntry(full));\n if (used + fullCost <= budget) {\n entries.push(full);\n used += fullCost;\n continue;\n }\n\n const nameOnly: SkillListingEntry = {\n name: fm.name,\n description: \"\",\n whenToUse: undefined,\n degraded: true,\n firstParty: false,\n };\n const nameCost = estimateTokens(renderEntry(nameOnly));\n if (used + nameCost <= budget) {\n entries.push(nameOnly);\n used += nameCost;\n }\n // else: drop entirely.\n }\n\n const text = entries.map(renderEntry).join(\"\\n\");\n return { entries, text };\n}\n\n// ── Phase 2: invocation ──────────────────────────────────────────────────────\n\nexport interface SkillMessage {\n readonly role: \"system\" | \"user\";\n readonly content: string;\n}\n\nexport interface ExpandedSkill {\n readonly messages: readonly SkillMessage[];\n readonly allowedTools: readonly string[];\n readonly modelOverride?: string | undefined;\n readonly effort?: \"low\" | \"medium\" | \"high\" | undefined;\n readonly executionMode: \"inline\" | \"fork\";\n}\n\nexport interface ExpandContext {\n readonly tenantId: string;\n readonly args: Record<string, string>;\n readonly vars: Record<string, string>;\n /** Lazy body fetch — host supplies; called only here, only once. */\n readonly bodyLoader: (bodyRef: string) => Promise<string>;\n /** Host-injected resource-base indirection (NO host FS access here). */\n readonly skillResourceBase: (slug: string) => string;\n}\n\n/**\n * Phase 2 — expand a skill into turn messages. Body is loaded lazily via the\n * injected `bodyLoader` (never during listing). A `Base directory…` line is\n * prepended via the injected `skillResourceBase` indirection. Args + vars are\n * substituted with `substituteArguments`. The skill's `allowedTools` are\n * returned for the caller to merge into the permission context — this never\n * mutates global state and never executes anything from the body.\n */\nexport async function expandSkill(skill: SkillRecord, ctx: ExpandContext): Promise<ExpandedSkill> {\n if (!ctx.tenantId) throw new Error(\"expandSkill: tenantId is required\");\n if (skill.tenantId !== ctx.tenantId) {\n throw new Error(\"expandSkill: cross-tenant skill access rejected\");\n }\n\n const rawBody = await ctx.bodyLoader(skill.bodyRef);\n const body = substituteArguments(rawBody, ctx.args, ctx.vars);\n const baseLine = `Base directory for this skill: ${ctx.skillResourceBase(skill.slug)}`;\n\n const messages: readonly SkillMessage[] = [\n { role: \"system\", content: baseLine },\n { role: \"user\", content: body },\n ];\n\n const allowedTools = [...skill.frontmatter.allowedTools];\n\n return {\n messages,\n allowedTools,\n modelOverride: skill.frontmatter.model,\n effort: skill.frontmatter.effort,\n executionMode: skill.frontmatter.executionMode,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,131 @@
1
+ // src/adapters/dispatcher-sse.ts
2
+ var ERR_PARSE = -32700;
3
+ var JSON_HEADERS = {
4
+ "content-type": "application/json; charset=utf-8"
5
+ };
6
+ function parseErrorEnvelope(message) {
7
+ return { jsonrpc: "2.0", id: null, error: { code: ERR_PARSE, message } };
8
+ }
9
+ function jsonResponse(payload, status) {
10
+ return new Response(JSON.stringify(payload), { status, headers: { ...JSON_HEADERS } });
11
+ }
12
+ function createRpcHandler(dispatcher) {
13
+ return async (request) => {
14
+ let raw;
15
+ try {
16
+ raw = await request.json();
17
+ } catch {
18
+ return jsonResponse(parseErrorEnvelope("Request body is not valid JSON"), 400);
19
+ }
20
+ try {
21
+ const envelope = await dispatcher.dispatch(raw);
22
+ return jsonResponse(envelope, 200);
23
+ } catch {
24
+ return jsonResponse(parseErrorEnvelope("Dispatcher failure"), 400);
25
+ }
26
+ };
27
+ }
28
+ function frame(event) {
29
+ return `event: ${event.type}
30
+ data: ${JSON.stringify(event)}
31
+
32
+ `;
33
+ }
34
+ function errorFrame(message) {
35
+ return `event: error
36
+ data: ${JSON.stringify({ type: "error", message })}
37
+
38
+ `;
39
+ }
40
+ function sseResponse(stream, opts) {
41
+ const encoder = new TextEncoder();
42
+ const signal = opts?.signal;
43
+ const body = new ReadableStream({
44
+ async start(controller) {
45
+ const iterator = stream[Symbol.asyncIterator]();
46
+ const onAbort = () => {
47
+ void iterator.return?.(void 0);
48
+ };
49
+ if (signal) {
50
+ if (signal.aborted) {
51
+ await iterator.return?.(void 0);
52
+ controller.close();
53
+ return;
54
+ }
55
+ signal.addEventListener("abort", onAbort, { once: true });
56
+ }
57
+ try {
58
+ for (; ; ) {
59
+ if (signal?.aborted) break;
60
+ const next = await iterator.next();
61
+ if (next.done) break;
62
+ controller.enqueue(encoder.encode(frame(next.value)));
63
+ }
64
+ } catch (error) {
65
+ const message = error instanceof Error ? error.message : "Stream error";
66
+ controller.enqueue(encoder.encode(errorFrame(message)));
67
+ } finally {
68
+ signal?.removeEventListener("abort", onAbort);
69
+ controller.close();
70
+ }
71
+ },
72
+ cancel() {
73
+ void stream[Symbol.asyncIterator]().return?.(void 0);
74
+ }
75
+ });
76
+ return new Response(body, {
77
+ status: 200,
78
+ headers: {
79
+ "content-type": "text/event-stream; charset=utf-8",
80
+ "cache-control": "no-cache, no-transform",
81
+ connection: "keep-alive"
82
+ }
83
+ });
84
+ }
85
+ function subscribeNotifications(dispatcher, names) {
86
+ return {
87
+ [Symbol.asyncIterator]() {
88
+ const backlog = [];
89
+ const waiters = [];
90
+ let closed = false;
91
+ const unsubscribers = names.map(
92
+ (name) => dispatcher.onNotification(name, (payload) => {
93
+ if (closed) return;
94
+ const value = { name, payload };
95
+ const waiter = waiters.shift();
96
+ if (waiter) waiter({ value, done: false });
97
+ else backlog.push(value);
98
+ })
99
+ );
100
+ const teardown = () => {
101
+ if (closed) return;
102
+ closed = true;
103
+ for (const off of unsubscribers) off();
104
+ let waiter = waiters.shift();
105
+ while (waiter) {
106
+ waiter({ value: void 0, done: true });
107
+ waiter = waiters.shift();
108
+ }
109
+ };
110
+ return {
111
+ next() {
112
+ const buffered = backlog.shift();
113
+ if (buffered) return Promise.resolve({ value: buffered, done: false });
114
+ if (closed) return Promise.resolve({ value: void 0, done: true });
115
+ return new Promise((resolve) => waiters.push(resolve));
116
+ },
117
+ return() {
118
+ teardown();
119
+ return Promise.resolve({ value: void 0, done: true });
120
+ }
121
+ };
122
+ }
123
+ };
124
+ }
125
+
126
+ export {
127
+ createRpcHandler,
128
+ sseResponse,
129
+ subscribeNotifications
130
+ };
131
+ //# sourceMappingURL=chunk-ZTSKS42I.js.map