@tangle-network/agent-runtime 0.138.0 → 0.139.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 (119) hide show
  1. package/dist/{activation-BHoMDzMv.js → activation-eLJ6uRlP.js} +3 -3
  2. package/dist/{activation-BHoMDzMv.js.map → activation-eLJ6uRlP.js.map} +1 -1
  3. package/dist/agent.d.ts +4 -4
  4. package/dist/agent.js +3 -3
  5. package/dist/{analyst-loop-Dk6_xfhI.js → analyst-loop-DtOEXGzR.js} +3 -3
  6. package/dist/{analyst-loop-Dk6_xfhI.js.map → analyst-loop-DtOEXGzR.js.map} +1 -1
  7. package/dist/analyst-loop.d.ts +1 -1
  8. package/dist/analyst-loop.js +1 -1
  9. package/dist/{authoring-rEOOEWn6.js → authoring-J93uFAwd.js} +3 -3
  10. package/dist/{authoring-rEOOEWn6.js.map → authoring-J93uFAwd.js.map} +1 -1
  11. package/dist/candidate-execution/index.d.ts +3 -3
  12. package/dist/candidate-execution/index.js +4 -4
  13. package/dist/{candidate-execution-CyCLYLfK.js → candidate-execution-uuUeFSc6.js} +5 -5
  14. package/dist/{candidate-execution-CyCLYLfK.js.map → candidate-execution-uuUeFSc6.js.map} +1 -1
  15. package/dist/{conversation-DIUtiLkC.js → conversation-C3eZCvLd.js} +8 -6
  16. package/dist/conversation-C3eZCvLd.js.map +1 -0
  17. package/dist/conversation.d.ts +1 -1
  18. package/dist/conversation.js +1 -1
  19. package/dist/durable.d.ts +34 -1
  20. package/dist/durable.js +82 -1
  21. package/dist/durable.js.map +1 -1
  22. package/dist/{environment-provider-CLNVqp6H.js → environment-provider-BAO8wt1_.js} +147 -48
  23. package/dist/environment-provider-BAO8wt1_.js.map +1 -0
  24. package/dist/{environment-provider-DA8LqBV-.d.ts → environment-provider-Bu3S3v78.d.ts} +3 -3
  25. package/dist/environment-provider.d.ts +1 -1
  26. package/dist/environment-provider.js +1 -1
  27. package/dist/{errors-CVIDr7GQ.js → errors-CDZ8XsVj.js} +34 -9
  28. package/dist/errors-CDZ8XsVj.js.map +1 -0
  29. package/dist/{graph-yaEG0ZiR.js → graph-BbMjq7-x.js} +5 -5
  30. package/dist/{graph-yaEG0ZiR.js.map → graph-BbMjq7-x.js.map} +1 -1
  31. package/dist/{improvement-cycle-BeVj-EGJ.js → improvement-cycle-D6rhMJR0.js} +67 -66
  32. package/dist/improvement-cycle-D6rhMJR0.js.map +1 -0
  33. package/dist/{index-Bdgpx2Dx.d.ts → index-C6yfz0QN.d.ts} +5 -5
  34. package/dist/{index-BOgzou1U.d.ts → index-COnuT0h7.d.ts} +263 -82
  35. package/dist/{index-CETQJlqT.d.ts → index-CTz17pH6.d.ts} +11 -11
  36. package/dist/{index-DQxQgKDs.d.ts → index-DQv7iCeE.d.ts} +6 -6
  37. package/dist/index.d.ts +85 -15
  38. package/dist/index.js +109 -18
  39. package/dist/index.js.map +1 -1
  40. package/dist/intelligence.d.ts +736 -681
  41. package/dist/intelligence.js +113 -8
  42. package/dist/intelligence.js.map +1 -1
  43. package/dist/jsonl-file-DtxhdAkZ.js +57 -0
  44. package/dist/jsonl-file-DtxhdAkZ.js.map +1 -0
  45. package/dist/kernel.d.ts +7 -7
  46. package/dist/kernel.js +12 -12
  47. package/dist/{knowledge-C1TcOBf6.js → knowledge-CD-_FbJT.js} +7 -7
  48. package/dist/{knowledge-C1TcOBf6.js.map → knowledge-CD-_FbJT.js.map} +1 -1
  49. package/dist/knowledge.d.ts +1 -1
  50. package/dist/knowledge.js +1 -1
  51. package/dist/{loop-runner-bin-DhUWzpNp.js → loop-runner-bin-BBz_ACfQ.js} +5 -5
  52. package/dist/{loop-runner-bin-DhUWzpNp.js.map → loop-runner-bin-BBz_ACfQ.js.map} +1 -1
  53. package/dist/{loop-runner-bin-OH61zEVw.d.ts → loop-runner-bin-DUNxyuaG.d.ts} +3 -3
  54. package/dist/loop-runner-bin.d.ts +1 -1
  55. package/dist/loop-runner-bin.js +1 -1
  56. package/dist/{materialization-BP5zspr2.js → materialization-CrdcfWXp.js} +4 -4
  57. package/dist/{materialization-BP5zspr2.js.map → materialization-CrdcfWXp.js.map} +1 -1
  58. package/dist/mcp/bin.js +4 -4
  59. package/dist/mcp/index.d.ts +5 -5
  60. package/dist/mcp/index.js +8 -8
  61. package/dist/mcp/memory-bin.js +1 -1
  62. package/dist/{memory-server-Cdpy7Uu8.js → memory-server-DsKOhzGf.js} +3 -3
  63. package/dist/{memory-server-Cdpy7Uu8.js.map → memory-server-DsKOhzGf.js.map} +1 -1
  64. package/dist/{model-policy-UTTc9Z-A.js → model-policy-DgRJIJse.js} +2 -2
  65. package/dist/{model-policy-UTTc9Z-A.js.map → model-policy-DgRJIJse.js.map} +1 -1
  66. package/dist/{openai-tools-K38wcStR.js → openai-tools-DIKr72_l.js} +2 -2
  67. package/dist/{openai-tools-K38wcStR.js.map → openai-tools-DIKr72_l.js.map} +1 -1
  68. package/dist/{openai-tools-DU3TZvFY.d.ts → openai-tools-yoRRzAUW.d.ts} +2 -2
  69. package/dist/{prepare-Czki1LBy.js → prepare-DK1qnk7-.js} +3 -3
  70. package/dist/{prepare-Czki1LBy.js.map → prepare-DK1qnk7-.js.map} +1 -1
  71. package/dist/primeintellect/index.d.ts +1 -1
  72. package/dist/profiles.d.ts +1 -1
  73. package/dist/profiles.js +1 -1
  74. package/dist/{protected-model-port-DEsTWOFs.d.ts → protected-model-port-B8ssMyUg.d.ts} +7 -7
  75. package/dist/{protected-model-port-OhS0l_6m.js → protected-model-port-DHeCm_UF.js} +2 -2
  76. package/dist/{protected-model-port-OhS0l_6m.js.map → protected-model-port-DHeCm_UF.js.map} +1 -1
  77. package/dist/{redact-BwEdhuVH.d.ts → redact-CLw2B7Qj.d.ts} +9 -9
  78. package/dist/{researcher-DBfKdnAz.js → researcher-CiSY6Wzb.js} +3 -3
  79. package/dist/{researcher-DBfKdnAz.js.map → researcher-CiSY6Wzb.js.map} +1 -1
  80. package/dist/retained-run-binding-Dp2e8xne.js +268 -0
  81. package/dist/retained-run-binding-Dp2e8xne.js.map +1 -0
  82. package/dist/{runtime-DhPCNvuH.d.ts → runtime-CVNWEw16.d.ts} +4 -4
  83. package/dist/{runtime-yCuqlhou.js → runtime-tTA2iCZQ.js} +650 -222
  84. package/dist/runtime-tTA2iCZQ.js.map +1 -0
  85. package/dist/{sandbox-events-CA2amHoA.js → sandbox-events-DPmXlcKw.js} +24 -2
  86. package/dist/sandbox-events-DPmXlcKw.js.map +1 -0
  87. package/dist/{snapshot-BOIprwBk.js → snapshot-cK9K420n.js} +2 -2
  88. package/dist/{snapshot-BOIprwBk.js.map → snapshot-cK9K420n.js.map} +1 -1
  89. package/dist/{spawn-journal-ZKEKel8N.js → spawn-journal-DGXei3xG.js} +5 -58
  90. package/dist/spawn-journal-DGXei3xG.js.map +1 -0
  91. package/dist/{stream-agent-turn-Q5ellSUu.js → stream-agent-turn-4kUH8Nnn.js} +77 -34
  92. package/dist/stream-agent-turn-4kUH8Nnn.js.map +1 -0
  93. package/dist/{stream-agent-turn-DhPlvHwp.d.ts → stream-agent-turn-RjrKGk6S.d.ts} +5 -8
  94. package/dist/{structural-rollout-2Z6J5ttt.js → structural-rollout-7LP9Ex0F.js} +9 -9
  95. package/dist/structural-rollout-7LP9Ex0F.js.map +1 -0
  96. package/dist/{supervise-to2ynnCG.js → supervise-Cx1QY_hj.js} +10 -9
  97. package/dist/{supervise-to2ynnCG.js.map → supervise-Cx1QY_hj.js.map} +1 -1
  98. package/dist/{supervisor-BfJNS2Z3.js → supervisor-BV72aDmP.js} +13 -9
  99. package/dist/supervisor-BV72aDmP.js.map +1 -0
  100. package/dist/testing.d.ts +2 -2
  101. package/dist/testing.js +12 -12
  102. package/dist/{tool-server-Dh-NcbHr.js → tool-server-DEmLr9YY.js} +2 -2
  103. package/dist/{tool-server-Dh-NcbHr.js.map → tool-server-DEmLr9YY.js.map} +1 -1
  104. package/dist/{types-CxU37Uzr.d.ts → types-19ZOddS3.d.ts} +2 -2
  105. package/dist/{types-BBwGSiAj.d.ts → types-C244Huc9.d.ts} +16 -7
  106. package/dist/{types-BCkweg3w.d.ts → types-D4arYjUl.d.ts} +6 -6
  107. package/dist/{workspace-archive-B_JDld25.js → workspace-archive-HYlsPhfm.js} +2 -2
  108. package/dist/{workspace-archive-B_JDld25.js.map → workspace-archive-HYlsPhfm.js.map} +1 -1
  109. package/package.json +4 -4
  110. package/dist/conversation-DIUtiLkC.js.map +0 -1
  111. package/dist/environment-provider-CLNVqp6H.js.map +0 -1
  112. package/dist/errors-CVIDr7GQ.js.map +0 -1
  113. package/dist/improvement-cycle-BeVj-EGJ.js.map +0 -1
  114. package/dist/runtime-yCuqlhou.js.map +0 -1
  115. package/dist/sandbox-events-CA2amHoA.js.map +0 -1
  116. package/dist/spawn-journal-ZKEKel8N.js.map +0 -1
  117. package/dist/stream-agent-turn-Q5ellSUu.js.map +0 -1
  118. package/dist/structural-rollout-2Z6J5ttt.js.map +0 -1
  119. package/dist/supervisor-BfJNS2Z3.js.map +0 -1
