@nebutra/agent-runtime 0.2.0 → 0.2.2

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 (137) hide show
  1. package/LICENSE +21 -676
  2. package/README.md +2 -0
  3. package/dist/adapters/dispatcher-sse.js +1 -0
  4. package/dist/adapters/index.d.ts +24 -5
  5. package/dist/adapters/index.js +28 -0
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/adapters/mcp-catalog.js +1 -0
  8. package/dist/adapters/prisma-rollout.js +1 -0
  9. package/dist/chunk-424PT5DM.js +23 -0
  10. package/dist/chunk-424PT5DM.js.map +1 -0
  11. package/dist/{chunk-NN7DATXA.js → chunk-4Y25ZTKI.js} +3 -3
  12. package/dist/chunk-4Y25ZTKI.js.map +1 -0
  13. package/dist/{chunk-BJBBR3QA.js → chunk-D4YAPLOW.js} +4 -4
  14. package/dist/chunk-D4YAPLOW.js.map +1 -0
  15. package/dist/{chunk-ZMYX5VBU.js → chunk-GQZKYWFT.js} +23 -7
  16. package/dist/chunk-GQZKYWFT.js.map +1 -0
  17. package/dist/chunk-KCNN4QUQ.js +255 -0
  18. package/dist/chunk-KCNN4QUQ.js.map +1 -0
  19. package/dist/{chunk-PGGWSUTM.js → chunk-NI4EDT4T.js} +2 -2
  20. package/dist/chunk-NI4EDT4T.js.map +1 -0
  21. package/dist/chunk-Q62VKHIT.js +178 -0
  22. package/dist/chunk-Q62VKHIT.js.map +1 -0
  23. package/dist/{chunk-MUF7ZZTO.js → chunk-R5HOSQUW.js} +2 -1
  24. package/dist/{chunk-MUF7ZZTO.js.map → chunk-R5HOSQUW.js.map} +1 -1
  25. package/dist/{chunk-5N4644PB.js → chunk-SD2ZJ7XG.js} +4 -4
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +52 -0
  28. package/dist/cli.js.map +1 -0
  29. package/dist/commands.js +1 -0
  30. package/dist/definitions.js +1 -0
  31. package/dist/dispatcher.js +1 -0
  32. package/dist/durable-turn.js +3 -2
  33. package/dist/hook-pipeline.js +1 -0
  34. package/dist/index.d.ts +8 -85
  35. package/dist/index.js +249 -134
  36. package/dist/index.js.map +1 -1
  37. package/dist/loop.d.ts +2 -2
  38. package/dist/loop.js +3 -2
  39. package/dist/mcp-bridge.d.ts +3 -3
  40. package/dist/mcp-bridge.js +3 -2
  41. package/dist/model.js +1 -0
  42. package/dist/orchestration.d.ts +84 -0
  43. package/dist/orchestration.js +16 -0
  44. package/dist/orchestration.js.map +1 -0
  45. package/dist/policy.js +1 -0
  46. package/dist/protocol.js +1 -0
  47. package/dist/pulsar.d.ts +78 -0
  48. package/dist/pulsar.js +17 -0
  49. package/dist/pulsar.js.map +1 -0
  50. package/dist/rollout-store-persistent.js +1 -0
  51. package/dist/rollout.js +1 -0
  52. package/dist/sandbox.js +2 -1
  53. package/dist/skills.js +2 -1
  54. package/dist/subagents.js +1 -0
  55. package/dist/tools.d.ts +4 -3
  56. package/dist/tools.js +5 -3
  57. package/package.json +84 -27
  58. package/.turbo/turbo-build.log +0 -115
  59. package/.turbo/turbo-test.log +0 -44
  60. package/.turbo/turbo-typecheck.log +0 -4
  61. package/CHANGELOG.md +0 -253
  62. package/dist/chunk-BJBBR3QA.js.map +0 -1
  63. package/dist/chunk-NN7DATXA.js.map +0 -1
  64. package/dist/chunk-PGGWSUTM.js.map +0 -1
  65. package/dist/chunk-ZMYX5VBU.js.map +0 -1
  66. package/src/adapters/dispatcher-sse.test.ts +0 -218
  67. package/src/adapters/dispatcher-sse.ts +0 -222
  68. package/src/adapters/index.ts +0 -18
  69. package/src/adapters/mcp-catalog.test.ts +0 -213
  70. package/src/adapters/mcp-catalog.ts +0 -188
  71. package/src/adapters/prisma-rollout.test.ts +0 -153
  72. package/src/adapters/prisma-rollout.ts +0 -104
  73. package/src/agent-runtime.test.ts +0 -176
  74. package/src/artifact-stream.test.ts +0 -330
  75. package/src/artifact-stream.ts +0 -453
  76. package/src/channel-gateway.test.ts +0 -432
  77. package/src/channel-gateway.ts +0 -357
  78. package/src/code-review.test.ts +0 -501
  79. package/src/code-review.ts +0 -495
  80. package/src/command-suggestions.test.ts +0 -251
  81. package/src/command-suggestions.ts +0 -338
  82. package/src/commands.test.ts +0 -184
  83. package/src/commands.ts +0 -140
  84. package/src/commit-message.test.ts +0 -249
  85. package/src/commit-message.ts +0 -180
  86. package/src/context-compaction.test.ts +0 -522
  87. package/src/context-compaction.ts +0 -434
  88. package/src/definitions.test.ts +0 -78
  89. package/src/definitions.ts +0 -190
  90. package/src/deployment-status.test.ts +0 -215
  91. package/src/deployment-status.ts +0 -227
  92. package/src/design-context.test.ts +0 -195
  93. package/src/design-context.ts +0 -198
  94. package/src/dispatcher.test.ts +0 -234
  95. package/src/dispatcher.ts +0 -189
  96. package/src/durable-turn.test.ts +0 -209
  97. package/src/durable-turn.ts +0 -135
  98. package/src/edit-planner.test.ts +0 -204
  99. package/src/edit-planner.ts +0 -325
  100. package/src/fuzzy-match.test.ts +0 -311
  101. package/src/fuzzy-match.ts +0 -444
  102. package/src/hook-pipeline.test.ts +0 -279
  103. package/src/hook-pipeline.ts +0 -373
  104. package/src/inbound-admission.test.ts +0 -394
  105. package/src/inbound-admission.ts +0 -246
  106. package/src/index.ts +0 -47
  107. package/src/loop.test.ts +0 -161
  108. package/src/loop.ts +0 -211
  109. package/src/mcp-bridge.test.ts +0 -165
  110. package/src/mcp-bridge.ts +0 -76
  111. package/src/memory-provider.test.ts +0 -232
  112. package/src/memory-provider.ts +0 -257
  113. package/src/model.ts +0 -168
  114. package/src/permission-ruleset.test.ts +0 -301
  115. package/src/permission-ruleset.ts +0 -200
  116. package/src/policy.ts +0 -151
  117. package/src/project-repo.test.ts +0 -232
  118. package/src/project-repo.ts +0 -311
  119. package/src/protocol.ts +0 -159
  120. package/src/rollout-store-persistent.test.ts +0 -217
  121. package/src/rollout-store-persistent.ts +0 -166
  122. package/src/rollout.ts +0 -150
  123. package/src/sandbox.ts +0 -113
  124. package/src/session-share.test.ts +0 -360
  125. package/src/session-share.ts +0 -310
  126. package/src/skill-distillation.test.ts +0 -177
  127. package/src/skill-distillation.ts +0 -369
  128. package/src/skills.test.ts +0 -277
  129. package/src/skills.ts +0 -255
  130. package/src/subagents.test.ts +0 -290
  131. package/src/subagents.ts +0 -332
  132. package/src/tools.ts +0 -126
  133. package/src/workbench.test.ts +0 -0
  134. package/src/workbench.ts +0 -0
  135. package/tsconfig.json +0 -12
  136. package/tsup.config.ts +0 -33
  137. /package/dist/{chunk-5N4644PB.js.map → chunk-SD2ZJ7XG.js.map} +0 -0
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @nebutra/agent-runtime
2
2
 