package/dist/durable.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { x as NodeId } from "./types-D4arYjUl.js";
1
2
  //#region src/durable/chat-engine.d.ts
2
3
  /**
3
4
  * `handleChatTurn` is a framework-neutral chat-turn HTTP orchestrator.
@@ -130,5 +131,37 @@ declare function deriveExecutionId(input: {
130
131
  turnIndex: number;
131
132
  }): string;
132
133
  //#endregion
133
- export { type ChatStreamEvent, type ChatTurnHooks, type ChatTurnIdentity, type ChatTurnProducer, type ChatTurnResult, type RunChatTurnInput, deriveExecutionId, handleChatTurn };
134
+ //#region src/durable/supervision-discovery.d.ts
135
+ interface DurableCoordinationStreamIdentity {
136
+ readonly runId: string;
137
+ /** Exact owner ids present in the side-log, sorted for deterministic display. */
138
+ readonly ownerIds: readonly string[];
139
+ /** Records written before owner-scoped coordination identities were introduced. */
140
+ readonly unscopedRecords: number;
141
+ readonly recordCount: number;
142
+ }
143
+ /**
144
+ * Identities discoverable from one `supervise({ runDir })` directory without
145
+ * already knowing the root node or coordination run id stored inside it.
146
+ */
147
+ interface DurableSupervisionDiscovery {
148
+ readonly runDir: string;
149
+ readonly spawnJournalPath: string;
150
+ readonly coordinationLogPath: string;
151
+ readonly roots: readonly NodeId[];
152
+ readonly coordinationStreams: readonly DurableCoordinationStreamIdentity[];
153
+ }
154
+ /**
155
+ * Discover the stable identities recorded by Runtime's durable supervision
156
+ * files. This is the developer-facing first step before calling
157
+ * `FileSpawnJournal.loadTree(root)`, `loadSpawnForest(journal, root)`, or
158
+ * `FileCoordinationLog.load(runId, ownerId)`.
159
+ *
160
+ * Missing files produce empty collections. A malformed committed JSONL record
161
+ * still fails loud through the same parser used by the runtime; a torn final
162
+ * append is ignored because it was never acknowledged as committed.
163
+ */
164
+ declare function discoverDurableSupervisionRun(runDir: string): Promise<DurableSupervisionDiscovery>;
165
+ //#endregion
166
+ export { type ChatStreamEvent, type ChatTurnHooks, type ChatTurnIdentity, type ChatTurnProducer, type ChatTurnResult, type DurableCoordinationStreamIdentity, type DurableSupervisionDiscovery, type RunChatTurnInput, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn };
134
167
  //# sourceMappingURL=durable.d.ts.map
package/dist/durable.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { t as parseCommittedJsonLines } from "./jsonl-file-DtxhdAkZ.js";
2
+ import { resolve } from "node:path";
1
3
  //#region src/durable/chat-engine.ts
2
4
  const encoder = new TextEncoder();