3
+ Status: **WIP**
4
+
3
5
  > Status: WIP — not yet integrated into a production app. Track-B kernel
4
6
  > transport and durable-turn queue binding are interface-only.
5
7
 
@@ -3,6 +3,7 @@ import {
3
3
  sseResponse,
4
4
  subscribeNotifications
5
5
  } from "../chunk-ZTSKS42I.js";
6
+ import "../chunk-424PT5DM.js";
6
7
  export {
7
8
  createRpcHandler,
8
9
  sseResponse,
@@ -1,12 +1,31 @@
1
+ import { ModelInvoker } from '../loop.js';
1
2
  export { NotificationFrame, SseOptions, createRpcHandler, sseResponse, subscribeNotifications } from './dispatcher-sse.js';
2
3
  export { McpExecutorLike, McpRegistryLike, createMcpClientPort, createMcpServerCatalog } from './mcp-catalog.js';
3
4
  export { PrismaRolloutDelegate, RolloutDelegateResolver, createPrismaRolloutPersistence } from './prisma-rollout.js';
4
- import '../dispatcher.js';
5
- import '../protocol.js';
6
- import 'zod';
7
5
  import '../model.js';
6
+ import '../policy.js';
7
+ import 'zod';
8
+ import '../protocol.js';
9
+ import '../rollout.js';
10
+ import '../tools.js';
11
+ import '../dispatcher.js';
8
12
  import '@nebutra/mcp';
9
13
  import '../mcp-bridge.js';
10
- import '../tools.js';
11
14
  import '../rollout-store-persistent.js';
12
- import '../rollout.js';
15
+
16
+ /**
17
+ * AgentsModelInvoker — the ONE bridge that makes the substrate's `runTurn` use
18
+ * the real provider stack. Implements `ModelInvoker` over @nebutra/agents
19
+ * `generateText` (which resolves the provider + model and runs the AI SDK).
20
+ * Each round sends the running history at the round's `config.model` — which is
21
+ * how a per-node model (resolved from a node's NodeModelSpec) reaches the
22
+ * provider.
23
+ *
24
+ * Tools are NOT passed here: the runtime's own ToolRegistry/loop owns tool
25
+ * dispatch; this round is a plain text completion. Errors propagate to the loop
26
+ * (which records them on the rollout), not swallowed.
27
+ */
28
+
29
+ declare function createAgentsModelInvoker(): ModelInvoker;
30
+
31
+ export { createAgentsModelInvoker };
@@ -10,7 +10,35 @@ import {
10
10
  import {
11
11
  createPrismaRolloutPersistence
12
12
  } from "../chunk-57W3AR43.js";
13
+ import "../chunk-424PT5DM.js";
14
+
15
+ // src/adapters/agents-model-invoker.ts
16
+ import { generateText } from "@nebutra/agents";
17
+ function toRole(role) {
18
+ return role === "assistant" || role === "system" ? role : "user";
19
+ }
20
+ function createAgentsModelInvoker() {
21
+ return {
22
+ async invoke(request) {
23
+ const messages = request.history.map((m) => ({
24
+ role: toRole(m.role),
25
+ content: m.content
26
+ }));
27
+ const result = await generateText(messages, { model: request.config.model });
28
+ const text = result.text ?? "";
29
+ const usage = result.usage;
30
+ return {
31
+ emissions: text.length > 0 ? [{ kind: "text", text }] : [],
32
+ usage: {
33
+ inputTokens: usage?.inputTokens ?? 0,
34
+ outputTokens: usage?.outputTokens ?? 0
35
+ }
36
+ };
37
+ }
38
+ };
39
+ }
13
40
  export {
41
+ createAgentsModelInvoker,
14
42
  createMcpClientPort,
15
43
  createMcpServerCatalog,
16
44
  createPrismaRolloutPersistence,
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/agents-model-invoker.ts"],"sourcesContent":["/**\n * AgentsModelInvoker — the ONE bridge that makes the substrate's `runTurn` use\n * the real provider stack. Implements `ModelInvoker` over @nebutra/agents\n * `generateText` (which resolves the provider + model and runs the AI SDK).\n * Each round sends the running history at the round's `config.model` — which is\n * how a per-node model (resolved from a node's NodeModelSpec) reaches the\n * provider.\n *\n * Tools are NOT passed here: the runtime's own ToolRegistry/loop owns tool\n * dispatch; this round is a plain text completion. Errors propagate to the loop\n * (which records them on the rollout), not swallowed.\n */\n\nimport { generateText } from \"@nebutra/agents\";\nimport type { ModelInvoker, ModelRoundRequest, ModelRoundResult } from \"../loop\";\n\ntype Role = \"user\" | \"assistant\" | \"system\";\n\nfunction toRole(role: string): Role {\n return role === \"assistant\" || role === \"system\" ? role : \"user\";\n}\n\nexport function createAgentsModelInvoker(): ModelInvoker {\n return {\n async invoke(request: ModelRoundRequest): Promise<ModelRoundResult> {\n const messages = request.history.map((m) => ({\n role: toRole(m.role),\n content: m.content,\n }));\n\n const result = await generateText(messages, { model: request.config.model });\n\n const text = result.text ?? \"\";\n const usage = result.usage;\n return {\n emissions: text.length > 0 ? [{ kind: \"text\", text }] : [],\n usage: {\n inputTokens: usage?.inputTokens ?? 0,\n outputTokens: usage?.outputTokens ?? 0,\n },\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAaA,SAAS,oBAAoB;AAK7B,SAAS,OAAO,MAAoB;AAClC,SAAO,SAAS,eAAe,SAAS,WAAW,OAAO;AAC5D;AAEO,SAAS,2BAAyC;AACvD,SAAO;AAAA,IACL,MAAM,OAAO,SAAuD;AAClE,YAAM,WAAW,QAAQ,QAAQ,IAAI,CAAC,OAAO;AAAA,QAC3C,MAAM,OAAO,EAAE,IAAI;AAAA,QACnB,SAAS,EAAE;AAAA,MACb,EAAE;AAEF,YAAM,SAAS,MAAM,aAAa,UAAU,EAAE,OAAO,QAAQ,OAAO,MAAM,CAAC;AAE3E,YAAM,OAAO,OAAO,QAAQ;AAC5B,YAAM,QAAQ,OAAO;AACrB,aAAO;AAAA,QACL,WAAW,KAAK,SAAS,IAAI,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC;AAAA,QACzD,OAAO;AAAA,UACL,aAAa,OAAO,eAAe;AAAA,UACnC,cAAc,OAAO,gBAAgB;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -2,6 +2,7 @@ import {
2
2
  createMcpClientPort,
3
3
  createMcpServerCatalog
4
4
  } from "../chunk-24ZXP7FI.js";
5
+ import "../chunk-424PT5DM.js";
5
6
  export {
6
7
  createMcpClientPort,
7
8
  createMcpServerCatalog
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  createPrismaRolloutPersistence
3
3
  } from "../chunk-57W3AR43.js";
4
+ import "../chunk-424PT5DM.js";
4
5
  export {
5
6
  createPrismaRolloutPersistence
6
7
  };
@@ -0,0 +1,23 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
18
+
19
+ export {
20
+ __export,
21
+ __reExport
22
+ };
23
+ //# sourceMappingURL=chunk-424PT5DM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,5 +1,5 @@
1
1
  // src/tools.ts
2
- var ToolRegistry = class {
2
+ var RuntimeToolRegistry = class {
3
3
  #tools = /* @__PURE__ */ new Map();
4
4
  #hooks;
5
5
  constructor(hooks = {}) {
@@ -40,7 +40,7 @@ function adaptMcpTool(server, definition, client) {
40
40
  }
41
41
 
42
42
  export {
43
- ToolRegistry,
43
+ RuntimeToolRegistry,
44
44
  adaptMcpTool
45
45
  };
46
- //# sourceMappingURL=chunk-NN7DATXA.js.map
46
+ //# sourceMappingURL=chunk-4Y25ZTKI.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 * runtime registry -> router -> orchestrator pipeline. MCP tools register via\n * {@link adaptMcpTool} and are dispatched identically to native tools.\n */\nexport class RuntimeToolRegistry {\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/** @deprecated Use RuntimeToolRegistry for new code. */\nexport { RuntimeToolRegistry as ToolRegistry };\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,sBAAN,MAA0B;AAAA,EACtB,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;AAmBO,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":[]}
@@ -1,11 +1,11 @@
1
+ import {
2
+ sanitizeForPersist
3
+ } from "./chunk-37BBB2P2.js";
1
4
  import {
2
5
  DENIED,
3
6
  isApproval,
4
7
  resolveRuleDecision
5
8
  } from "./chunk-CGRCUKGT.js";
6
- import {
7
- sanitizeForPersist
8
- } from "./chunk-37BBB2P2.js";
9
9
 
10
10
  // src/loop.ts
11
11
  var DEFAULT_MAX_STEPS = 20;
@@ -118,4 +118,4 @@ async function gateToolCall(name, args, deps, ruleEvaluator) {
118
118
  export {
119
119
  runTurn
120
120
  };
121
- //# sourceMappingURL=chunk-BJBBR3QA.js.map
121
+ //# sourceMappingURL=chunk-D4YAPLOW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loop.ts"],"sourcesContent":["/**\n * Agent loop runner (WRAP — the turn engine).\n *\n * Faithful re-expression of the upstream loop: a turn is\n * `loop { model_call → emit items → execute tools → feed results back }`\n * until the model stops requesting tools or a bounded step ceiling is hit.\n * Single-threaded (Cognition teaching: shared context, no conflicting\n * sub-agent decisions). Every item is appended to the tenant-scoped rollout\n * as it reaches a terminal state, so the turn is resumable by replay.\n *\n * The model call is abstracted behind {@link ModelInvoker} so this WRAPs an\n * existing model stack (e.g. `@nebutra/agents`) rather than re-porting\n * provider/routing/fallback. No untrusted code runs here — command items are\n * dispatched through the tool registry / external-sandbox seam.\n */\n\nimport type { AgentMessageItem, ThreadEvent, ThreadItem, TurnConfig, TurnUsage } from \"./model\";\nimport {\n type ApprovalPolicy,\n DENIED,\n isApproval,\n type ReviewDecision,\n type RuleDecision,\n resolveRuleDecision,\n} from \"./policy\";\nimport type { ServerRequest } from \"./protocol\";\nimport { type RolloutLine, type RolloutStore, sanitizeForPersist } from \"./rollout\";\nimport type { RuntimeToolRegistry, ToolDispatchContext } from \"./tools\";\n\n/** A single thing the model emitted in one round. */\nexport type ModelEmission =\n | { readonly kind: \"text\"; readonly text: string }\n | {\n readonly kind: \"tool_call\";\n readonly id: string;\n readonly name: string;\n readonly args: unknown;\n };\n\nexport interface ModelRoundResult {\n readonly emissions: readonly ModelEmission[];\n readonly usage?: Partial<TurnUsage>;\n}\n\nexport interface ModelRoundRequest {\n readonly config: TurnConfig;\n /** Running transcript: user input, agent text, and tool results. */\n readonly history: readonly { readonly role: string; readonly content: string }[];\n readonly toolNames: readonly string[];\n}\n\n/** Abstracts the model stack — implement over `@nebutra/agents`, etc. */\nexport interface ModelInvoker {\n invoke(request: ModelRoundRequest): Promise<ModelRoundResult>;\n}\n\n/** Server-initiated approval transport (see {@link ServerRequest}). */\nexport interface ApprovalGate {\n request(serverRequest: ServerRequest): Promise<ReviewDecision>;\n}\n\n/** Classifies a tool call into a static rule decision before approval. */\nexport type RuleEvaluator = (toolName: string, args: unknown) => RuleDecision;\n\nexport interface RunTurnDeps {\n readonly tenantId: string;\n readonly threadId: string;\n readonly config: TurnConfig;\n readonly approvalPolicy: ApprovalPolicy;\n readonly model: ModelInvoker;\n readonly tools: RuntimeToolRegistry;\n readonly store: RolloutStore;\n readonly approvalGate: ApprovalGate;\n /** Defaults to: everything requires a prompt (safe). */\n readonly ruleEvaluator?: RuleEvaluator;\n /** Bounded steps (parity with upstream step ceiling). Default 20. */\n readonly maxSteps?: number;\n}\n\nconst DEFAULT_MAX_STEPS = 20;\nconst requirePrompt: RuleEvaluator = () => \"prompt\";\n\nfunction newId(prefix: string): string {\n return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`;\n}\n\n/**\n * Drive one turn to completion. Yields the {@link ThreadEvent} stream and\n * appends each terminal item + the turn outcome to the rollout store.\n * Never throws to the caller — failures surface as a `turn.failed` event.\n */\nexport async function* runTurn(userInput: string, deps: RunTurnDeps): AsyncGenerator<ThreadEvent> {\n const at = () => new Date().toISOString();\n const ruleEvaluator = deps.ruleEvaluator ?? requirePrompt;\n const maxSteps = deps.maxSteps ?? DEFAULT_MAX_STEPS;\n const ctx: ToolDispatchContext = { tenantId: deps.tenantId, threadId: deps.threadId };\n\n const append = async (line: RolloutLine): Promise<void> => deps.store.append(line);\n const event = async (e: ThreadEvent): Promise<ThreadEvent> => {\n await append({\n tenantId: deps.tenantId,\n threadId: deps.threadId,\n type: \"event\",\n event:\n e.type === \"item.completed\"\n ? { type: \"item.completed\", item: sanitizeForPersist(e.item) }\n : e,\n at: at(),\n });\n return e;\n };\n\n yield await event({ type: \"turn.started\" });\n\n const history: { role: string; content: string }[] = [{ role: \"user\", content: userInput }];\n let usage: TurnUsage = {\n inputTokens: 0,\n cachedInputTokens: 0,\n outputTokens: 0,\n reasoningOutputTokens: 0,\n };\n\n try {\n for (let step = 0; step < maxSteps; step++) {\n const round = await deps.model.invoke({\n config: deps.config,\n history,\n toolNames: deps.tools.list().map((t) => t.definition.name),\n });\n if (round.usage) {\n const u = round.usage;\n usage = {\n inputTokens: usage.inputTokens + (u.inputTokens ?? 0),\n cachedInputTokens: usage.cachedInputTokens + (u.cachedInputTokens ?? 0),\n outputTokens: usage.outputTokens + (u.outputTokens ?? 0),\n reasoningOutputTokens: usage.reasoningOutputTokens + (u.reasoningOutputTokens ?? 0),\n };\n }\n\n const toolCalls = round.emissions.filter(\n (e): e is Extract<ModelEmission, { kind: \"tool_call\" }> => e.kind === \"tool_call\",\n );\n\n for (const e of round.emissions) {\n if (e.kind !== \"text\") continue;\n const item: AgentMessageItem = { id: newId(\"msg\"), type: \"agent_message\", text: e.text };\n history.push({ role: \"assistant\", content: e.text });\n yield await event({ type: \"item.completed\", item });\n }\n\n if (toolCalls.length === 0) break; // model is done\n\n for (const call of toolCalls) {\n const decision = await gateToolCall(call.name, call.args, deps, ruleEvaluator);\n if (!isApproval(decision)) {\n const failed: ThreadItem = {\n id: call.id,\n type: \"error\",\n message: `tool '${call.name}' not approved (${decision.kind})`,\n };\n history.push({ role: \"tool\", content: `DENIED: ${call.name}` });\n yield await event({ type: \"item.completed\", item: failed });\n continue;\n }\n try {\n const output = await deps.tools.dispatch(call.name, call.args, ctx);\n const item: ThreadItem = {\n id: call.id,\n type: \"mcp_tool_call\",\n server: \"native\",\n tool: call.name,\n arguments: call.args,\n result: { content: output },\n status: \"completed\",\n };\n history.push({ role: \"tool\", content: JSON.stringify(output) });\n yield await event({ type: \"item.completed\", item });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n history.push({ role: \"tool\", content: `ERROR: ${message}` });\n yield await event({\n type: \"item.completed\",\n item: { id: call.id, type: \"error\", message },\n });\n }\n }\n }\n\n yield await event({ type: \"turn.completed\", usage });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n yield await event({ type: \"turn.failed\", error: { message } });\n }\n}\n\n/** Resolve a tool call's approval, raising a server-initiated request if asked. */\nasync function gateToolCall(\n name: string,\n args: unknown,\n deps: RunTurnDeps,\n ruleEvaluator: RuleEvaluator,\n): Promise<ReviewDecision> {\n const outcome = resolveRuleDecision(ruleEvaluator(name, args), deps.approvalPolicy);\n if (outcome === \"auto_allow\") return { kind: \"approved\" };\n if (outcome === \"auto_reject\") return DENIED;\n return deps.approvalGate.request({\n type: \"permissions.request_approval\",\n requestId: newId(\"appr\"),\n summary: `tool '${name}'`,\n });\n}\n"],"mappings":"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","names":[]}
@@ -3,20 +3,36 @@ import {
3
3
  } from "./chunk-7BUOF367.js";
4
4
 
5
5
  // src/skills.ts
6
+ import { estimateTokens as estimatePrimitiveTokens } from "@nebutra/ai-primitives";
6
7
  import { z } from "zod";
7
8
  var FIRST_PARTY_TIERS = /* @__PURE__ */ new Set(["bundled", "builtin"]);
9
+ var REGEX_META_CHARS = /* @__PURE__ */ new Set([
10
+ ".",
11
+ "+",
12
+ "?",
13
+ "^",
14
+ "$",
15
+ "{",
16
+ "}",
17
+ "(",
18
+ ")",
19
+ "|",
20
+ "[",
21
+ "]",
22
+ "\\"
23
+ ]);
8
24
  var isFirstParty = (s) => FIRST_PARTY_TIERS.has(s.sourceTier);
25
+ var estimateListingTokens = (text) => estimatePrimitiveTokens(text, { correction: 1 });
9
26
  var listingOptionsSchema = z.object({
10
27
  maxListingDescChars: z.number().int().positive().default(250),
11
28
  contextWindowTokens: z.number().int().positive().default(2e5),
12
29
  budgetPercent: z.number().positive().max(1).default(0.01),
13
30
  touchedPaths: z.array(z.string()).default([])
14
31
  });
15
- var estimateTokens = (text) => Math.ceil(text.length / 4);
16
32
  function globToRegExp(glob) {
17
33
  let out = "^";
18
34
  for (let i = 0; i < glob.length; i++) {
19
- const c = glob[i];
35
+ const c = glob.charAt(i);
20
36
  if (c === "*") {
21
37
  if (glob[i + 1] === "*") {
22
38
  out += ".*";
@@ -24,7 +40,7 @@ function globToRegExp(glob) {
24
40
  } else {
25
41
  out += "[^/]*";
26
42
  }
27
- } else if (".+?^${}()|[]\\".includes(c)) {
43
+ } else if (REGEX_META_CHARS.has(c)) {
28
44
  out += `\\${c}`;
29
45
  } else {
30
46
  out += c;
@@ -74,7 +90,7 @@ function buildSkillListing(resolver, ctx, options = {}) {
74
90
  firstParty: true
75
91
  };
76
92
  entries.push(entry);
77
- used += estimateTokens(renderEntry(entry));
93
+ used += estimateListingTokens(renderEntry(entry));
78
94
  continue;
79
95
  }
80
96
  const full = {
@@ -84,7 +100,7 @@ function buildSkillListing(resolver, ctx, options = {}) {
84
100
  degraded: false,
85
101
  firstParty: false
86
102
  };
87
- const fullCost = estimateTokens(renderEntry(full));
103
+ const fullCost = estimateListingTokens(renderEntry(full));
88
104
  if (used + fullCost <= budget) {
89
105
  entries.push(full);
90
106
  used += fullCost;
@@ -97,7 +113,7 @@ function buildSkillListing(resolver, ctx, options = {}) {
97
113
  degraded: true,
98
114
  firstParty: false
99
115
  };
100
- const nameCost = estimateTokens(renderEntry(nameOnly));
116
+ const nameCost = estimateListingTokens(renderEntry(nameOnly));
101
117
  if (used + nameCost <= budget) {
102
118
  entries.push(nameOnly);
103
119
  used += nameCost;
@@ -132,4 +148,4 @@ export {
132
148
  buildSkillListing,
133
149
  expandSkill
134
150
  };
135
- //# sourceMappingURL=chunk-ZMYX5VBU.js.map
151
+ //# sourceMappingURL=chunk-GQZKYWFT.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 { estimateTokens as estimatePrimitiveTokens } from \"@nebutra/ai-primitives\";\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\"]);\nconst REGEX_META_CHARS = new Set([\n \".\",\n \"+\",\n \"?\",\n \"^\",\n \"$\",\n \"{\",\n \"}\",\n \"(\",\n \")\",\n \"|\",\n \"[\",\n \"]\",\n \"\\\\\",\n]);\n\nconst isFirstParty = (s: SkillRecord): boolean => FIRST_PARTY_TIERS.has(s.sourceTier);\n\n/**\n * Listing budgets preserve the catalog degradation contract: very tight lower-\n * tier budgets should still degrade to names-only before dropping. Context\n * compaction uses the corrected 1.3 estimate; this listing surface deliberately\n * uses the shared primitive with no correction.\n */\nconst estimateListingTokens = (text: string): number =>\n estimatePrimitiveTokens(text, { correction: 1 });\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/** 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.charAt(i);\n if (c === \"*\") {\n if (glob[i + 1] === \"*\") {\n out += \".*\";\n i++;\n } else {\n out += \"[^/]*\";\n }\n } else if (REGEX_META_CHARS.has(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 += estimateListingTokens(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 = estimateListingTokens(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 = estimateListingTokens(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,255 @@
1
+ import {
2
+ runTurn
3
+ } from "./chunk-D4YAPLOW.js";
4
+ import {
5
+ replay
6
+ } from "./chunk-37BBB2P2.js";
7
+ import {
8
+ RuntimeToolRegistry
9
+ } from "./chunk-4Y25ZTKI.js";
10
+ import {
11
+ isTurnTerminal
12
+ } from "./chunk-6EGG2OZC.js";
13
+ import {
14
+ DEFAULT_APPROVAL_POLICY
15
+ } from "./chunk-CGRCUKGT.js";
16
+
17
+ // src/pulsar.ts
18
+ function newId(prefix) {
19
+ return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`;
20
+ }
21
+ function requireNonEmpty(value, label) {
22
+ const trimmed = value.trim();
23
+ if (!trimmed) {
24
+ throw new Error(`${label} is required. Suggestion: pass a non-empty ${label}.`);
25
+ }
26
+ return trimmed;
27
+ }
28
+ function recordedEvents(lines) {
29
+ return lines.flatMap((line) => line.type === "event" ? [line.event] : []);
30
+ }
31
+ function mapItemToEvent(item) {
32
+ switch (item.type) {
33
+ case "agent_message":
34
+ case "reasoning":
35
+ return "llm_call";
36
+ case "file_change":
37
+ return "content_write";
38
+ case "command_execution":
39
+ case "mcp_tool_call":
40
+ return "tool_call";
41
+ case "error":
42
+ case "todo_list":
43
+ case "web_search":
44
+ return "tool_call";
45
+ }
46
+ }
47
+ function affectedPaths(item) {
48
+ return item.type === "file_change" ? item.changes.map((change) => change.path) : [];
49
+ }
50
+ var PulsarThread = class {
51
+ id;
52
+ playId;
53
+ tenantId;
54
+ #deps;
55
+ #input;
56
+ #maxSteps;
57
+ #mode;
58
+ #events;
59
+ constructor(input) {
60
+ this.id = input.id;
61
+ this.playId = input.playId;
62
+ this.tenantId = input.tenantId;
63
+ this.#deps = input.deps;
64
+ this.#input = input.userInput;
65
+ this.#mode = input.mode;
66
+ this.#maxSteps = input.maxSteps;
67
+ }
68
+ async *subscribe() {
69
+ if (this.#events) {
70
+ for (const event of this.#events) yield event;
71
+ return;
72
+ }
73
+ const events = [];
74
+ const emit = async (event) => {
75
+ events.push(event);
76
+ if (event.type === "item.completed") {
77
+ await this.#mirrorItem(event.item);
78
+ }
79
+ return event;
80
+ };
81
+ if (this.#mode === "resume") {
82
+ const lines = await this.#deps.store.read(this.tenantId, this.id);
83
+ const projection = replay(lines);
84
+ const recorded = recordedEvents(lines);
85
+ const last = recorded.at(-1);
86
+ if (projection && last && isTurnTerminal(last)) {
87
+ this.#events = recorded;
88
+ for (const event of recorded) yield event;
89
+ return;
90
+ }
91
+ }
92
+ yield await emit({ type: "thread.started", threadId: this.id });
93
+ await this.#deps.store.append({
94
+ tenantId: this.tenantId,
95
+ threadId: this.id,
96
+ type: "event",
97
+ event: { type: "thread.started", threadId: this.id },
98
+ at: (/* @__PURE__ */ new Date()).toISOString()
99
+ });
100
+ const stream = runTurn(this.#input, {
101
+ tenantId: this.tenantId,
102
+ threadId: this.id,
103
+ config: this.#deps.config,
104
+ approvalPolicy: this.#deps.approvalPolicy,
105
+ model: this.#deps.model,
106
+ tools: this.#deps.tools,
107
+ store: this.#deps.store,
108
+ approvalGate: this.#deps.approvalGate,
109
+ ruleEvaluator: () => "allow",
110
+ ...this.#maxSteps !== void 0 ? { maxSteps: this.#maxSteps } : {}
111
+ });
112
+ for await (const event of stream) {
113
+ yield await emit(event);
114
+ }
115
+ this.#events = events;
116
+ }
117
+ async #mirrorItem(item) {
118
+ const eventLog = this.#deps.eventLog;
119
+ if (!eventLog) return;
120
+ const parent = Pulsar.itemCommitIds.get(`${this.id}::__last`) ?? null;
121
+ const commitId = await eventLog.commit({
122
+ traceId: this.id,
123
+ kind: mapItemToEvent(item),
124
+ affected: affectedPaths(item),
125
+ parent
126
+ });
127
+ Pulsar.itemCommitIds.set(`${this.id}:${item.id}`, commitId);
128
+ Pulsar.itemCommitIds.set(`${this.id}::__last`, commitId);
129
+ }
130
+ };
131
+ var PulsarBuilder = class {
132
+ #tenantId;
133
+ #config;
134
+ #model;
135
+ #tools = new RuntimeToolRegistry();
136
+ #store;
137
+ #approvalGate;
138
+ #approvalPolicy = DEFAULT_APPROVAL_POLICY;
139
+ #eventLog;
140
+ withTenant(tenantId) {
141
+ this.#tenantId = requireNonEmpty(tenantId, "tenantId");
142
+ return this;
143
+ }
144
+ withConfig(config) {
145
+ this.#config = config;
146
+ return this;
147
+ }
148
+ withModel(model) {
149
+ this.#model = model;
150
+ return this;
151
+ }
152
+ withTools(tools) {
153
+ this.#tools = tools;
154
+ return this;
155
+ }
156
+ withRolloutStore(store) {
157
+ this.#store = store;
158
+ return this;
159
+ }
160
+ withApprovalGate(approvalGate) {
161
+ this.#approvalGate = approvalGate;
162
+ return this;
163
+ }
164
+ withApprovalPolicy(policy) {
165
+ this.#approvalPolicy = policy;
166
+ return this;
167
+ }
168
+ withEventLog(eventLog) {
169
+ this.#eventLog = eventLog;
170
+ return this;
171
+ }
172
+ build() {
173
+ if (!this.#tenantId) throw new Error("tenantId is required. Suggestion: call withTenant().");
174
+ if (!this.#config) throw new Error("config is required. Suggestion: call withConfig().");
175
+ if (!this.#model) throw new Error("model is required. Suggestion: call withModel().");
176
+ if (!this.#store) {
177
+ throw new Error("rollout store is required. Suggestion: call withRolloutStore().");
178
+ }
179
+ if (!this.#approvalGate) {
180
+ throw new Error("approval gate is required. Suggestion: call withApprovalGate().");
181
+ }
182
+ const deps = {
183
+ tenantId: this.#tenantId,
184
+ config: this.#config,
185
+ model: this.#model,
186
+ tools: this.#tools,
187
+ store: this.#store,
188
+ approvalGate: this.#approvalGate,
189
+ approvalPolicy: this.#approvalPolicy,
190
+ ...this.#eventLog !== void 0 ? { eventLog: this.#eventLog } : {}
191
+ };
192
+ return new Pulsar(deps);
193
+ }
194
+ };
195
+ var Pulsar = class _Pulsar {
196
+ static itemCommitIds = /* @__PURE__ */ new Map();
197
+ #deps;
198
+ constructor(deps) {
199
+ this.#deps = deps;
200
+ }
201
+ static builder() {
202
+ return new PulsarBuilder();
203
+ }
204
+ async startPlay(playId, input, options = {}) {
205
+ const threadId = options.threadId ?? newId("thread");
206
+ await this.#deps.store.append({
207
+ tenantId: this.#deps.tenantId,
208
+ threadId,
209
+ type: "session_meta",
210
+ config: this.#deps.config,
211
+ at: (/* @__PURE__ */ new Date()).toISOString()
212
+ });
213
+ return new PulsarThread({
214
+ id: threadId,
215
+ playId: requireNonEmpty(playId, "playId"),
216
+ tenantId: this.#deps.tenantId,
217
+ deps: this.#deps,
218
+ mode: "start",
219
+ userInput: input,
220
+ ...options.maxSteps !== void 0 ? { maxSteps: options.maxSteps } : {}
221
+ });
222
+ }
223
+ async resume(threadId) {
224
+ return new PulsarThread({
225
+ id: requireNonEmpty(threadId, "threadId"),
226
+ playId: "resume",
227
+ tenantId: this.#deps.tenantId,
228
+ deps: this.#deps,
229
+ mode: "resume",
230
+ userInput: ""
231
+ });
232
+ }
233
+ async branchFromItem(threadId, itemId, name) {
234
+ const eventLog = this.#deps.eventLog;
235
+ if (!eventLog) {
236
+ throw new Error("event-log is required. Suggestion: call withEventLog() before branching.");
237
+ }
238
+ const commitId = _Pulsar.itemCommitIds.get(
239
+ `${requireNonEmpty(threadId, "threadId")}:${requireNonEmpty(itemId, "itemId")}`
240
+ );
241
+ if (!commitId) {
242
+ throw new Error(
243
+ "item has no event-log commit. Suggestion: subscribe to the thread before branching."
244
+ );
245
+ }
246
+ return eventLog.branchFrom(commitId, requireNonEmpty(name, "branch name"));
247
+ }
248
+ };
249
+
250
+ export {
251
+ PulsarThread,
252
+ PulsarBuilder,
253
+ Pulsar
254
+ };
255
+ //# sourceMappingURL=chunk-KCNN4QUQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pulsar.ts"],"sourcesContent":["import { type ApprovalGate, type ModelInvoker, runTurn } from \"./loop\";\nimport type { ThreadEvent, ThreadItem, TurnConfig } from \"./model\";\nimport { isTurnTerminal } from \"./model\";\nimport type { ApprovalPolicy } from \"./policy\";\nimport { DEFAULT_APPROVAL_POLICY } from \"./policy\";\nimport type { RolloutLine, RolloutStore } from \"./rollout\";\nimport { replay } from \"./rollout\";\nimport { RuntimeToolRegistry } from \"./tools\";\n\ntype EventLogKind = \"llm_call\" | \"tool_call\" | \"sub_agent_call\" | \"content_write\";\n\nexport interface PulsarEventInput {\n readonly traceId: string;\n readonly kind: EventLogKind;\n readonly affected: readonly string[];\n readonly parent: string | null;\n readonly snapshot?: Record<string, string>;\n}\n\nexport interface PulsarBranchRecord {\n readonly name: string;\n readonly from: string;\n readonly at: string;\n}\n\nexport interface PulsarEventLogPort {\n commit(event: PulsarEventInput): Promise<string>;\n branchFrom(id: string, name: string): Promise<PulsarBranchRecord>;\n}\n\nexport interface StartThreadOptions {\n readonly threadId?: string;\n readonly maxSteps?: number;\n}\n\nexport interface PulsarDeps {\n readonly tenantId: string;\n readonly config: TurnConfig;\n readonly model: ModelInvoker;\n readonly tools: RuntimeToolRegistry;\n readonly store: RolloutStore;\n readonly approvalGate: ApprovalGate;\n readonly approvalPolicy: ApprovalPolicy;\n readonly eventLog?: PulsarEventLogPort;\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`;\n}\n\nfunction requireNonEmpty(value: string, label: string): string {\n const trimmed = value.trim();\n if (!trimmed) {\n throw new Error(`${label} is required. Suggestion: pass a non-empty ${label}.`);\n }\n return trimmed;\n}\n\nfunction recordedEvents(lines: readonly RolloutLine[]): ThreadEvent[] {\n return lines.flatMap((line) => (line.type === \"event\" ? [line.event] : []));\n}\n\nfunction mapItemToEvent(item: ThreadItem): PulsarEventInput[\"kind\"] {\n switch (item.type) {\n case \"agent_message\":\n case \"reasoning\":\n return \"llm_call\";\n case \"file_change\":\n return \"content_write\";\n case \"command_execution\":\n case \"mcp_tool_call\":\n return \"tool_call\";\n case \"error\":\n case \"todo_list\":\n case \"web_search\":\n return \"tool_call\";\n }\n}\n\nfunction affectedPaths(item: ThreadItem): readonly string[] {\n return item.type === \"file_change\" ? item.changes.map((change) => change.path) : [];\n}\n\nexport class PulsarThread {\n readonly id: string;\n readonly playId: string;\n readonly tenantId: string;\n readonly #deps: PulsarDeps;\n readonly #input: string;\n readonly #maxSteps: number | undefined;\n readonly #mode: \"start\" | \"resume\";\n #events: ThreadEvent[] | undefined;\n\n constructor(input: {\n readonly id: string;\n readonly playId: string;\n readonly tenantId: string;\n readonly deps: PulsarDeps;\n readonly mode: \"start\" | \"resume\";\n readonly userInput: string;\n readonly maxSteps?: number;\n }) {\n this.id = input.id;\n this.playId = input.playId;\n this.tenantId = input.tenantId;\n this.#deps = input.deps;\n this.#input = input.userInput;\n this.#mode = input.mode;\n this.#maxSteps = input.maxSteps;\n }\n\n async *subscribe(): AsyncGenerator<ThreadEvent> {\n if (this.#events) {\n for (const event of this.#events) yield event;\n return;\n }\n\n const events: ThreadEvent[] = [];\n const emit = async (event: ThreadEvent): Promise<ThreadEvent> => {\n events.push(event);\n if (event.type === \"item.completed\") {\n await this.#mirrorItem(event.item);\n }\n return event;\n };\n\n if (this.#mode === \"resume\") {\n const lines = await this.#deps.store.read(this.tenantId, this.id);\n const projection = replay(lines);\n const recorded = recordedEvents(lines);\n const last = recorded.at(-1);\n if (projection && last && isTurnTerminal(last)) {\n this.#events = recorded;\n for (const event of recorded) yield event;\n return;\n }\n }\n\n yield await emit({ type: \"thread.started\", threadId: this.id });\n await this.#deps.store.append({\n tenantId: this.tenantId,\n threadId: this.id,\n type: \"event\",\n event: { type: \"thread.started\", threadId: this.id },\n at: new Date().toISOString(),\n });\n\n const stream = runTurn(this.#input, {\n tenantId: this.tenantId,\n threadId: this.id,\n config: this.#deps.config,\n approvalPolicy: this.#deps.approvalPolicy,\n model: this.#deps.model,\n tools: this.#deps.tools,\n store: this.#deps.store,\n approvalGate: this.#deps.approvalGate,\n ruleEvaluator: () => \"allow\",\n ...(this.#maxSteps !== undefined ? { maxSteps: this.#maxSteps } : {}),\n });\n\n for await (const event of stream) {\n yield await emit(event);\n }\n this.#events = events;\n }\n\n async #mirrorItem(item: ThreadItem): Promise<void> {\n const eventLog = this.#deps.eventLog;\n if (!eventLog) return;\n const parent = Pulsar.itemCommitIds.get(`${this.id}::__last`) ?? null;\n const commitId = await eventLog.commit({\n traceId: this.id,\n kind: mapItemToEvent(item),\n affected: affectedPaths(item),\n parent,\n });\n Pulsar.itemCommitIds.set(`${this.id}:${item.id}`, commitId);\n Pulsar.itemCommitIds.set(`${this.id}::__last`, commitId);\n }\n}\n\nexport class PulsarBuilder {\n #tenantId?: string;\n #config?: TurnConfig;\n #model?: ModelInvoker;\n #tools: RuntimeToolRegistry = new RuntimeToolRegistry();\n #store?: RolloutStore;\n #approvalGate?: ApprovalGate;\n #approvalPolicy: ApprovalPolicy = DEFAULT_APPROVAL_POLICY;\n #eventLog?: PulsarEventLogPort;\n\n withTenant(tenantId: string): this {\n this.#tenantId = requireNonEmpty(tenantId, \"tenantId\");\n return this;\n }\n\n withConfig(config: TurnConfig): this {\n this.#config = config;\n return this;\n }\n\n withModel(model: ModelInvoker): this {\n this.#model = model;\n return this;\n }\n\n withTools(tools: RuntimeToolRegistry): this {\n this.#tools = tools;\n return this;\n }\n\n withRolloutStore(store: RolloutStore): this {\n this.#store = store;\n return this;\n }\n\n withApprovalGate(approvalGate: ApprovalGate): this {\n this.#approvalGate = approvalGate;\n return this;\n }\n\n withApprovalPolicy(policy: ApprovalPolicy): this {\n this.#approvalPolicy = policy;\n return this;\n }\n\n withEventLog(eventLog: PulsarEventLogPort): this {\n this.#eventLog = eventLog;\n return this;\n }\n\n build(): Pulsar {\n if (!this.#tenantId) throw new Error(\"tenantId is required. Suggestion: call withTenant().\");\n if (!this.#config) throw new Error(\"config is required. Suggestion: call withConfig().\");\n if (!this.#model) throw new Error(\"model is required. Suggestion: call withModel().\");\n if (!this.#store) {\n throw new Error(\"rollout store is required. Suggestion: call withRolloutStore().\");\n }\n if (!this.#approvalGate) {\n throw new Error(\"approval gate is required. Suggestion: call withApprovalGate().\");\n }\n const deps: PulsarDeps = {\n tenantId: this.#tenantId,\n config: this.#config,\n model: this.#model,\n tools: this.#tools,\n store: this.#store,\n approvalGate: this.#approvalGate,\n approvalPolicy: this.#approvalPolicy,\n ...(this.#eventLog !== undefined ? { eventLog: this.#eventLog } : {}),\n };\n return new Pulsar(deps);\n }\n}\n\nexport class Pulsar {\n static readonly itemCommitIds = new Map<string, string>();\n readonly #deps: PulsarDeps;\n\n constructor(deps: PulsarDeps) {\n this.#deps = deps;\n }\n\n static builder(): PulsarBuilder {\n return new PulsarBuilder();\n }\n\n async startPlay(\n playId: string,\n input: string,\n options: StartThreadOptions = {},\n ): Promise<PulsarThread> {\n const threadId = options.threadId ?? newId(\"thread\");\n await this.#deps.store.append({\n tenantId: this.#deps.tenantId,\n threadId,\n type: \"session_meta\",\n config: this.#deps.config,\n at: new Date().toISOString(),\n });\n return new PulsarThread({\n id: threadId,\n playId: requireNonEmpty(playId, \"playId\"),\n tenantId: this.#deps.tenantId,\n deps: this.#deps,\n mode: \"start\",\n userInput: input,\n ...(options.maxSteps !== undefined ? { maxSteps: options.maxSteps } : {}),\n });\n }\n\n async resume(threadId: string): Promise<PulsarThread> {\n return new PulsarThread({\n id: requireNonEmpty(threadId, \"threadId\"),\n playId: \"resume\",\n tenantId: this.#deps.tenantId,\n deps: this.#deps,\n mode: \"resume\",\n userInput: \"\",\n });\n }\n\n async branchFromItem(\n threadId: string,\n itemId: string,\n name: string,\n ): Promise<PulsarBranchRecord> {\n const eventLog = this.#deps.eventLog;\n if (!eventLog) {\n throw new Error(\"event-log is required. Suggestion: call withEventLog() before branching.\");\n }\n const commitId = Pulsar.itemCommitIds.get(\n `${requireNonEmpty(threadId, \"threadId\")}:${requireNonEmpty(itemId, \"itemId\")}`,\n );\n if (!commitId) {\n throw new Error(\n \"item has no event-log commit. Suggestion: subscribe to the thread before branching.\",\n );\n }\n return eventLog.branchFrom(commitId, requireNonEmpty(name, \"branch name\"));\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  adaptMcpTool
3
- } from "./chunk-NN7DATXA.js";
3
+ } from "./chunk-4Y25ZTKI.js";
4
4
 
5
5
  // src/mcp-bridge.ts
6
6
  import { z } from "zod";
@@ -30,4 +30,4 @@ async function activateMcpTools(registry, catalog, client, ctx) {
30
30
  export {
31
31
  activateMcpTools
32
32
  };
33
- //# sourceMappingURL=chunk-PGGWSUTM.js.map
33
+ //# sourceMappingURL=chunk-NI4EDT4T.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 {\n adaptMcpTool,\n type McpClientLike,\n type RuntimeToolRegistry,\n type ToolDefinition,\n} 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 RuntimeToolRegistry}. 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: RuntimeToolRegistry,\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;AAqBlB,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":[]}