3
5
  function encodeLine(event) {
@@ -119,6 +121,85 @@ function deriveExecutionId(input) {
119
121
  return executionId;
120
122
  }
121
123
  //#endregion
122
- export { deriveExecutionId, handleChatTurn };
124
+ //#region src/durable/supervision-discovery.ts
125
+ /**
126
+ * Discover the stable identities recorded by Runtime's durable supervision
127
+ * files. This is the developer-facing first step before calling
128
+ * `FileSpawnJournal.loadTree(root)`, `loadSpawnForest(journal, root)`, or
129
+ * `FileCoordinationLog.load(runId, ownerId)`.
130
+ *
131
+ * Missing files produce empty collections. A malformed committed JSONL record
132
+ * still fails loud through the same parser used by the runtime; a torn final
133
+ * append is ignored because it was never acknowledged as committed.
134
+ */
135
+ async function discoverDurableSupervisionRun(runDir) {
136
+ if (typeof runDir !== "string" || runDir.trim().length === 0) throw new TypeError("discoverDurableSupervisionRun: runDir must be a non-empty string");
137
+ const canonicalRunDir = resolve(runDir);
138
+ const spawnJournalPath = `${canonicalRunDir}/spawn-journal.jsonl`;
139
+ const coordinationLogPath = `${canonicalRunDir}/coordination-log.jsonl`;
140
+ const [spawnText, coordinationText] = await Promise.all([readOptionalText(spawnJournalPath), readOptionalText(coordinationLogPath)]);
141
+ const allRoots = /* @__PURE__ */ new Set();
142
+ const nestedRoots = /* @__PURE__ */ new Set();
143
+ if (spawnText !== void 0) for (const record of parseCommittedJsonLines(spawnText, spawnJournalPath)) {
144
+ if (record.kind === "begin") {
145
+ if (typeof record.root !== "string" || record.root.length === 0) throw new Error(`${spawnJournalPath}: begin record has no non-empty string root identity`);
146
+ allRoots.add(record.root);
147
+ continue;
148
+ }
149
+ if (record.kind !== "event") continue;
150
+ const event = record.event;
151
+ if (!isRecord(event) || event.kind !== "spawned") continue;
152
+ if (!Object.hasOwn(event, "ownedTreeRoot")) continue;
153
+ if (typeof event.ownedTreeRoot !== "string" || event.ownedTreeRoot.length === 0) throw new Error(`${spawnJournalPath}: spawned event ownedTreeRoot must be a non-empty string when present`);
154
+ nestedRoots.add(event.ownedTreeRoot);
155
+ }
156
+ const streams = /* @__PURE__ */ new Map();
157
+ if (coordinationText !== void 0) for (const record of parseCommittedJsonLines(coordinationText, coordinationLogPath)) {
158
+ if (typeof record.runId !== "string" || record.runId.length === 0) throw new Error(`${coordinationLogPath}: record has no non-empty string runId identity`);
159
+ const stream = streams.get(record.runId) ?? {
160
+ owners: /* @__PURE__ */ new Set(),
161
+ unscopedRecords: 0,
162
+ recordCount: 0
163
+ };
164
+ stream.recordCount += 1;
165
+ if (record.ownerId === void 0) stream.unscopedRecords += 1;
166
+ else if (typeof record.ownerId === "string" && record.ownerId.length > 0) stream.owners.add(record.ownerId);
167
+ else throw new Error(`${coordinationLogPath}: ownerId must be a non-empty string when present`);
168
+ streams.set(record.runId, stream);
169
+ }
170
+ const coordinationStreams = [...streams.entries()].sort(([left], [right]) => compareText(left, right)).map(([runId, stream]) => Object.freeze({
171
+ runId,
172
+ ownerIds: Object.freeze([...stream.owners].sort(compareText)),
173
+ unscopedRecords: stream.unscopedRecords,
174
+ recordCount: stream.recordCount
175
+ }));
176
+ return Object.freeze({
177
+ runDir: canonicalRunDir,
178
+ spawnJournalPath,
179
+ coordinationLogPath,
180
+ roots: Object.freeze([...allRoots].filter((root) => !nestedRoots.has(root)).sort(compareText)),
181
+ coordinationStreams: Object.freeze(coordinationStreams)
182
+ });
183
+ }
184
+ async function readOptionalText(path) {
185
+ const fs = await import("node:fs/promises");
186
+ try {
187
+ return await fs.readFile(path, "utf8");
188
+ } catch (error) {
189
+ if (isNoEntError(error)) return void 0;
190
+ throw error;
191
+ }
192
+ }
193
+ function isRecord(value) {
194
+ return typeof value === "object" && value !== null && !Array.isArray(value);
195
+ }
196
+ function compareText(left, right) {
197
+ return left < right ? -1 : left > right ? 1 : 0;
198
+ }
199
+ function isNoEntError(error) {
200
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
201
+ }
202
+ //#endregion
203
+ export { deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn };
123
204
 
124
205
  //# sourceMappingURL=durable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"durable.js","names":[],"sources":["../src/durable/chat-engine.ts","../src/durable/execution-handle.ts"],"sourcesContent":["/**\n * `handleChatTurn` is a framework-neutral chat-turn HTTP orchestrator.\n * Owns the NDJSON `ChatStreamEvent` line protocol, the `session.run.*`\n * lifecycle vocabulary, and the persist / post-process / trace-flush\n * hook order. Returns a `ReadableStream` body the product hands to its\n * platform `Response`.\n *\n * Sandbox owns long-running execution, reconnect, and replay.\n * The producer this engine wraps already speaks that protocol; this\n * engine frames the events and orders product callbacks.\n *\n * Hooks (`ChatTurnHooks`):\n * - `produce`: build the backend event stream\n * - `persistAssistantMessage`: write the assistant turn to the product DB\n * - `onTurnComplete?`: post-process proposals, citations, and similar work\n * - `onEvent?`: send each event to another consumer\n * - `transformFinalText?`: transform text before persistence\n * - `traceFlush?`: let `waitUntil` keep the worker alive for export\n *\n * Framework neutrality: takes already-resolved values (`identity` tuple,\n * a `waitUntil`), never a `Request` or a `Context`. The product's thin\n * route adapter does auth + parse + access-control, then calls\n * `handleChatTurn(...)` and returns `result.body` as its platform `Response`.\n */\n\n/** The NDJSON line protocol every product chat client already speaks. */\nexport interface ChatStreamEvent {\n type: string\n data?: Record<string, unknown>\n}\n\n/** Identity of a chat turn. `tenantId` is the workspace id for workspace-\n * scoped products and the user id for session-scoped products. */\nexport interface ChatTurnIdentity {\n tenantId: string\n /** Thread / session id. */\n sessionId: string\n userId: string\n /** Monotonic 0-based turn index within the session. */\n turnIndex: number\n}\n\n/** The live side of a turn returned by the product's `produce` hook. */\nexport interface ChatTurnProducer<TEvent extends ChatStreamEvent = ChatStreamEvent> {\n /** The turn's event stream. Forwarded verbatim to the caller. */\n stream: AsyncGenerator<TEvent, void, unknown>\n /** The turn's final assistant text. Read once, after `stream` drains. */\n finalText(): string\n}\n\n/** Product callbacks invoked while one chat turn runs. */\nexport interface ChatTurnHooks {\n /** Build the backend stream. The engine forwards events verbatim and\n * reads `finalText()` once the stream drains. */\n produce(): ChatTurnProducer\n /** Persist the assistant message to the product's own store. Called\n * once, after drain, with the assembled (transform-applied) text. */\n persistAssistantMessage(input: { identity: ChatTurnIdentity; finalText: string }): Promise<void>\n /** Optional post-processing for proposals, citations, or credit metering.\n * Errors are logged without failing a turn that already streamed. */\n onTurnComplete?(input: { identity: ChatTurnIdentity; finalText: string }): Promise<void>\n /** Optional per-event side channel, such as a Durable Object broadcast.\n * Runs for every emitted event, including the lifecycle envelope.\n * Errors are logged without breaking the chat stream. */\n onEvent?(event: ChatStreamEvent): void | Promise<void>\n /** Optional pre-persist transform of the final text (e.g. PII\n * redaction). Affects only what is persisted; the live stream is\n * never altered. */\n transformFinalText?(text: string): string | Promise<string>\n /** Optional trace flush. Handed to `waitUntil` so the worker stays alive\n * until export completes. */\n traceFlush?(): Promise<void>\n}\n\n/** Inputs for one streamed product chat turn. */\nexport interface RunChatTurnInput {\n identity: ChatTurnIdentity\n hooks: ChatTurnHooks\n /** Worker liveness hook. When omitted, trace flush is awaited inline\n * before the stream closes. */\n waitUntil?: (p: Promise<unknown>) => void\n /** Structured logger for swallowed hook errors. Defaults to\n * `console.error` so failures surface without product wiring. */\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** HTTP response values returned for one chat turn. */\nexport interface ChatTurnResult {\n /** NDJSON body to return as the platform `Response` body. */\n body: ReadableStream<Uint8Array>\n /** Content type for the response. */\n contentType: 'application/x-ndjson'\n}\n\nconst encoder = new TextEncoder()\n\nfunction encodeLine(event: ChatStreamEvent): Uint8Array {\n return encoder.encode(`${JSON.stringify(event)}\\n`)\n}\n\nfunction defaultLog(message: string, meta?: Record<string, unknown>): void {\n if (meta) console.error(message, meta)\n else console.error(message)\n}\n\n/**\n * Run one chat turn. Returns immediately with a `ReadableStream` body;\n * execution starts while the stream is constructed. Backend\n * failures surface as `error` + `session.run.failed` events.\n */\nexport function handleChatTurn(input: RunChatTurnInput): ChatTurnResult {\n const log = input.log ?? defaultLog\n const { identity, hooks } = input\n\n const body = new ReadableStream<Uint8Array>({\n start: async (controller) => {\n const emit = async (event: ChatStreamEvent): Promise<void> => {\n controller.enqueue(encodeLine(event))\n if (hooks.onEvent) {\n try {\n await hooks.onEvent(event)\n } catch (err) {\n log('[chat-engine] onEvent hook threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n }\n\n try {\n await emit({\n type: 'session.run.started',\n data: {\n sessionId: identity.sessionId,\n tenantId: identity.tenantId,\n turnIndex: identity.turnIndex,\n },\n })\n\n const producer = hooks.produce()\n for await (const event of producer.stream) {\n await emit(event)\n }\n const rawFinal = producer.finalText()\n const finalText = hooks.transformFinalText\n ? await hooks.transformFinalText(rawFinal)\n : rawFinal\n\n await hooks.persistAssistantMessage({ identity, finalText })\n if (hooks.onTurnComplete) {\n try {\n await hooks.onTurnComplete({ identity, finalText })\n } catch (err) {\n log('[chat-engine] onTurnComplete threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n await emit({\n type: 'session.run.completed',\n data: { sessionId: identity.sessionId },\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n log('[chat-engine] turn failed', { error: message })\n await emit({ type: 'error', data: { message } })\n await emit({\n type: 'session.run.failed',\n data: { sessionId: identity.sessionId, message },\n })\n } finally {\n if (hooks.traceFlush) {\n const flush = hooks.traceFlush().catch((err) =>\n log('[chat-engine] traceFlush threw', {\n error: err instanceof Error ? err.message : String(err),\n }),\n )\n if (input.waitUntil) input.waitUntil(flush)\n else await flush\n }\n controller.close()\n }\n },\n })\n\n return { body, contentType: 'application/x-ndjson' }\n}\n","/**\n * Derive a stable execution id from the run identity.\n * The same `(projectId, sessionId, turnIndex)` tuple yields the same id.\n *\n * Use the result as both `PromptOptions.executionId` and\n * `PromptOptions.turnId` on the first dispatch.\n * The execution id addresses the server-side execution for reconnect and\n * replay; the turn id makes a repeated dispatch idempotent.\n * An execution id alone does not make a repeated POST idempotent.\n *\n * Format is readable, not hashed: operators grepping orchestrator logs\n * for `gtm-agent:thread-abc:3` find the run without translating an\n * opaque id. Components are URL-encoded so delimiters inside caller ids\n * cannot collapse distinct tuples. The final id is limited to the\n * orchestrator replay route's 256-byte maximum. Execution ids are not a\n * secrecy boundary.\n *\n * Wire integration:\n * - Initial dispatch: pass the result as `executionId` and `turnId`.\n * - Stream replay: pass it as `executionId` with `lastEventId`.\n *\n * @throws `TypeError` when either string id is blank.\n * @throws `RangeError` when `turnIndex` is invalid or the result exceeds 256 bytes.\n */\nexport function deriveExecutionId(input: {\n projectId: string\n sessionId: string\n turnIndex: number\n}): string {\n if (input.projectId.trim().length === 0) {\n throw new TypeError('projectId must be a non-empty string')\n }\n if (input.sessionId.trim().length === 0) {\n throw new TypeError('sessionId must be a non-empty string')\n }\n if (!Number.isSafeInteger(input.turnIndex) || input.turnIndex < 0) {\n throw new RangeError('turnIndex must be a non-negative safe integer')\n }\n\n const executionId = [\n encodeURIComponent(input.projectId),\n encodeURIComponent(input.sessionId),\n String(input.turnIndex),\n ].join(':')\n if (executionId.length > 256) {\n throw new RangeError('derived execution id must not exceed 256 bytes')\n }\n return executionId\n}\n"],"mappings":";AA8FA,MAAM,UAAU,IAAI,YAAY;AAEhC,SAAS,WAAW,OAAoC;CACtD,OAAO,QAAQ,OAAO,GAAG,KAAK,UAAU,KAAK,EAAE,GAAG;AACpD;AAEA,SAAS,WAAW,SAAiB,MAAsC;CACzE,IAAI,MAAM,QAAQ,MAAM,SAAS,IAAI;MAChC,QAAQ,MAAM,OAAO;AAC5B;;;;;;AAOA,SAAgB,eAAe,OAAyC;CACtE,MAAM,MAAM,MAAM,OAAO;CACzB,MAAM,EAAE,UAAU,UAAU;CA0E5B,OAAO;EAAE,MAAA,IAxEQ,eAA2B,EAC1C,OAAO,OAAO,eAAe;GAC3B,MAAM,OAAO,OAAO,UAA0C;IAC5D,WAAW,QAAQ,WAAW,KAAK,CAAC;IACpC,IAAI,MAAM,SACR,IAAI;KACF,MAAM,MAAM,QAAQ,KAAK;IAC3B,SAAS,KAAK;KACZ,IAAI,oCAAoC,EACtC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;IACH;GAEJ;GAEA,IAAI;IACF,MAAM,KAAK;KACT,MAAM;KACN,MAAM;MACJ,WAAW,SAAS;MACpB,UAAU,SAAS;MACnB,WAAW,SAAS;KACtB;IACF,CAAC;IAED,MAAM,WAAW,MAAM,QAAQ;IAC/B,WAAW,MAAM,SAAS,SAAS,QACjC,MAAM,KAAK,KAAK;IAElB,MAAM,WAAW,SAAS,UAAU;IACpC,MAAM,YAAY,MAAM,qBACpB,MAAM,MAAM,mBAAmB,QAAQ,IACvC;IAEJ,MAAM,MAAM,wBAAwB;KAAE;KAAU;IAAU,CAAC;IAC3D,IAAI,MAAM,gBACR,IAAI;KACF,MAAM,MAAM,eAAe;MAAE;MAAU;KAAU,CAAC;IACpD,SAAS,KAAK;KACZ,IAAI,sCAAsC,EACxC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;IACH;IAGF,MAAM,KAAK;KACT,MAAM;KACN,MAAM,EAAE,WAAW,SAAS,UAAU;IACxC,CAAC;GACH,SAAS,KAAK;IACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IAC/D,IAAI,6BAA6B,EAAE,OAAO,QAAQ,CAAC;IACnD,MAAM,KAAK;KAAE,MAAM;KAAS,MAAM,EAAE,QAAQ;IAAE,CAAC;IAC/C,MAAM,KAAK;KACT,MAAM;KACN,MAAM;MAAE,WAAW,SAAS;MAAW;KAAQ;IACjD,CAAC;GACH,UAAU;IACR,IAAI,MAAM,YAAY;KACpB,MAAM,QAAQ,MAAM,WAAW,CAAC,CAAC,OAAO,QACtC,IAAI,kCAAkC,EACpC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC,CACH;KACA,IAAI,MAAM,WAAW,MAAM,UAAU,KAAK;UACrC,MAAM;IACb;IACA,WAAW,MAAM;GACnB;EACF,EACF,CAEY;EAAG,aAAa;CAAuB;AACrD;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnKA,SAAgB,kBAAkB,OAIvB;CACT,IAAI,MAAM,UAAU,KAAK,CAAC,CAAC,WAAW,GACpC,MAAM,IAAI,UAAU,sCAAsC;CAE5D,IAAI,MAAM,UAAU,KAAK,CAAC,CAAC,WAAW,GACpC,MAAM,IAAI,UAAU,sCAAsC;CAE5D,IAAI,CAAC,OAAO,cAAc,MAAM,SAAS,KAAK,MAAM,YAAY,GAC9D,MAAM,IAAI,WAAW,+CAA+C;CAGtE,MAAM,cAAc;EAClB,mBAAmB,MAAM,SAAS;EAClC,mBAAmB,MAAM,SAAS;EAClC,OAAO,MAAM,SAAS;CACxB,CAAC,CAAC,KAAK,GAAG;CACV,IAAI,YAAY,SAAS,KACvB,MAAM,IAAI,WAAW,gDAAgD;CAEvE,OAAO;AACT"}
1
+ {"version":3,"file":"durable.js","names":[],"sources":["../src/durable/chat-engine.ts","../src/durable/execution-handle.ts","../src/durable/supervision-discovery.ts"],"sourcesContent":["/**\n * `handleChatTurn` is a framework-neutral chat-turn HTTP orchestrator.\n * Owns the NDJSON `ChatStreamEvent` line protocol, the `session.run.*`\n * lifecycle vocabulary, and the persist / post-process / trace-flush\n * hook order. Returns a `ReadableStream` body the product hands to its\n * platform `Response`.\n *\n * Sandbox owns long-running execution, reconnect, and replay.\n * The producer this engine wraps already speaks that protocol; this\n * engine frames the events and orders product callbacks.\n *\n * Hooks (`ChatTurnHooks`):\n * - `produce`: build the backend event stream\n * - `persistAssistantMessage`: write the assistant turn to the product DB\n * - `onTurnComplete?`: post-process proposals, citations, and similar work\n * - `onEvent?`: send each event to another consumer\n * - `transformFinalText?`: transform text before persistence\n * - `traceFlush?`: let `waitUntil` keep the worker alive for export\n *\n * Framework neutrality: takes already-resolved values (`identity` tuple,\n * a `waitUntil`), never a `Request` or a `Context`. The product's thin\n * route adapter does auth + parse + access-control, then calls\n * `handleChatTurn(...)` and returns `result.body` as its platform `Response`.\n */\n\n/** The NDJSON line protocol every product chat client already speaks. */\nexport interface ChatStreamEvent {\n type: string\n data?: Record<string, unknown>\n}\n\n/** Identity of a chat turn. `tenantId` is the workspace id for workspace-\n * scoped products and the user id for session-scoped products. */\nexport interface ChatTurnIdentity {\n tenantId: string\n /** Thread / session id. */\n sessionId: string\n userId: string\n /** Monotonic 0-based turn index within the session. */\n turnIndex: number\n}\n\n/** The live side of a turn returned by the product's `produce` hook. */\nexport interface ChatTurnProducer<TEvent extends ChatStreamEvent = ChatStreamEvent> {\n /** The turn's event stream. Forwarded verbatim to the caller. */\n stream: AsyncGenerator<TEvent, void, unknown>\n /** The turn's final assistant text. Read once, after `stream` drains. */\n finalText(): string\n}\n\n/** Product callbacks invoked while one chat turn runs. */\nexport interface ChatTurnHooks {\n /** Build the backend stream. The engine forwards events verbatim and\n * reads `finalText()` once the stream drains. */\n produce(): ChatTurnProducer\n /** Persist the assistant message to the product's own store. Called\n * once, after drain, with the assembled (transform-applied) text. */\n persistAssistantMessage(input: { identity: ChatTurnIdentity; finalText: string }): Promise<void>\n /** Optional post-processing for proposals, citations, or credit metering.\n * Errors are logged without failing a turn that already streamed. */\n onTurnComplete?(input: { identity: ChatTurnIdentity; finalText: string }): Promise<void>\n /** Optional per-event side channel, such as a Durable Object broadcast.\n * Runs for every emitted event, including the lifecycle envelope.\n * Errors are logged without breaking the chat stream. */\n onEvent?(event: ChatStreamEvent): void | Promise<void>\n /** Optional pre-persist transform of the final text (e.g. PII\n * redaction). Affects only what is persisted; the live stream is\n * never altered. */\n transformFinalText?(text: string): string | Promise<string>\n /** Optional trace flush. Handed to `waitUntil` so the worker stays alive\n * until export completes. */\n traceFlush?(): Promise<void>\n}\n\n/** Inputs for one streamed product chat turn. */\nexport interface RunChatTurnInput {\n identity: ChatTurnIdentity\n hooks: ChatTurnHooks\n /** Worker liveness hook. When omitted, trace flush is awaited inline\n * before the stream closes. */\n waitUntil?: (p: Promise<unknown>) => void\n /** Structured logger for swallowed hook errors. Defaults to\n * `console.error` so failures surface without product wiring. */\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** HTTP response values returned for one chat turn. */\nexport interface ChatTurnResult {\n /** NDJSON body to return as the platform `Response` body. */\n body: ReadableStream<Uint8Array>\n /** Content type for the response. */\n contentType: 'application/x-ndjson'\n}\n\nconst encoder = new TextEncoder()\n\nfunction encodeLine(event: ChatStreamEvent): Uint8Array {\n return encoder.encode(`${JSON.stringify(event)}\\n`)\n}\n\nfunction defaultLog(message: string, meta?: Record<string, unknown>): void {\n if (meta) console.error(message, meta)\n else console.error(message)\n}\n\n/**\n * Run one chat turn. Returns immediately with a `ReadableStream` body;\n * execution starts while the stream is constructed. Backend\n * failures surface as `error` + `session.run.failed` events.\n */\nexport function handleChatTurn(input: RunChatTurnInput): ChatTurnResult {\n const log = input.log ?? defaultLog\n const { identity, hooks } = input\n\n const body = new ReadableStream<Uint8Array>({\n start: async (controller) => {\n const emit = async (event: ChatStreamEvent): Promise<void> => {\n controller.enqueue(encodeLine(event))\n if (hooks.onEvent) {\n try {\n await hooks.onEvent(event)\n } catch (err) {\n log('[chat-engine] onEvent hook threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n }\n\n try {\n await emit({\n type: 'session.run.started',\n data: {\n sessionId: identity.sessionId,\n tenantId: identity.tenantId,\n turnIndex: identity.turnIndex,\n },\n })\n\n const producer = hooks.produce()\n for await (const event of producer.stream) {\n await emit(event)\n }\n const rawFinal = producer.finalText()\n const finalText = hooks.transformFinalText\n ? await hooks.transformFinalText(rawFinal)\n : rawFinal\n\n await hooks.persistAssistantMessage({ identity, finalText })\n if (hooks.onTurnComplete) {\n try {\n await hooks.onTurnComplete({ identity, finalText })\n } catch (err) {\n log('[chat-engine] onTurnComplete threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n await emit({\n type: 'session.run.completed',\n data: { sessionId: identity.sessionId },\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n log('[chat-engine] turn failed', { error: message })\n await emit({ type: 'error', data: { message } })\n await emit({\n type: 'session.run.failed',\n data: { sessionId: identity.sessionId, message },\n })\n } finally {\n if (hooks.traceFlush) {\n const flush = hooks.traceFlush().catch((err) =>\n log('[chat-engine] traceFlush threw', {\n error: err instanceof Error ? err.message : String(err),\n }),\n )\n if (input.waitUntil) input.waitUntil(flush)\n else await flush\n }\n controller.close()\n }\n },\n })\n\n return { body, contentType: 'application/x-ndjson' }\n}\n","/**\n * Derive a stable execution id from the run identity.\n * The same `(projectId, sessionId, turnIndex)` tuple yields the same id.\n *\n * Use the result as both `PromptOptions.executionId` and\n * `PromptOptions.turnId` on the first dispatch.\n * The execution id addresses the server-side execution for reconnect and\n * replay; the turn id makes a repeated dispatch idempotent.\n * An execution id alone does not make a repeated POST idempotent.\n *\n * Format is readable, not hashed: operators grepping orchestrator logs\n * for `gtm-agent:thread-abc:3` find the run without translating an\n * opaque id. Components are URL-encoded so delimiters inside caller ids\n * cannot collapse distinct tuples. The final id is limited to the\n * orchestrator replay route's 256-byte maximum. Execution ids are not a\n * secrecy boundary.\n *\n * Wire integration:\n * - Initial dispatch: pass the result as `executionId` and `turnId`.\n * - Stream replay: pass it as `executionId` with `lastEventId`.\n *\n * @throws `TypeError` when either string id is blank.\n * @throws `RangeError` when `turnIndex` is invalid or the result exceeds 256 bytes.\n */\nexport function deriveExecutionId(input: {\n projectId: string\n sessionId: string\n turnIndex: number\n}): string {\n if (input.projectId.trim().length === 0) {\n throw new TypeError('projectId must be a non-empty string')\n }\n if (input.sessionId.trim().length === 0) {\n throw new TypeError('sessionId must be a non-empty string')\n }\n if (!Number.isSafeInteger(input.turnIndex) || input.turnIndex < 0) {\n throw new RangeError('turnIndex must be a non-negative safe integer')\n }\n\n const executionId = [\n encodeURIComponent(input.projectId),\n encodeURIComponent(input.sessionId),\n String(input.turnIndex),\n ].join(':')\n if (executionId.length > 256) {\n throw new RangeError('derived execution id must not exceed 256 bytes')\n }\n return executionId\n}\n","import { resolve } from 'node:path'\nimport type { NodeId } from '../runtime/supervise/types'\nimport { parseCommittedJsonLines } from './jsonl-file'\n\nexport interface DurableCoordinationStreamIdentity {\n readonly runId: string\n /** Exact owner ids present in the side-log, sorted for deterministic display. */\n readonly ownerIds: readonly string[]\n /** Records written before owner-scoped coordination identities were introduced. */\n readonly unscopedRecords: number\n readonly recordCount: number\n}\n\n/**\n * Identities discoverable from one `supervise({ runDir })` directory without\n * already knowing the root node or coordination run id stored inside it.\n */\nexport interface DurableSupervisionDiscovery {\n readonly runDir: string\n readonly spawnJournalPath: string\n readonly coordinationLogPath: string\n readonly roots: readonly NodeId[]\n readonly coordinationStreams: readonly DurableCoordinationStreamIdentity[]\n}\n\ntype SpawnJournalIdentityRecord = {\n readonly kind?: unknown\n readonly root?: unknown\n readonly event?: unknown\n}\n\ntype CoordinationIdentityRecord = {\n readonly runId?: unknown\n readonly ownerId?: unknown\n}\n\n/**\n * Discover the stable identities recorded by Runtime's durable supervision\n * files. This is the developer-facing first step before calling\n * `FileSpawnJournal.loadTree(root)`, `loadSpawnForest(journal, root)`, or\n * `FileCoordinationLog.load(runId, ownerId)`.\n *\n * Missing files produce empty collections. A malformed committed JSONL record\n * still fails loud through the same parser used by the runtime; a torn final\n * append is ignored because it was never acknowledged as committed.\n */\nexport async function discoverDurableSupervisionRun(\n runDir: string,\n): Promise<DurableSupervisionDiscovery> {\n if (typeof runDir !== 'string' || runDir.trim().length === 0) {\n throw new TypeError('discoverDurableSupervisionRun: runDir must be a non-empty string')\n }\n\n const canonicalRunDir = resolve(runDir)\n const spawnJournalPath = `${canonicalRunDir}/spawn-journal.jsonl`\n const coordinationLogPath = `${canonicalRunDir}/coordination-log.jsonl`\n const [spawnText, coordinationText] = await Promise.all([\n readOptionalText(spawnJournalPath),\n readOptionalText(coordinationLogPath),\n ])\n\n const allRoots = new Set<NodeId>()\n const nestedRoots = new Set<NodeId>()\n if (spawnText !== undefined) {\n for (const record of parseCommittedJsonLines<SpawnJournalIdentityRecord>(\n spawnText,\n spawnJournalPath,\n )) {\n if (record.kind === 'begin') {\n if (typeof record.root !== 'string' || record.root.length === 0) {\n throw new Error(`${spawnJournalPath}: begin record has no non-empty string root identity`)\n }\n allRoots.add(record.root as NodeId)\n continue\n }\n if (record.kind !== 'event') continue\n const event = record.event\n if (!isRecord(event) || event.kind !== 'spawned') continue\n if (!Object.hasOwn(event, 'ownedTreeRoot')) continue\n if (typeof event.ownedTreeRoot !== 'string' || event.ownedTreeRoot.length === 0) {\n throw new Error(\n `${spawnJournalPath}: spawned event ownedTreeRoot must be a non-empty string when present`,\n )\n }\n nestedRoots.add(event.ownedTreeRoot as NodeId)\n }\n }\n\n const streams = new Map<\n string,\n { owners: Set<string>; unscopedRecords: number; recordCount: number }\n >()\n if (coordinationText !== undefined) {\n for (const record of parseCommittedJsonLines<CoordinationIdentityRecord>(\n coordinationText,\n coordinationLogPath,\n )) {\n if (typeof record.runId !== 'string' || record.runId.length === 0) {\n throw new Error(`${coordinationLogPath}: record has no non-empty string runId identity`)\n }\n const stream = streams.get(record.runId) ?? {\n owners: new Set<string>(),\n unscopedRecords: 0,\n recordCount: 0,\n }\n stream.recordCount += 1\n if (record.ownerId === undefined) {\n stream.unscopedRecords += 1\n } else if (typeof record.ownerId === 'string' && record.ownerId.length > 0) {\n stream.owners.add(record.ownerId)\n } else {\n throw new Error(`${coordinationLogPath}: ownerId must be a non-empty string when present`)\n }\n streams.set(record.runId, stream)\n }\n }\n\n const coordinationStreams = [...streams.entries()]\n .sort(([left], [right]) => compareText(left, right))\n .map(([runId, stream]) =>\n Object.freeze({\n runId,\n ownerIds: Object.freeze([...stream.owners].sort(compareText)),\n unscopedRecords: stream.unscopedRecords,\n recordCount: stream.recordCount,\n }),\n )\n\n return Object.freeze({\n runDir: canonicalRunDir,\n spawnJournalPath,\n coordinationLogPath,\n roots: Object.freeze([...allRoots].filter((root) => !nestedRoots.has(root)).sort(compareText)),\n coordinationStreams: Object.freeze(coordinationStreams),\n })\n}\n\nasync function readOptionalText(path: string): Promise<string | undefined> {\n const fs = await import('node:fs/promises')\n try {\n return await fs.readFile(path, 'utf8')\n } catch (error) {\n if (isNoEntError(error)) return undefined\n throw error\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction compareText(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nfunction isNoEntError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: unknown }).code === 'ENOENT'\n )\n}\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
+ import { C as sameControlCoordinates, E as promptOptionsFromAgentTurnInput, T as promptFromAgentTurnInput, d as awaitAbortable } from "./retained-run-binding-Dp2e8xne.js";
1
2
  import { _ as zeroTokenUsage } from "./util-C1bv6yog.js";
2
- import { harnessSystemPromptIntents } from "@tangle-network/agent-interface";
3
+ import { t as canonicalStreamEventFromSandboxEvent } from "./sandbox-events-DPmXlcKw.js";
4
+ import { AgentExactRunControlRefSchema, AgentRunCancellationRequestSchema, harnessSystemPromptIntents } from "@tangle-network/agent-interface";
3
5
  import { isDeepStrictEqual } from "node:util";
4
6
  //#region src/runtime/tangle-sandbox-exact-process-provider.ts
5
7
  const exactProcessProviderMarker = "agent-runtime.exact-process-provider";
@@ -458,7 +460,7 @@ function sandboxClientAsProvider(client, options = {}) {
458
460
  ...options.validateProfile ? { validateProfile: options.validateProfile } : {},
459
461
  async create(input) {
460
462
  const createOptions = options.mapCreateInput?.(input) ?? await sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode", options.resolveProfile);
461
- return sandboxInstanceAsEnvironment(await client.create(createOptions), providerName, client);
463
+ return sandboxInstanceAsEnvironment(await client.create(createOptions, input.signal === void 0 ? void 0 : { signal: input.signal }), providerName, client);
462
464
  },
463
465
  ...hasGet(client) ? { async get(id) {
464
466
  const box = await client.get(id);
@@ -660,7 +662,12 @@ function environmentAsSandboxInstance(environment, options) {
660
662
  if (cancellationStarted || !input.sessionId || !environment.session) return;
661
663
  cancellationStarted = true;
662
664
  try {
663
- cancellation = environment.session(input.sessionId).cancel().catch((error) => {
665
+ const session = environment.session(input.sessionId, {
666
+ ...input.controlRef === void 0 ? {} : { controlRef: input.controlRef },
667
+ ...input.signal === void 0 ? {} : { signal: input.signal }
668
+ });
669
+ assertScopedSessionForInput(session, input);
670
+ cancellation = session.cancel().catch((error) => {
664
671
  cancellationFailed = true;
665
672
  cancellationError = error;
666
673
  });
@@ -725,8 +732,8 @@ function environmentAsSandboxInstance(environment, options) {
725
732
  if (!session) throw new ValidationError("providerAsSandboxClient: dispatch returned no session");
726
733
  return sandboxDispatchResultFromSessionRef(session);
727
734
  } } : {},
728
- ...environment.session ? { session(id) {
729
- return sandboxSessionFromAgentSession(environment.session?.(id));
735
+ ...environment.session ? { session(id, sessionOptions) {
736
+ return sandboxSessionFromAgentSession(environment.session?.(id, sessionOptions), sessionOptions?.controlRef);
730
737
  } } : {},
731
738
  ...environment.read ? { read: environment.read.bind(environment) } : {},
732
739
  ...environment.write ? { write: environment.write.bind(environment) } : {},
@@ -753,19 +760,25 @@ function sandboxInstanceAsEnvironment(box, providerName, client) {
753
760
  id: String(box.id),
754
761
  provider: providerName,
755
762
  ...typeof box.name === "string" ? { name: box.name } : {},
763
+ ...readBoxMetadata(box) ? { metadata: readBoxMetadata(box) } : {},
756
764
  async status() {
757
765
  await maybeRefresh(box);
758
766
  return statusFromUnknown(readBoxStatus(box));
759
767
  },
760
768
  async *stream(input) {
761
- for await (const event of box.streamPrompt(promptFromTurnInput(input), promptOptionsFromTurnInput(input))) yield environmentEventFromSandboxEvent(event);
769
+ for await (const event of box.streamPrompt(promptFromAgentTurnInput(input), promptOptionsFromAgentTurnInput(input))) yield environmentEventFromSandboxEvent(event);
762
770
  },
763
771
  ...hasDispatchPrompt(box) ? { async dispatch(input) {
764
- return sessionRefFromSandboxDispatch(await box.dispatchPrompt(promptFromTurnInput(input), promptOptionsFromTurnInput(input)), providerName);
765
- } } : {},
766
- ...hasSession(box) ? { session(id) {
767
- return sandboxSessionAsAgentSession(box.session(id));
772
+ return sessionRefFromSandboxDispatch(await box.dispatchPrompt(promptFromAgentTurnInput(input), promptOptionsFromAgentTurnInput(input)), providerName);
768
773
  } } : {},
774
+ ...hasSession(box) ? {
775
+ session(id, sessionOptions) {
776
+ return sandboxSessionAsAgentSession(box.session(id), sessionOptions?.controlRef);
777
+ },
778
+ async respondToInteraction(command, options) {
779
+ return (await box.session(command.binding.sessionId).respondToInteraction(command, options)).acknowledgement;
780
+ }
781
+ } : {},
769
782
  ...hasRead(box) ? { read: box.read.bind(box) } : {},
770
783
  ...hasWrite(box) ? { write: box.write.bind(box) } : {},
771
784
  ...hasExec(box) ? { async exec(command, options) {
@@ -797,32 +810,50 @@ function sandboxInstanceAsEnvironment(box, providerName, client) {
797
810
  }
798
811
  };
799
812
  }
800
- function sandboxSessionAsAgentSession(session) {
813
+ function sandboxSessionAsAgentSession(session, expectedControlRef) {
814
+ const controlRef = resolveSessionControlRef(session.controlRef, expectedControlRef, { allowExpectedWhenActualAbsent: true });
801
815
  return {
802
816
  id: session.id,
817
+ ...controlRef === void 0 ? {} : { controlRef },
803
818
  async status() {
804
819
  const status = await session.status();
805
820
  if (!status) return null;
821
+ assertSandboxStatusBinding(status, controlRef);
806
822
  return sessionStatusFromUnknown(status.status);
807
823
  },
808
824
  async *events(options) {
809
- for await (const event of session.events(options)) yield environmentEventFromSandboxEvent(event);
825
+ const executionId = scopedExecutionId(controlRef, options?.executionId);
826
+ for await (const event of session.events({
827
+ ...options?.since === void 0 ? {} : { since: options.since },
828
+ ...executionId === void 0 ? {} : { executionId },
829
+ ...options?.signal === void 0 ? {} : { signal: options.signal }
830
+ })) yield environmentEventFromSandboxEvent(event);
810
831
  },
811
- async result() {
812
- return agentTurnResultFromPromptResult(await session.result());
832
+ async result(options) {
833
+ return agentTurnResultFromPromptResult(await awaitAbortable(Promise.resolve().then(() => session.result({ ...controlRef?.executionId === void 0 ? {} : { executionId: controlRef.executionId } })), options?.signal));
813
834
  },
814
835
  async prompt(input) {
815
- return agentTurnResultFromPromptResult(await session.prompt(promptFromTurnInput(input), promptOptionsFromTurnInput(input)));
836
+ return agentTurnResultFromPromptResult(await session.prompt(promptFromAgentTurnInput(input), promptOptionsFromAgentTurnInput(input)));
816
837
  },
838
+ ...session.respondToInteraction ? { async respondToInteraction(command, options) {
839
+ assertInteractionCommandScope(command, controlRef);
840
+ return (await session.respondToInteraction(command, options)).acknowledgement;
841
+ } } : {},
842
+ ...session.cancelRun ? { async cancelRun(request, options) {
843
+ assertCancellationScope(request, controlRef);
844
+ return session.cancelRun(request, options);
845
+ } } : {},
817
846
  cancel() {
818
- return session.interrupt().then(() => void 0);
847
+ return session.interrupt(controlRef?.executionId === void 0 ? void 0 : { executionId: controlRef.executionId }).then(() => void 0);
819
848
  }
820
849
  };
821
850
  }
822
- function sandboxSessionFromAgentSession(session) {
851
+ function sandboxSessionFromAgentSession(session, expectedControlRef) {
823
852
  if (!session) throw new ValidationError("providerAsSandboxClient: session is unavailable");
853
+ const controlRef = resolveSessionControlRef(session.controlRef, expectedControlRef);
824
854
  return {
825
855
  id: session.id,
856
+ ...controlRef === void 0 ? {} : { controlRef },
826
857
  async status() {
827
858
  const status = await session.status();
828
859
  if (!status) return null;
@@ -832,15 +863,30 @@ function sandboxSessionFromAgentSession(session) {
832
863
  };
833
864
  },
834
865
  async *events(options) {
835
- for await (const event of session.events(options)) yield sandboxEventFromEnvironmentEvent(event);
866
+ const executionId = scopedExecutionId(controlRef, options?.executionId);
867
+ for await (const event of session.events({
868
+ ...options?.since === void 0 ? {} : { since: options.since },
869
+ ...executionId === void 0 ? {} : { executionId },
870
+ ...options?.signal === void 0 ? {} : { signal: options.signal }
871
+ })) yield sandboxEventFromEnvironmentEvent(event);
836
872
  },
837
- async result() {
873
+ async result(options) {
874
+ scopedExecutionId(controlRef, options?.executionId);
838
875
  return promptResultFromAgentTurnResult(await session.result());
839
876
  },
840
877
  async prompt(message, options) {
841
878
  return promptResultFromAgentTurnResult(await session.prompt(turnInputFromPrompt(message, options)));
842
879
  },
843
- async interrupt() {
880
+ ...session.respondToInteraction ? { async respondToInteraction(command, options) {
881
+ assertInteractionCommandScope(command, controlRef);
882
+ return { acknowledgement: await session.respondToInteraction(command, options) };
883
+ } } : {},
884
+ ...session.cancelRun ? { async cancelRun(request, options) {
885
+ assertCancellationScope(request, controlRef);
886
+ return session.cancelRun(request, options);
887
+ } } : {},
888
+ async interrupt(options) {
889
+ scopedExecutionId(controlRef, options?.executionId);
844
890
  await session.cancel();
845
891
  return { cancelled: true };
846
892
  }
@@ -877,10 +923,12 @@ function promptResultFromAgentTurnResult(result) {
877
923
  }
878
924
  function environmentEventFromSandboxEvent(event) {
879
925
  const data = event.data && typeof event.data === "object" ? event.data : {};
926
+ const normalized = canonicalStreamEventFromSandboxEvent(event);
880
927
  return {
881
928
  type: String(event.type),
882
929
  data,
883
930
  ...event.id ? { id: event.id } : {},
931
+ ...normalized ? { normalized } : {},
884
932
  usage: tokenUsageFromData(data),
885
933
  providerEvent: event
886
934
  };
@@ -955,37 +1003,56 @@ function turnInputFromPrompt(message, options) {
955
1003
  ...options?.turnId ? { turnId: options.turnId } : {},
956
1004
  ...options?.detach !== void 0 ? { detach: options.detach } : {},
957
1005
  ...options?.context ? { context: options.context } : {},
1006
+ ...options?.runControlRef ? { controlRef: options.runControlRef } : {},
1007
+ ...options?.backend?.interactions ? { interactions: options.backend.interactions } : {},
958
1008
  ...options?.signal ? { signal: options.signal } : {},
959
1009
  ...options?.backend ? { providerOptions: { backend: options.backend } } : {}
960
1010
  };
961
1011
  }
962
- function promptFromTurnInput(input) {
963
- if (input.parts) return input.parts.map(promptPartFromInputPart);
964
- return input.prompt ?? "";
1012
+ function resolveSessionControlRef(actual, expected, options = {}) {
1013
+ if (expected === void 0) {
1014
+ if (actual === void 0) return void 0;
1015
+ return AgentExactRunControlRefSchema.parse(actual);
1016
+ }
1017
+ const expectedExact = AgentExactRunControlRefSchema.parse(expected);
1018
+ if (actual === void 0) {
1019
+ if (options.allowExpectedWhenActualAbsent === true) return expectedExact;
1020
+ throw new ValidationError("provider session omitted the required exact run control reference");
1021
+ }
1022
+ const actualExact = AgentExactRunControlRefSchema.safeParse(actual);
1023
+ if (!actualExact.success) throw new ValidationError("provider session returned an invalid exact run control reference");
1024
+ if (!sameControlCoordinates(actualExact.data, expectedExact)) throw new ValidationError("provider session returned a different exact run control reference");
1025
+ return actualExact.data;
1026
+ }
1027
+ function assertSandboxStatusBinding(status, controlRef) {
1028
+ if (!status || typeof status !== "object" || controlRef === void 0) return;
1029
+ const record = status;
1030
+ if (record.runControlRef !== void 0) {
1031
+ if (!sameControlCoordinates(AgentExactRunControlRefSchema.parse(record.runControlRef), controlRef)) throw new ValidationError("sandbox status returned a different exact run control reference");
1032
+ }
1033
+ for (const key of ["activeExecutionId", "latestExecutionId"]) {
1034
+ const executionId = record[key];
1035
+ if (executionId !== void 0 && executionId !== controlRef.executionId) throw new ValidationError("sandbox status returned a different execution");
1036
+ }
965
1037
  }
966
- function promptPartFromInputPart(part) {
967
- if (part.type === "text" || part.type === "image") return part;
968
- if (part.content !== void 0 || part.path !== void 0) throw new ValidationError("Tangle Sandbox file prompt parts require a URL; inline content and local paths are not representable");
969
- if (!part.filename || !part.url) throw new ValidationError("Tangle Sandbox file prompt parts require both filename and URL");
970
- return {
971
- type: "file",
972
- filename: part.filename,
973
- ...part.mediaType ? { mediaType: part.mediaType } : {},
974
- url: part.url
975
- };
1038
+ function assertInteractionCommandScope(command, controlRef) {
1039
+ if (controlRef === void 0) return;
1040
+ if (command.binding.runId !== controlRef.runId || command.binding.provider !== controlRef.provider || command.binding.environmentId !== controlRef.environmentId || command.binding.sessionId !== controlRef.sessionId || command.binding.executionId !== controlRef.executionId) throw new ValidationError("interaction response targeted a different execution");
976
1041
  }
977
- function promptOptionsFromTurnInput(input) {
978
- return {
979
- ...input.sessionId ? { sessionId: input.sessionId } : {},
980
- ...input.model ? { model: input.model } : {},
981
- ...input.timeoutMs ? { timeoutMs: input.timeoutMs } : {},
982
- ...input.context ? { context: input.context } : {},
983
- ...input.signal ? { signal: input.signal } : {},
984
- ...input.executionId ? { executionId: input.executionId } : {},
985
- ...input.lastEventId ? { lastEventId: input.lastEventId } : {},
986
- ...input.turnId ? { turnId: input.turnId } : {},
987
- ...input.detach !== void 0 ? { detach: input.detach } : {}
988
- };
1042
+ function assertCancellationScope(request, controlRef) {
1043
+ if (controlRef === void 0) throw new ValidationError("durable cancellation requires an exact wrapper run control reference");
1044
+ if (!sameControlCoordinates(AgentRunCancellationRequestSchema.parse(request).run, controlRef)) throw new ValidationError("cancellation targeted a different execution");
1045
+ }
1046
+ function scopedExecutionId(controlRef, requested) {
1047
+ if (controlRef?.executionId !== void 0 && requested !== void 0 && controlRef.executionId !== requested) throw new ValidationError("session operation targeted a different execution");
1048
+ return controlRef?.executionId ?? requested;
1049
+ }
1050
+ function assertScopedSessionForInput(session, input) {
1051
+ if (input.executionId === void 0 && input.controlRef === void 0) return;
1052
+ const sessionControlRef = AgentExactRunControlRefSchema.safeParse(session.controlRef);
1053
+ if (!sessionControlRef.success) throw new ValidationError("provider session did not expose an exact run control reference for scoped cancellation");
1054
+ const expectedControlRef = input.controlRef === void 0 ? void 0 : AgentExactRunControlRefSchema.parse(input.controlRef);
1055
+ if (input.executionId !== void 0 && sessionControlRef.data.executionId !== input.executionId || expectedControlRef !== void 0 && !sameControlCoordinates(sessionControlRef.data, expectedControlRef)) throw new ValidationError("provider session returned a different exact run control reference");
989
1056
  }
990
1057
  function taskToTurnInput(task, signal) {
991
1058
  return {
@@ -1111,11 +1178,23 @@ function agentTurnResultFromPromptResult(result) {
1111
1178
  };
1112
1179
  }
1113
1180
  function sandboxDispatchResultFromSessionRef(session) {
1181
+ const controlRef = session.controlRef === void 0 ? void 0 : AgentExactRunControlRefSchema.parse(session.controlRef);
1182
+ const metadataExecutionId = optionalDispatchIdentity(session.metadata?.executionId, "executionId");
1183
+ if (controlRef !== void 0) {
1184
+ if (controlRef.sessionId !== session.id) throw new ValidationError("provider dispatch returned a control reference for another session");
1185
+ if (session.provider !== void 0 && controlRef.provider !== session.provider) throw new ValidationError("provider dispatch returned a control reference for another provider");
1186
+ if (metadataExecutionId !== void 0 && metadataExecutionId !== controlRef.executionId) throw new ValidationError("provider dispatch returned conflicting execution identities");
1187
+ }
1114
1188
  const status = session.metadata && Object.hasOwn(session.metadata, "status") ? sessionStatusFromUnknown(session.metadata?.status) : "running";
1115
1189
  return {
1116
1190
  sessionId: session.id,
1117
1191
  status,
1118
- alreadyExisted: session.metadata?.alreadyExisted === true
1192
+ alreadyExisted: session.metadata?.alreadyExisted === true,
1193
+ ...session.metadata?.dispatched === void 0 ? {} : { dispatched: session.metadata.dispatched === true },
1194
+ ...controlRef === void 0 ? metadataExecutionId === void 0 ? {} : { executionId: metadataExecutionId } : {
1195
+ executionId: controlRef.executionId,
1196
+ runControlRef: controlRef
1197
+ }
1119
1198
  };
1120
1199
  }
1121
1200
  function sessionRefFromSandboxDispatch(dispatched, providerName) {
@@ -1123,15 +1202,35 @@ function sessionRefFromSandboxDispatch(dispatched, providerName) {
1123
1202
  const id = record?.sessionId ?? record?.id;
1124
1203
  if (typeof id !== "string" || id.length === 0) throw new ValidationError("sandboxClientAsProvider: dispatch returned no session id");
1125
1204
  if (!record) throw new ValidationError("sandboxClientAsProvider: dispatch returned no session record");
1205
+ const executionId = optionalDispatchIdentity(record.executionId, "executionId");
1206
+ const controlRef = record.runControlRef === void 0 ? void 0 : AgentExactRunControlRefSchema.parse(record.runControlRef);
1207
+ if (controlRef !== void 0) {
1208
+ if (controlRef.sessionId !== id || controlRef.provider !== providerName) throw new ValidationError("sandbox dispatch returned a control reference for another session");
1209
+ if (executionId !== void 0 && controlRef.executionId !== executionId) throw new ValidationError("sandbox dispatch returned conflicting execution identities");
1210
+ }
1211
+ if (record.alreadyExisted !== void 0 && typeof record.alreadyExisted !== "boolean") throw new ValidationError("sandbox dispatch returned an invalid alreadyExisted flag");
1212
+ if (record.dispatched !== void 0 && typeof record.dispatched !== "boolean") throw new ValidationError("sandbox dispatch returned an invalid dispatched flag");
1213
+ if (record.status !== void 0 && !isSandboxSessionStatus(record.status)) throw new ValidationError("sandbox dispatch returned an invalid session status");
1126
1214
  return {
1127
1215
  id,
1128
1216
  provider: providerName,
1217
+ ...controlRef === void 0 ? {} : { controlRef },
1129
1218
  metadata: {
1130
1219
  ...record.status ? { status: record.status } : {},
1131
- ...record.alreadyExisted !== void 0 ? { alreadyExisted: record.alreadyExisted } : {}
1220
+ ...executionId === void 0 ? {} : { executionId },
1221
+ ...record.alreadyExisted !== void 0 ? { alreadyExisted: record.alreadyExisted } : {},
1222
+ ...record.dispatched !== void 0 ? { dispatched: record.dispatched } : {}
1132
1223
  }
1133
1224
  };
1134
1225
  }
1226
+ function optionalDispatchIdentity(value, label) {
1227
+ if (value === void 0) return void 0;
1228
+ if (typeof value !== "string" || value.length === 0) throw new ValidationError(`sandbox dispatch returned an invalid ${label}`);
1229
+ return value;
1230
+ }
1231
+ function isSandboxSessionStatus(value) {
1232
+ return value === "queued" || value === "running" || value === "completed" || value === "failed" || value === "cancelled";
1233
+ }
1135
1234
  function execResultFromSandboxExecResult(result) {
1136
1235
  const record = result;
1137
1236
  const exitCode = finiteNumber(record.exitCode) ?? finiteNumber(record.code);
@@ -1284,4 +1383,4 @@ function hasExec(box) {
1284
1383
  //#endregion
1285
1384
  export { sandboxClientAsProvider as a, resolveAgentEnvironmentProvider as i, providerAsExecutor as n, createTangleSandboxExactProcessProvider as o, providerAsSandboxClient as r, createAgentEnvironmentProviderRegistry as t };
1286
1385
 
1287
- //# sourceMappingURL=environment-provider-CLNVqp6H.js.map
1386
+ //# sourceMappingURL=environment-provider-BAO8wt1_.js.map