@tangle-network/agent-runtime 0.105.0 → 0.106.1

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 (218) hide show
  1. package/README.md +2 -2
  2. package/dist/activation-CRjVfOiy.js +203 -0
  3. package/dist/activation-CRjVfOiy.js.map +1 -0
  4. package/dist/activation-DyWB0K6E.d.ts +98 -0
  5. package/dist/agent.d.ts +216 -234
  6. package/dist/agent.js +615 -547
  7. package/dist/agent.js.map +1 -1
  8. package/dist/analyst-loop-DGdtVENg.js +517 -0
  9. package/dist/analyst-loop-DGdtVENg.js.map +1 -0
  10. package/dist/analyst-loop.d.ts +10 -43
  11. package/dist/analyst-loop.js +2 -11
  12. package/dist/backends-CiOCyRHb.js +743 -0
  13. package/dist/backends-CiOCyRHb.js.map +1 -0
  14. package/dist/candidate-execution/index.d.ts +4 -192
  15. package/dist/candidate-execution/index.js +5 -66
  16. package/dist/candidate-execution-DqMbv1rU.js +488 -0
  17. package/dist/candidate-execution-DqMbv1rU.js.map +1 -0
  18. package/dist/coder-yhVWbdWc.js +40 -0
  19. package/dist/coder-yhVWbdWc.js.map +1 -0
  20. package/dist/conversation-BpLQZGPH.js +1265 -0
  21. package/dist/conversation-BpLQZGPH.js.map +1 -0
  22. package/dist/conversation.d.ts +2 -804
  23. package/dist/conversation.js +2 -55
  24. package/dist/environment-provider-CfLUmKmf.js +1168 -0
  25. package/dist/environment-provider-CfLUmKmf.js.map +1 -0
  26. package/dist/environment-provider-IUGU3epE.d.ts +964 -0
  27. package/dist/environment-provider.d.ts +2 -78
  28. package/dist/environment-provider.js +2 -18
  29. package/dist/errors-DEAvWQPy.js +111 -0
  30. package/dist/errors-DEAvWQPy.js.map +1 -0
  31. package/dist/improvement-adapter-Cr5TBSji.d.ts +166 -0
  32. package/dist/improvement-cycle-C095kS_g.js +2624 -0
  33. package/dist/improvement-cycle-C095kS_g.js.map +1 -0
  34. package/dist/index-BGdxAwfO.d.ts +838 -0
  35. package/dist/index-C-FYUuFG.d.ts +193 -0
  36. package/dist/index-CbNM40xG.d.ts +140 -0
  37. package/dist/index-DruSKs3Z.d.ts +7224 -0
  38. package/dist/index-Efjb3nrQ.d.ts +671 -0
  39. package/dist/index.d.ts +14 -1058
  40. package/dist/index.js +2106 -2082
  41. package/dist/index.js.map +1 -1
  42. package/dist/intelligence.d.ts +768 -889
  43. package/dist/intelligence.js +1466 -1367
  44. package/dist/intelligence.js.map +1 -1
  45. package/dist/{kb-gate-CwHO0vz6.d.ts → kb-gate-C8z2juK8.d.ts} +33 -31
  46. package/dist/kb-gate-DpaSwXVx.js +80 -0
  47. package/dist/kb-gate-DpaSwXVx.js.map +1 -0
  48. package/dist/knowledge-Ku-mPP4f.js +433 -0
  49. package/dist/knowledge-Ku-mPP4f.js.map +1 -0
  50. package/dist/knowledge.d.ts +2 -150
  51. package/dist/knowledge.js +2 -40
  52. package/dist/local-harness-Dh8PJ0ot.d.ts +455 -0
  53. package/dist/loop-runner-bin-BXsFEUcf.d.ts +162 -0
  54. package/dist/loop-runner-bin-CRagyuNb.js +268 -0
  55. package/dist/loop-runner-bin-CRagyuNb.js.map +1 -0
  56. package/dist/loop-runner-bin.d.ts +2 -15
  57. package/dist/loop-runner-bin.js +2 -24
  58. package/dist/loops.d.ts +5 -4164
  59. package/dist/loops.js +8 -419
  60. package/dist/mcp/bin.d.ts +1 -1
  61. package/dist/mcp/bin.js +116 -146
  62. package/dist/mcp/bin.js.map +1 -1
  63. package/dist/mcp/index.d.ts +295 -436
  64. package/dist/mcp/index.js +1242 -1177
  65. package/dist/mcp/index.js.map +1 -1
  66. package/dist/mcp/memory-bin.d.ts +1 -1
  67. package/dist/mcp/memory-bin.js +37 -16
  68. package/dist/mcp/memory-bin.js.map +1 -1
  69. package/dist/memory-server-DL6cE2Ag.js +234 -0
  70. package/dist/memory-server-DL6cE2Ag.js.map +1 -0
  71. package/dist/model-resolution-Btd9iIKV.js +98 -0
  72. package/dist/model-resolution-Btd9iIKV.js.map +1 -0
  73. package/dist/openai-tools-B68JaOCx.d.ts +24 -0
  74. package/dist/openai-tools-CKLy1C7M.js +44 -0
  75. package/dist/openai-tools-CKLy1C7M.js.map +1 -0
  76. package/dist/otel-export-D34IQW1C.js +843 -0
  77. package/dist/otel-export-D34IQW1C.js.map +1 -0
  78. package/dist/platform.d.ts +188 -185
  79. package/dist/platform.js +185 -218
  80. package/dist/platform.js.map +1 -1
  81. package/dist/prepare-B53sPV7M.js +4292 -0
  82. package/dist/prepare-B53sPV7M.js.map +1 -0
  83. package/dist/primeintellect/index.d.ts +149 -145
  84. package/dist/primeintellect/index.js +626 -1003
  85. package/dist/primeintellect/index.js.map +1 -1
  86. package/dist/profiles.d.ts +296 -490
  87. package/dist/profiles.js +722 -749
  88. package/dist/profiles.js.map +1 -1
  89. package/dist/protected-model-port-BLPH5VVv.js +722 -0
  90. package/dist/protected-model-port-BLPH5VVv.js.map +1 -0
  91. package/dist/protected-model-port-BP6Z4eau.d.ts +283 -0
  92. package/dist/protected-redaction--F3v1oo8.js +200 -0
  93. package/dist/protected-redaction--F3v1oo8.js.map +1 -0
  94. package/dist/redact-D-u-rrcn.js +82 -0
  95. package/dist/redact-D-u-rrcn.js.map +1 -0
  96. package/dist/redact-kbIOjfjM.d.ts +452 -0
  97. package/dist/researcher-CoVqNhfI.js +433 -0
  98. package/dist/researcher-CoVqNhfI.js.map +1 -0
  99. package/dist/runtime-5zM5XxLr.js +5408 -0
  100. package/dist/runtime-5zM5XxLr.js.map +1 -0
  101. package/dist/sandbox-events-DeI5xX8P.js +316 -0
  102. package/dist/sandbox-events-DeI5xX8P.js.map +1 -0
  103. package/dist/spawn-journal-CwPvKUTa.js +456 -0
  104. package/dist/spawn-journal-CwPvKUTa.js.map +1 -0
  105. package/dist/structural-rollout-IXUEplky.js +1445 -0
  106. package/dist/structural-rollout-IXUEplky.js.map +1 -0
  107. package/dist/substrate-B0TYNrXn.js +27 -0
  108. package/dist/substrate-B0TYNrXn.js.map +1 -0
  109. package/dist/{substrate-Dyiqmd3o.d.ts → substrate-BcnuSHXm.d.ts} +57 -54
  110. package/dist/supervise-BiRutHS9.js +7321 -0
  111. package/dist/supervise-BiRutHS9.js.map +1 -0
  112. package/dist/supervisor-DTKhF-RV.js +3826 -0
  113. package/dist/supervisor-DTKhF-RV.js.map +1 -0
  114. package/dist/testing.d.ts +4 -3
  115. package/dist/testing.js +2478 -2637
  116. package/dist/testing.js.map +1 -1
  117. package/dist/tool-server-RcWgLIsL.js +141 -0
  118. package/dist/tool-server-RcWgLIsL.js.map +1 -0
  119. package/dist/trace-propagation-CJJC7SVB.js +57 -0
  120. package/dist/trace-propagation-CJJC7SVB.js.map +1 -0
  121. package/dist/types-BaIpb0XT.d.ts +492 -0
  122. package/dist/types-BevOjfTY.d.ts +765 -0
  123. package/dist/types-C9j4qg6l.d.ts +500 -0
  124. package/dist/types-Cja5OTGh.d.ts +190 -0
  125. package/dist/util-MVgdwuIS.js +145 -0
  126. package/dist/util-MVgdwuIS.js.map +1 -0
  127. package/dist/workspace-archive-BosQiotA.js +575 -0
  128. package/dist/workspace-archive-BosQiotA.js.map +1 -0
  129. package/package.json +44 -56
  130. package/skills/build-with-agent-runtime/SKILL.md +1 -0
  131. package/dist/activation-Cm-9MMX1.d.ts +0 -69
  132. package/dist/analyst-loop.js.map +0 -1
  133. package/dist/backends-BO4Jqoe2.d.ts +0 -134
  134. package/dist/candidate-execution/index.js.map +0 -1
  135. package/dist/chunk-2KGAN2HM.js +0 -1256
  136. package/dist/chunk-2KGAN2HM.js.map +0 -1
  137. package/dist/chunk-2VZYRB55.js +0 -834
  138. package/dist/chunk-2VZYRB55.js.map +0 -1
  139. package/dist/chunk-3MDZX7YU.js +0 -125
  140. package/dist/chunk-3MDZX7YU.js.map +0 -1
  141. package/dist/chunk-5IBQAPVB.js +0 -29
  142. package/dist/chunk-5IBQAPVB.js.map +0 -1
  143. package/dist/chunk-5QOB7H74.js +0 -387
  144. package/dist/chunk-5QOB7H74.js.map +0 -1
  145. package/dist/chunk-C5RTIBNZ.js +0 -48
  146. package/dist/chunk-C5RTIBNZ.js.map +0 -1
  147. package/dist/chunk-CTRA64LY.js +0 -680
  148. package/dist/chunk-CTRA64LY.js.map +0 -1
  149. package/dist/chunk-DPEUKJRO.js +0 -27
  150. package/dist/chunk-DPEUKJRO.js.map +0 -1
  151. package/dist/chunk-FD2MBMOH.js +0 -494
  152. package/dist/chunk-FD2MBMOH.js.map +0 -1
  153. package/dist/chunk-H5QPIZNX.js +0 -907
  154. package/dist/chunk-H5QPIZNX.js.map +0 -1
  155. package/dist/chunk-HLKC4UYB.js +0 -3514
  156. package/dist/chunk-HLKC4UYB.js.map +0 -1
  157. package/dist/chunk-HNP72PNU.js +0 -192
  158. package/dist/chunk-HNP72PNU.js.map +0 -1
  159. package/dist/chunk-ISPWRSEC.js +0 -408
  160. package/dist/chunk-ISPWRSEC.js.map +0 -1
  161. package/dist/chunk-KRBFHMV6.js +0 -1228
  162. package/dist/chunk-KRBFHMV6.js.map +0 -1
  163. package/dist/chunk-M22PL4GH.js +0 -171
  164. package/dist/chunk-M22PL4GH.js.map +0 -1
  165. package/dist/chunk-NURIQBJQ.js +0 -72
  166. package/dist/chunk-NURIQBJQ.js.map +0 -1
  167. package/dist/chunk-OPVWXJ2H.js +0 -75
  168. package/dist/chunk-OPVWXJ2H.js.map +0 -1
  169. package/dist/chunk-PZZKQVQV.js +0 -52
  170. package/dist/chunk-PZZKQVQV.js.map +0 -1
  171. package/dist/chunk-Q2JSAVQ3.js +0 -671
  172. package/dist/chunk-Q2JSAVQ3.js.map +0 -1
  173. package/dist/chunk-RDOAVVHY.js +0 -699
  174. package/dist/chunk-RDOAVVHY.js.map +0 -1
  175. package/dist/chunk-SGKPNBXE.js +0 -60
  176. package/dist/chunk-SGKPNBXE.js.map +0 -1
  177. package/dist/chunk-SMQXZGLZ.js +0 -351
  178. package/dist/chunk-SMQXZGLZ.js.map +0 -1
  179. package/dist/chunk-UHEZW5BU.js +0 -2918
  180. package/dist/chunk-UHEZW5BU.js.map +0 -1
  181. package/dist/chunk-UPWGXKXB.js +0 -254
  182. package/dist/chunk-UPWGXKXB.js.map +0 -1
  183. package/dist/chunk-VISA6CI3.js +0 -200
  184. package/dist/chunk-VISA6CI3.js.map +0 -1
  185. package/dist/chunk-WMTCUOQL.js +0 -474
  186. package/dist/chunk-WMTCUOQL.js.map +0 -1
  187. package/dist/chunk-WRTOVNN4.js +0 -4728
  188. package/dist/chunk-WRTOVNN4.js.map +0 -1
  189. package/dist/chunk-WSTRQZYQ.js +0 -5576
  190. package/dist/chunk-WSTRQZYQ.js.map +0 -1
  191. package/dist/chunk-XBG2W2VW.js +0 -6434
  192. package/dist/chunk-XBG2W2VW.js.map +0 -1
  193. package/dist/chunk-YEJR7IXO.js +0 -68
  194. package/dist/chunk-YEJR7IXO.js.map +0 -1
  195. package/dist/chunk-YJZA2BIK.js +0 -1338
  196. package/dist/chunk-YJZA2BIK.js.map +0 -1
  197. package/dist/completion-gate-BMy5LGoP.d.ts +0 -256
  198. package/dist/conversation.js.map +0 -1
  199. package/dist/coordination-BZZSVYpZ.d.ts +0 -2570
  200. package/dist/environment-provider.js.map +0 -1
  201. package/dist/improvement-adapter-HAZz-7vK.d.ts +0 -203
  202. package/dist/knowledge.js.map +0 -1
  203. package/dist/local-harness-BDNcl6jI.d.ts +0 -175
  204. package/dist/loop-runner-bin-68aoM9-7.d.ts +0 -199
  205. package/dist/loop-runner-bin.js.map +0 -1
  206. package/dist/loops.js.map +0 -1
  207. package/dist/openai-tools-fnj6SRVg.d.ts +0 -46
  208. package/dist/protected-model-port-B4t-OQjL.d.ts +0 -271
  209. package/dist/redact-BuFjxBUJ.d.ts +0 -481
  210. package/dist/sanitize-DEbPNtyI.d.ts +0 -328
  211. package/dist/structural-rollout-DEh6CEsa.d.ts +0 -446
  212. package/dist/supervise-4_48wwvy.d.ts +0 -379
  213. package/dist/types-BCoemcXU.d.ts +0 -802
  214. package/dist/types-BwoZWq-i.d.ts +0 -510
  215. package/dist/types-Cbp72qyu.d.ts +0 -491
  216. package/dist/types-CmYCMbFT.d.ts +0 -203
  217. package/dist/types-CvNwMzQt.d.ts +0 -957
  218. package/dist/worktree-fanout-DxhAWr5Z.d.ts +0 -999
package/dist/mcp/index.js CHANGED
@@ -1,1281 +1,1346 @@
1
- import {
2
- coderTaskToPrompt
3
- } from "../chunk-5IBQAPVB.js";
4
- import {
5
- createPropagatingTraceEmitter,
6
- readTraceContextFromEnv,
7
- traceContextToEnv
8
- } from "../chunk-PZZKQVQV.js";
9
- import {
10
- MEMORY_FILE_ENV,
11
- MEMORY_ITEMS_ENV,
12
- MEMORY_LOG_ENV,
13
- MEMORY_NAME_ENV,
14
- createMemoryToolServer,
15
- createStdioToolServer,
16
- parseMemoryItems,
17
- readMemoryItemsFile,
18
- resolveMemoryFromEnv
19
- } from "../chunk-SMQXZGLZ.js";
20
- import {
21
- mcpToolsForRuntimeMcp,
22
- mcpToolsForRuntimeMcpSubset
23
- } from "../chunk-C5RTIBNZ.js";
24
- import {
25
- createKbGate
26
- } from "../chunk-SGKPNBXE.js";
27
- import {
28
- assertTraceDerivedFindings,
29
- runCoderChecks,
30
- selectValidWinner
31
- } from "../chunk-WRTOVNN4.js";
32
- import "../chunk-Q2JSAVQ3.js";
33
- import {
34
- DELEGATE_DESCRIPTION,
35
- DELEGATE_FEEDBACK_DESCRIPTION,
36
- DELEGATE_FEEDBACK_INPUT_SCHEMA,
37
- DELEGATE_FEEDBACK_TOOL_NAME,
38
- DELEGATE_INPUT_SCHEMA,
39
- DELEGATE_TOOL_NAME,
40
- DELEGATE_UI_AUDIT_DESCRIPTION,
41
- DELEGATE_UI_AUDIT_INPUT_SCHEMA,
42
- DELEGATE_UI_AUDIT_TOOL_NAME,
43
- DELEGATION_HISTORY_DESCRIPTION,
44
- DELEGATION_HISTORY_INPUT_SCHEMA,
45
- DELEGATION_HISTORY_TOOL_NAME,
46
- DELEGATION_STATUS_DESCRIPTION,
47
- DELEGATION_STATUS_INPUT_SCHEMA,
48
- DELEGATION_STATUS_TOOL_NAME,
49
- DELEGATION_TRACE_MAX_BYTES,
50
- DELEGATION_TRACE_MAX_SPANS,
51
- DelegationPersistenceError,
52
- DelegationStateCorruptError,
53
- DelegationTaskQueue,
54
- FileDelegationStore,
55
- InMemoryDelegationStore,
56
- InMemoryFeedbackStore,
57
- buildDelegationTraceSpans,
58
- capDelegationTrace,
59
- composeLoopTraceEmitters,
60
- createCoordinationTools,
61
- createDelegateFeedbackHandler,
62
- createDelegateHandler,
63
- createDelegateUiAuditHandler,
64
- createDelegationHistoryHandler,
65
- createDelegationStatusHandler,
66
- createDelegationTraceCollector,
67
- createInProcessTransport,
68
- createMcpServer,
69
- createSandboxForSpec,
70
- eventToSnapshot,
71
- hashIdempotencyInput,
72
- runAgentRounds,
73
- validateDelegateArgs,
74
- validateDelegateFeedbackArgs,
75
- validateDelegateUiAuditArgs,
76
- validateDelegationHistoryArgs,
77
- validateDelegationStatusArgs
78
- } from "../chunk-XBG2W2VW.js";
79
- import "../chunk-DPEUKJRO.js";
80
- import "../chunk-YJZA2BIK.js";
81
- import "../chunk-UPWGXKXB.js";
82
- import "../chunk-2VZYRB55.js";
83
- import {
84
- CodexExecutionDiagnosticError,
85
- captureWorktreeDiff,
86
- createWorktree,
87
- parseCodexTokenUsage,
88
- removeWorktree,
89
- runLocalHarness,
90
- runWorktreeHarness
91
- } from "../chunk-HLKC4UYB.js";
92
- import "../chunk-ISPWRSEC.js";
93
- import "../chunk-KRBFHMV6.js";
94
- import {
95
- deleteBoxSafe,
96
- sleep,
97
- throwAbort,
98
- throwIfAborted
99
- } from "../chunk-3MDZX7YU.js";
100
- import "../chunk-M22PL4GH.js";
101
- import {
102
- ValidationError
103
- } from "../chunk-YEJR7IXO.js";
104
-
105
- // src/mcp/executor.ts
1
+ import { u as ValidationError } from "../errors-DEAvWQPy.js";
2
+ import { l as throwAbort, n as deleteBoxSafe, s as sleep, u as throwIfAborted } from "../util-MVgdwuIS.js";
3
+ import { A as removeWorktree, D as runWorktreeHarness, M as parseCodexTokenUsage, N as runLocalHarness, O as captureWorktreeDiff, P as CodexExecutionDiagnosticError, k as createWorktree } from "../supervisor-DTKhF-RV.js";
4
+ import { W as selectValidWinner, m as runCoderChecks, q as assertTraceDerivedFindings } from "../runtime-5zM5XxLr.js";
5
+ import { $ as eventToSnapshot, A as createDelegateHandler, B as DelegationTaskQueue, Bt as runAgentRounds, C as DELEGATE_FEEDBACK_INPUT_SCHEMA, D as DELEGATE_DESCRIPTION, E as validateDelegateFeedbackArgs, G as capDelegationTrace, H as DELEGATION_TRACE_MAX_BYTES, J as DelegationPersistenceError, K as composeLoopTraceEmitters, O as DELEGATE_INPUT_SCHEMA, Q as InMemoryFeedbackStore, Rt as createSandboxForSpec, S as DELEGATE_FEEDBACK_DESCRIPTION, T as createDelegateFeedbackHandler, U as DELEGATION_TRACE_MAX_SPANS, V as hashIdempotencyInput, W as buildDelegationTraceSpans, X as FileDelegationStore, Y as DelegationStateCorruptError, Z as InMemoryDelegationStore, _ as DELEGATE_UI_AUDIT_DESCRIPTION, a as createInProcessTransport, b as createDelegateUiAuditHandler, c as DELEGATION_STATUS_INPUT_SCHEMA, d as validateDelegationStatusArgs, f as DELEGATION_HISTORY_DESCRIPTION, g as validateDelegationHistoryArgs, h as createDelegationHistoryHandler, ht as createCoordinationTools, j as validateDelegateArgs, k as DELEGATE_TOOL_NAME, l as DELEGATION_STATUS_TOOL_NAME, m as DELEGATION_HISTORY_TOOL_NAME, mt as DEFAULT_AWAIT_EVENT_TIMEOUT_MS, o as createMcpServer, p as DELEGATION_HISTORY_INPUT_SCHEMA, q as createDelegationTraceCollector, s as DELEGATION_STATUS_DESCRIPTION, u as createDelegationStatusHandler, v as DELEGATE_UI_AUDIT_INPUT_SCHEMA, w as DELEGATE_FEEDBACK_TOOL_NAME, x as validateDelegateUiAuditArgs, y as DELEGATE_UI_AUDIT_TOOL_NAME } from "../supervise-BiRutHS9.js";
6
+ import { t as createStdioToolServer } from "../tool-server-RcWgLIsL.js";
7
+ import { t as createKbGate } from "../kb-gate-DpaSwXVx.js";
8
+ import { n as mcpToolsForRuntimeMcpSubset, t as mcpToolsForRuntimeMcp } from "../openai-tools-CKLy1C7M.js";
9
+ import { t as coderTaskToPrompt } from "../coder-yhVWbdWc.js";
10
+ import { a as createMemoryToolServer, c as resolveMemoryFromEnv, i as MEMORY_NAME_ENV, n as MEMORY_ITEMS_ENV, o as parseMemoryItems, r as MEMORY_LOG_ENV, s as readMemoryItemsFile, t as MEMORY_FILE_ENV } from "../memory-server-DL6cE2Ag.js";
11
+ import { n as readTraceContextFromEnv, r as traceContextToEnv, t as createPropagatingTraceEmitter } from "../trace-propagation-CJJC7SVB.js";
12
+ import { makeFinding } from "@tangle-network/agent-eval";
13
+ import { randomUUID } from "node:crypto";
14
+ //#region src/mcp/executor.ts
15
+ /**
16
+ * Wrap a raw sandbox SDK client so the kernel emits
17
+ * `loop.iteration.dispatch` events with `{ placement: 'sibling', sandboxId }`.
18
+ *
19
+ * The returned client `.create()` delegates to the underlying client; the
20
+ * only added behavior is a `describePlacement` tag the kernel reads.
21
+ *
22
+ * @experimental
23
+ */
106
24
  function createSiblingSandboxExecutor(options) {
107
- const underlying = options.client;
108
- const client = {
109
- create(opts) {
110
- return underlying.create(opts);
111
- },
112
- describePlacement(box) {
113
- return { kind: "sibling", sandboxId: readId(box) };
114
- }
115
- };
116
- return {
117
- client,
118
- placement: "sibling",
119
- describe() {
120
- return "sibling-sandbox (each delegation = fresh sandbox via client.create)";
121
- }
122
- };
25
+ const underlying = options.client;
26
+ return {
27
+ client: {
28
+ create(opts) {
29
+ return underlying.create(opts);
30
+ },
31
+ describePlacement(box) {
32
+ return {
33
+ kind: "sibling",
34
+ sandboxId: readId(box)
35
+ };
36
+ }
37
+ },
38
+ placement: "sibling",
39
+ describe() {
40
+ return "sibling-sandbox (each delegation = fresh sandbox via client.create)";
41
+ }
42
+ };
123
43
  }
44
+ /**
45
+ * Build an executor that resolves each delegated iteration to an existing
46
+ * machine in `fleet`. The fleet's shared-workspace policy means the worker
47
+ * machine sees the caller's filesystem — diffs land in-place with no
48
+ * cross-sandbox copy step.
49
+ *
50
+ * @experimental
51
+ */
124
52
  function createFleetWorkspaceExecutor(options) {
125
- const fleet = options.fleet;
126
- const exclude = new Set(options.excludeMachineIds ?? []);
127
- let callIndex = 0;
128
- const placementBySandboxId = /* @__PURE__ */ new Map();
129
- const client = {
130
- async create() {
131
- const ids = fleet.ids.filter((id) => !exclude.has(id));
132
- if (ids.length === 0) {
133
- throw new Error(
134
- `agent-runtime: fleet ${fleet.fleetId} has no eligible worker machines (ids=[${fleet.ids.join(",")}], excluded=[${[...exclude].join(",")}])`
135
- );
136
- }
137
- const selector = options.selectMachine;
138
- const machineId = selector ? selector({ callIndex, ids }) : ids[callIndex % ids.length];
139
- callIndex += 1;
140
- if (typeof machineId !== "string" || machineId.length === 0) {
141
- throw new Error("agent-runtime: fleet executor selectMachine returned an empty machine id");
142
- }
143
- const box = await fleet.sandbox(machineId);
144
- const sandboxId = readId(box);
145
- if (sandboxId) placementBySandboxId.set(sandboxId, { machineId });
146
- return box;
147
- },
148
- describePlacement(box) {
149
- const sandboxId = readId(box);
150
- const recorded = sandboxId ? placementBySandboxId.get(sandboxId) : void 0;
151
- return {
152
- kind: "fleet",
153
- sandboxId,
154
- fleetId: fleet.fleetId,
155
- machineId: recorded?.machineId
156
- };
157
- }
158
- };
159
- return {
160
- client,
161
- placement: "fleet",
162
- describe() {
163
- const excluded = exclude.size > 0 ? ` (excluded=[${[...exclude].join(",")}])` : "";
164
- return `fleet-workspace (fleetId=${fleet.fleetId}, machines=[${fleet.ids.join(",")}]${excluded})`;
165
- }
166
- };
53
+ const fleet = options.fleet;
54
+ const exclude = new Set(options.excludeMachineIds ?? []);
55
+ let callIndex = 0;
56
+ const placementBySandboxId = /* @__PURE__ */ new Map();
57
+ return {
58
+ client: {
59
+ async create() {
60
+ const ids = fleet.ids.filter((id) => !exclude.has(id));
61
+ if (ids.length === 0) throw new Error(`agent-runtime: fleet ${fleet.fleetId} has no eligible worker machines (ids=[${fleet.ids.join(",")}], excluded=[${[...exclude].join(",")}])`);
62
+ const selector = options.selectMachine;
63
+ const machineId = selector ? selector({
64
+ callIndex,
65
+ ids
66
+ }) : ids[callIndex % ids.length];
67
+ callIndex += 1;
68
+ if (typeof machineId !== "string" || machineId.length === 0) throw new Error("agent-runtime: fleet executor selectMachine returned an empty machine id");
69
+ const box = await fleet.sandbox(machineId);
70
+ const sandboxId = readId(box);
71
+ if (sandboxId) placementBySandboxId.set(sandboxId, { machineId });
72
+ return box;
73
+ },
74
+ describePlacement(box) {
75
+ const sandboxId = readId(box);
76
+ const recorded = sandboxId ? placementBySandboxId.get(sandboxId) : void 0;
77
+ return {
78
+ kind: "fleet",
79
+ sandboxId,
80
+ fleetId: fleet.fleetId,
81
+ machineId: recorded?.machineId
82
+ };
83
+ }
84
+ },
85
+ placement: "fleet",
86
+ describe() {
87
+ const excluded = exclude.size > 0 ? ` (excluded=[${[...exclude].join(",")}])` : "";
88
+ return `fleet-workspace (fleetId=${fleet.fleetId}, machines=[${fleet.ids.join(",")}]${excluded})`;
89
+ }
90
+ };
167
91
  }
168
92
  function readId(box) {
169
- const raw = box.id;
170
- return typeof raw === "string" && raw.length > 0 ? raw : void 0;
93
+ const raw = box.id;
94
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
171
95
  }
172
-
173
- // src/mcp/in-process-executor.ts
174
- import { randomUUID } from "crypto";
175
- var DEFAULT_HARNESS_TIMEOUT_MS = 5 * 60 * 1e3;
176
- var DEFAULT_POSTCHECK_TIMEOUT_MS = 2 * 60 * 1e3;
96
+ //#endregion
97
+ //#region src/mcp/in-process-executor.ts
98
+ /**
99
+ *
100
+ * In-process delegation executor when `agent-runtime-mcp` runs inside a sandbox whose image
101
+ * carries the local coding-harness CLIs (claude / codex / opencode), delegations spawn the harness
102
+ * AS A SUBPROCESS against a git worktree on the SAME filesystem instead of provisioning a sibling
103
+ * sandbox. Zero provisioning latency; worker diffs land in-place; multi-harness fanout = N parallel
104
+ * subprocesses in N parallel worktrees (round-robin `harnesses`).
105
+ *
106
+ * This is a THIN adapter over `runWorktreeHarness` (`./worktree-harness`) — the SAME core the
107
+ * `Scope` leaf `createWorktreeCliExecutor` uses. It only adapts the core to the `SandboxClient`
108
+ * port: `create()` reads the authored profile from `CreateSandboxOptions.backend.profile`, and
109
+ * `streamPrompt` runs the core then emits its raw `WorktreeHarnessResult` (the content-addressed
110
+ * patch artifact) on the `result` event. The sandbox-session decode layer
111
+ * (`./detached-coder`) projects that artifact onto `CoderOutput`; the generic `Scope` path settles
112
+ * the artifact directly. The §1.5 payload (systemPrompt + model) reaches the harness inside the core.
113
+ *
114
+ * @experimental
115
+ */
116
+ const DEFAULT_HARNESS_TIMEOUT_MS = 300 * 1e3;
117
+ const DEFAULT_POSTCHECK_TIMEOUT_MS = 120 * 1e3;
118
+ /**
119
+ * Build an in-process executor. Returns a {@link DelegationExecutor} whose `client.create()`
120
+ * returns a minimal virtual `SandboxInstance`; the kernel calls `streamPrompt(msg)` on it, which
121
+ * runs the shared worktree-harness core and emits one `result` event whose `data.result` is the
122
+ * raw `WorktreeHarnessResult` (the content-addressed patch artifact). The authored profile
123
+ * (`backend.profile`) threads its systemPrompt + model into the harness via the core.
124
+ *
125
+ * @experimental
126
+ */
177
127
  function createInProcessExecutor(options) {
178
- const harnesses = options.harnesses && options.harnesses.length > 0 ? [...options.harnesses] : ["claude"];
179
- const runPostCheck = options.runPostCheck ?? defaultRunPostCheck;
180
- const runCommand = async ({
181
- command,
182
- cwd,
183
- signal
184
- }) => {
185
- try {
186
- const r = await runPostCheck(command, cwd, signal);
187
- return { exitCode: r.exitCode, output: r.stderr || r.stdout };
188
- } catch (err) {
189
- return { exitCode: -1, output: err instanceof Error ? err.message : String(err) };
190
- }
191
- };
192
- let callIndex = 0;
193
- const client = {
194
- async create(opts) {
195
- const runId = randomUUID();
196
- const harness = harnesses[callIndex % harnesses.length];
197
- callIndex += 1;
198
- const profile = opts?.backend?.profile ?? { name: `in-process-${harness}` };
199
- const virtual = {
200
- id: `in-process-${runId}`,
201
- __inProcess: { runId, harness },
202
- async *streamPrompt(message, promptOpts) {
203
- const taskPrompt = typeof message === "string" ? message : message.map(
204
- (p) => typeof p === "object" && p && "text" in p ? String(p.text) : ""
205
- ).join("\n");
206
- const run = await runWorktreeHarness({
207
- repoRoot: options.repoRoot,
208
- profile,
209
- harness,
210
- taskPrompt,
211
- runId,
212
- harnessTimeoutMs: options.harnessTimeoutMs ?? DEFAULT_HARNESS_TIMEOUT_MS,
213
- checkTimeoutMs: options.postCheckTimeoutMs ?? DEFAULT_POSTCHECK_TIMEOUT_MS,
214
- ...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
215
- ...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
216
- ...options.runGit ? { runGit: options.runGit } : {},
217
- ...options.runHarness ? { runHarness: options.runHarness } : {},
218
- runCommand,
219
- ...promptOpts?.signal ? { signal: promptOpts.signal } : {}
220
- });
221
- this.__inProcess.worktree = run.worktree;
222
- try {
223
- yield {
224
- type: "in_process.harness.started",
225
- data: { runId, harness, worktreePath: run.worktree.path, command: harness }
226
- };
227
- const h = run.result.harness;
228
- yield {
229
- type: "in_process.harness.ended",
230
- data: {
231
- runId,
232
- exitCode: h.exitCode,
233
- durationMs: h.durationMs,
234
- killedBySignal: h.killedBySignal,
235
- timedOut: h.timedOut,
236
- stdoutBytes: h.stdout.length,
237
- stderrBytes: h.stderr.length
238
- }
239
- };
240
- yield {
241
- type: "result",
242
- data: {
243
- result: run.result,
244
- source: "in-process-executor",
245
- harness,
246
- runId
247
- }
248
- };
249
- } finally {
250
- await run.cleanup();
251
- }
252
- }
253
- };
254
- return virtual;
255
- },
256
- describePlacement(box) {
257
- const sandboxId = box.id;
258
- const meta = box.__inProcess;
259
- return {
260
- kind: "sibling",
261
- sandboxId,
262
- worktreePath: meta?.worktree?.path,
263
- harness: meta?.harness
264
- };
265
- }
266
- };
267
- return {
268
- client,
269
- placement: "in-process",
270
- describe() {
271
- return `in-process (repoRoot=${options.repoRoot}, harnesses=[${harnesses.join(",")}]${options.testCmd ? `, testCmd="${options.testCmd}"` : ""}${options.typecheckCmd ? `, typecheckCmd="${options.typecheckCmd}"` : ""})`;
272
- }
273
- };
128
+ const harnesses = options.harnesses && options.harnesses.length > 0 ? [...options.harnesses] : ["claude"];
129
+ const runPostCheck = options.runPostCheck ?? defaultRunPostCheck;
130
+ const runCommand = async ({ command, cwd, signal }) => {
131
+ try {
132
+ const r = await runPostCheck(command, cwd, signal);
133
+ return {
134
+ exitCode: r.exitCode,
135
+ output: r.stderr || r.stdout
136
+ };
137
+ } catch (err) {
138
+ return {
139
+ exitCode: -1,
140
+ output: err instanceof Error ? err.message : String(err)
141
+ };
142
+ }
143
+ };
144
+ let callIndex = 0;
145
+ return {
146
+ client: {
147
+ async create(opts) {
148
+ const runId = randomUUID();
149
+ const harness = harnesses[callIndex % harnesses.length];
150
+ callIndex += 1;
151
+ const profile = (opts?.backend)?.profile ?? { name: `in-process-${harness}` };
152
+ return {
153
+ id: `in-process-${runId}`,
154
+ __inProcess: {
155
+ runId,
156
+ harness
157
+ },
158
+ async *streamPrompt(message, promptOpts) {
159
+ const taskPrompt = typeof message === "string" ? message : message.map((p) => typeof p === "object" && p && "text" in p ? String(p.text) : "").join("\n");
160
+ const run = await runWorktreeHarness({
161
+ repoRoot: options.repoRoot,
162
+ profile,
163
+ harness,
164
+ taskPrompt,
165
+ runId,
166
+ harnessTimeoutMs: options.harnessTimeoutMs ?? DEFAULT_HARNESS_TIMEOUT_MS,
167
+ checkTimeoutMs: options.postCheckTimeoutMs ?? DEFAULT_POSTCHECK_TIMEOUT_MS,
168
+ ...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
169
+ ...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
170
+ ...options.runGit ? { runGit: options.runGit } : {},
171
+ ...options.runHarness ? { runHarness: options.runHarness } : {},
172
+ runCommand,
173
+ ...promptOpts?.signal ? { signal: promptOpts.signal } : {}
174
+ });
175
+ this.__inProcess.worktree = run.worktree;
176
+ try {
177
+ yield {
178
+ type: "in_process.harness.started",
179
+ data: {
180
+ runId,
181
+ harness,
182
+ worktreePath: run.worktree.path,
183
+ command: harness
184
+ }
185
+ };
186
+ const h = run.result.harness;
187
+ yield {
188
+ type: "in_process.harness.ended",
189
+ data: {
190
+ runId,
191
+ exitCode: h.exitCode,
192
+ durationMs: h.durationMs,
193
+ killedBySignal: h.killedBySignal,
194
+ timedOut: h.timedOut,
195
+ stdoutBytes: h.stdout.length,
196
+ stderrBytes: h.stderr.length
197
+ }
198
+ };
199
+ yield {
200
+ type: "result",
201
+ data: {
202
+ result: run.result,
203
+ source: "in-process-executor",
204
+ harness,
205
+ runId
206
+ }
207
+ };
208
+ } finally {
209
+ await run.cleanup();
210
+ }
211
+ }
212
+ };
213
+ },
214
+ describePlacement(box) {
215
+ const sandboxId = box.id;
216
+ const meta = box.__inProcess;
217
+ return {
218
+ kind: "sibling",
219
+ sandboxId,
220
+ worktreePath: meta?.worktree?.path,
221
+ harness: meta?.harness
222
+ };
223
+ }
224
+ },
225
+ placement: "in-process",
226
+ describe() {
227
+ return `in-process (repoRoot=${options.repoRoot}, harnesses=[${harnesses.join(",")}]${options.testCmd ? `, testCmd="${options.testCmd}"` : ""}${options.typecheckCmd ? `, typecheckCmd="${options.typecheckCmd}"` : ""})`;
228
+ }
229
+ };
274
230
  }
275
231
  async function defaultRunPostCheck(cmd, cwd, signal) {
276
- const { spawn } = await import("child_process");
277
- return new Promise((resolve, reject) => {
278
- const child = spawn("sh", ["-c", cmd], { cwd, stdio: "pipe" });
279
- let stdout = "";
280
- let stderr = "";
281
- child.stdout?.on("data", (c) => {
282
- stdout += String(c);
283
- });
284
- child.stderr?.on("data", (c) => {
285
- stderr += String(c);
286
- });
287
- if (signal) {
288
- const onAbort = () => {
289
- if (!child.killed) child.kill("SIGTERM");
290
- };
291
- if (signal.aborted) onAbort();
292
- else signal.addEventListener("abort", onAbort, { once: true });
293
- }
294
- const killTimer = setTimeout(() => {
295
- if (!child.killed) child.kill("SIGTERM");
296
- }, DEFAULT_POSTCHECK_TIMEOUT_MS);
297
- if (typeof killTimer.unref === "function") {
298
- ;
299
- killTimer.unref();
300
- }
301
- child.on("error", (err) => {
302
- clearTimeout(killTimer);
303
- reject(err);
304
- });
305
- child.on("close", (code) => {
306
- clearTimeout(killTimer);
307
- resolve({ exitCode: code ?? -1, stdout, stderr });
308
- });
309
- });
232
+ const { spawn } = await import("node:child_process");
233
+ return new Promise((resolve, reject) => {
234
+ const child = spawn("sh", ["-c", cmd], {
235
+ cwd,
236
+ stdio: "pipe"
237
+ });
238
+ let stdout = "";
239
+ let stderr = "";
240
+ child.stdout?.on("data", (c) => {
241
+ stdout += String(c);
242
+ });
243
+ child.stderr?.on("data", (c) => {
244
+ stderr += String(c);
245
+ });
246
+ if (signal) {
247
+ const onAbort = () => {
248
+ if (!child.killed) child.kill("SIGTERM");
249
+ };
250
+ if (signal.aborted) onAbort();
251
+ else signal.addEventListener("abort", onAbort, { once: true });
252
+ }
253
+ const killTimer = setTimeout(() => {
254
+ if (!child.killed) child.kill("SIGTERM");
255
+ }, DEFAULT_POSTCHECK_TIMEOUT_MS);
256
+ if (typeof killTimer.unref === "function") killTimer.unref();
257
+ child.on("error", (err) => {
258
+ clearTimeout(killTimer);
259
+ reject(err);
260
+ });
261
+ child.on("close", (code) => {
262
+ clearTimeout(killTimer);
263
+ resolve({
264
+ exitCode: code ?? -1,
265
+ stdout,
266
+ stderr
267
+ });
268
+ });
269
+ });
310
270
  }
311
-
312
- // src/mcp/bin-helpers.ts
271
+ //#endregion
272
+ //#region src/mcp/bin-helpers.ts
273
+ /**
274
+ * Pick the right executor for an MCP server invocation based on env vars.
275
+ *
276
+ * - `TANGLE_FLEET_ID` set → fleet-workspace placement; resolves the handle
277
+ * via `sandboxClient.fleets.get(...)`.
278
+ * - Otherwise → sibling-sandbox placement; each delegation creates a fresh
279
+ * sandbox via `sandboxClient.create(...)`.
280
+ *
281
+ * Fails loud (throws) when fleet mode is requested but the SDK shape is
282
+ * incompatible — the operator chose fleet semantics, silently degrading to
283
+ * sibling mode would lie about workspace topology.
284
+ *
285
+ * @experimental
286
+ */
313
287
  async function detectExecutor(args) {
314
- const env = args.env ?? process.env;
315
- if (env.AGENT_RUNTIME_IN_SANDBOX === "1") {
316
- const repoRoot = env.AGENT_RUNTIME_REPO_ROOT?.trim();
317
- if (!repoRoot) {
318
- throw new Error(
319
- "agent-runtime-mcp: AGENT_RUNTIME_IN_SANDBOX=1 requires AGENT_RUNTIME_REPO_ROOT to point at the workspace root"
320
- );
321
- }
322
- return createInProcessExecutor({
323
- repoRoot,
324
- harnesses: parseHarnesses(env.AGENT_RUNTIME_LOCAL_HARNESSES),
325
- testCmd: env.AGENT_RUNTIME_TEST_CMD?.trim() || void 0,
326
- typecheckCmd: env.AGENT_RUNTIME_TYPECHECK_CMD?.trim() || void 0
327
- });
328
- }
329
- const fleetId = parseFleetId(env.TANGLE_FLEET_ID);
330
- if (!fleetId) {
331
- return createSiblingSandboxExecutor({ client: args.sandboxClient });
332
- }
333
- const resolveFleet = args.resolveFleet ?? defaultResolveFleet;
334
- const fleet = await resolveFleet(args.sandboxClient, fleetId);
335
- const excludeMachineIds = parseList(env.TANGLE_FLEET_EXCLUDE_MACHINES);
336
- return createFleetWorkspaceExecutor({
337
- fleet,
338
- excludeMachineIds
339
- });
288
+ const env = args.env ?? process.env;
289
+ if (env.AGENT_RUNTIME_IN_SANDBOX === "1") {
290
+ const repoRoot = env.AGENT_RUNTIME_REPO_ROOT?.trim();
291
+ if (!repoRoot) throw new Error("agent-runtime-mcp: AGENT_RUNTIME_IN_SANDBOX=1 requires AGENT_RUNTIME_REPO_ROOT to point at the workspace root");
292
+ return createInProcessExecutor({
293
+ repoRoot,
294
+ harnesses: parseHarnesses(env.AGENT_RUNTIME_LOCAL_HARNESSES),
295
+ testCmd: env.AGENT_RUNTIME_TEST_CMD?.trim() || void 0,
296
+ typecheckCmd: env.AGENT_RUNTIME_TYPECHECK_CMD?.trim() || void 0
297
+ });
298
+ }
299
+ const fleetId = parseFleetId(env.TANGLE_FLEET_ID);
300
+ if (!fleetId) return createSiblingSandboxExecutor({ client: args.sandboxClient });
301
+ return createFleetWorkspaceExecutor({
302
+ fleet: await (args.resolveFleet ?? defaultResolveFleet)(args.sandboxClient, fleetId),
303
+ excludeMachineIds: parseList(env.TANGLE_FLEET_EXCLUDE_MACHINES)
304
+ });
340
305
  }
341
- var KNOWN_HARNESSES = ["claude", "codex", "opencode"];
306
+ const KNOWN_HARNESSES = [
307
+ "claude",
308
+ "codex",
309
+ "opencode"
310
+ ];
342
311
  function parseHarnesses(raw) {
343
- if (!raw) return void 0;
344
- const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
345
- if (parts.length === 0) return void 0;
346
- for (const part of parts) {
347
- if (!KNOWN_HARNESSES.includes(part)) {
348
- throw new Error(
349
- `agent-runtime-mcp: AGENT_RUNTIME_LOCAL_HARNESSES contains unknown harness "${part}". Expected: ${KNOWN_HARNESSES.join(", ")}.`
350
- );
351
- }
352
- }
353
- return parts;
312
+ if (!raw) return void 0;
313
+ const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
314
+ if (parts.length === 0) return void 0;
315
+ for (const part of parts) if (!KNOWN_HARNESSES.includes(part)) throw new Error(`agent-runtime-mcp: AGENT_RUNTIME_LOCAL_HARNESSES contains unknown harness "${part}". Expected: ${KNOWN_HARNESSES.join(", ")}.`);
316
+ return parts;
354
317
  }
355
318
  async function defaultResolveFleet(sandboxClient, fleetId) {
356
- const fleets = sandboxClient.fleets;
357
- if (!fleets || typeof fleets.get !== "function") {
358
- throw new Error(
359
- "agent-runtime-mcp: the configured sandbox client does not expose `.fleets.get`; upgrade @tangle-network/sandbox to >= 0.2.1 or unset TANGLE_FLEET_ID."
360
- );
361
- }
362
- const raw = await fleets.get(fleetId);
363
- if (!raw || typeof raw !== "object") {
364
- throw new Error(`agent-runtime-mcp: fleets.get(${fleetId}) returned no handle`);
365
- }
366
- const handle = raw;
367
- if (typeof handle.fleetId !== "string" || !Array.isArray(handle.ids)) {
368
- throw new Error(
369
- `agent-runtime-mcp: fleet handle for ${fleetId} is missing fleetId/ids \u2014 incompatible sandbox SDK shape`
370
- );
371
- }
372
- if (typeof handle.sandbox !== "function") {
373
- throw new Error(
374
- `agent-runtime-mcp: fleet handle for ${fleetId} is missing sandbox(machineId) \u2014 incompatible sandbox SDK shape`
375
- );
376
- }
377
- return handle;
319
+ const fleets = sandboxClient.fleets;
320
+ if (!fleets || typeof fleets.get !== "function") throw new Error("agent-runtime-mcp: the configured sandbox client does not expose `.fleets.get`; upgrade @tangle-network/sandbox to >= 0.2.1 or unset TANGLE_FLEET_ID.");
321
+ const raw = await fleets.get(fleetId);
322
+ if (!raw || typeof raw !== "object") throw new Error(`agent-runtime-mcp: fleets.get(${fleetId}) returned no handle`);
323
+ const handle = raw;
324
+ if (typeof handle.fleetId !== "string" || !Array.isArray(handle.ids)) throw new Error(`agent-runtime-mcp: fleet handle for ${fleetId} is missing fleetId/ids — incompatible sandbox SDK shape`);
325
+ if (typeof handle.sandbox !== "function") throw new Error(`agent-runtime-mcp: fleet handle for ${fleetId} is missing sandbox(machineId) — incompatible sandbox SDK shape`);
326
+ return handle;
378
327
  }
379
328
  function parseFleetId(raw) {
380
- if (typeof raw !== "string") return void 0;
381
- const trimmed = raw.trim();
382
- return trimmed.length > 0 ? trimmed : void 0;
329
+ if (typeof raw !== "string") return void 0;
330
+ const trimmed = raw.trim();
331
+ return trimmed.length > 0 ? trimmed : void 0;
383
332
  }
384
333
  function parseList(raw) {
385
- if (!raw) return void 0;
386
- const list = raw.split(",").map((entry) => entry.trim()).filter(Boolean);
387
- return list.length > 0 ? list : void 0;
334
+ if (!raw) return void 0;
335
+ const list = raw.split(",").map((entry) => entry.trim()).filter(Boolean);
336
+ return list.length > 0 ? list : void 0;
388
337
  }
389
-
390
- // src/mcp/detached-coder.ts
391
- var DEFAULT_MAX_DIFF_LINES = 400;
338
+ //#endregion
339
+ //#region src/mcp/detached-coder.ts
340
+ const DEFAULT_MAX_DIFF_LINES = 400;
341
+ /**
342
+ * The minimal default worker profile (§1.5: the system authors profiles — there is no hardcoded
343
+ * coder). Model-only by construction: no skills, no tool grants, no standing prompt. Callers that
344
+ * want a richer worker pass their own `AgentProfile` via `CoderRunSpecOptions.profile`. `harness` /
345
+ * `model` / `systemPrompt` are layered onto whichever profile is used.
346
+ */
392
347
  function minimalCoderProfile() {
393
- return { name: "coder" };
348
+ return { name: "coder" };
394
349
  }
350
+ /** Build the authored `AgentProfile` for one harness on the sandbox-session path: the caller's
351
+ * profile (or the minimal model-only default), with the per-run harness/model/prompt overrides. */
395
352
  function coderRunProfile(options) {
396
- const harness = options.harness ?? "claude-code";
397
- const name = options.name ?? `coder-${harness}`;
398
- const base = options.profile ?? minimalCoderProfile();
399
- return {
400
- ...base,
401
- name,
402
- ...options.systemPrompt ? { prompt: { systemPrompt: options.systemPrompt } } : {},
403
- model: options.model ? { default: options.model } : base.model,
404
- metadata: { ...base.metadata, backendType: harness }
405
- };
353
+ const harness = options.harness ?? "claude-code";
354
+ const name = options.name ?? `coder-${harness}`;
355
+ const base = options.profile ?? minimalCoderProfile();
356
+ return {
357
+ ...base,
358
+ name,
359
+ ...options.systemPrompt ? { prompt: { systemPrompt: options.systemPrompt } } : {},
360
+ model: options.model ? { default: options.model } : base.model,
361
+ metadata: {
362
+ ...base.metadata,
363
+ backendType: harness
364
+ }
365
+ };
406
366
  }
367
+ /** @experimental Build the `AgentRunSpec<CoderTask>` the sandbox-session `runAgentRounds` path drives. */
407
368
  function coderRunSpec(options = {}) {
408
- return {
409
- name: options.name ?? `coder-${options.harness ?? "claude-code"}`,
410
- profile: coderRunProfile(options),
411
- taskToPrompt: coderTaskToPrompt
412
- };
369
+ return {
370
+ name: options.name ?? `coder-${options.harness ?? "claude-code"}`,
371
+ profile: coderRunProfile(options),
372
+ taskToPrompt: coderTaskToPrompt
373
+ };
413
374
  }
414
- var coderOutputAdapter = { parse: parseCoderEvents };
375
+ /** @experimental The output adapter the sandbox-session path decodes the harness stream with. */
376
+ const coderOutputAdapter = { parse: parseCoderEvents };
377
+ /**
378
+ * The multi-harness coder fanout driving the sandbox-session delegate's `variants>1` path.
379
+ * (`worktreeFanout` is the local-repo generic counterpart for new code.)
380
+ *
381
+ * @experimental
382
+ */
415
383
  function multiHarnessCoderFanout(options = {}) {
416
- const harnesses = options.harnesses && options.harnesses.length > 0 ? options.harnesses : ["claude-code", "codex", "opencode/zai-coding-plan/glm-5.1"];
417
- const models = options.models ?? [];
418
- const agentRuns = harnesses.map(
419
- (harness, i) => coderRunSpec({
420
- ...options.profile ? { profile: options.profile } : {},
421
- harness,
422
- model: models[i]
423
- })
424
- );
425
- const driver = {
426
- name: "fanout",
427
- plan: async (task, history) => history.length === 0 ? agentRuns.map(() => task) : [],
428
- decide: (history) => history.some((i) => i.verdict?.valid === true) ? "pick-winner" : "fail"
429
- };
430
- return { agentRuns, output: coderOutputAdapter, validator: defaultCoderValidator(), driver };
384
+ const harnesses = options.harnesses && options.harnesses.length > 0 ? options.harnesses : [
385
+ "claude-code",
386
+ "codex",
387
+ "opencode/zai-coding-plan/glm-5.1"
388
+ ];
389
+ const models = options.models ?? [];
390
+ const agentRuns = harnesses.map((harness, i) => coderRunSpec({
391
+ ...options.profile ? { profile: options.profile } : {},
392
+ harness,
393
+ model: models[i]
394
+ }));
395
+ return {
396
+ agentRuns,
397
+ output: coderOutputAdapter,
398
+ validator: defaultCoderValidator(),
399
+ driver: {
400
+ name: "fanout",
401
+ plan: async (task, history) => history.length === 0 ? agentRuns.map(() => task) : [],
402
+ decide: (history) => history.some((i) => i.verdict?.valid === true) ? "pick-winner" : "fail"
403
+ }
404
+ };
431
405
  }
406
+ /**
407
+ * The sandbox `CoderOutput` validator. A thin shim over the shared {@link runCoderChecks} gate,
408
+ * adapting the parsed `CoderOutput` into the gate inputs.
409
+ *
410
+ * @experimental
411
+ */
432
412
  function createCoderValidator(task) {
433
- const constraints = {
434
- maxDiffLines: task.maxDiffLines ?? DEFAULT_MAX_DIFF_LINES,
435
- forbiddenPaths: task.forbiddenPaths ?? []
436
- };
437
- return {
438
- async validate(output) {
439
- return runCoderChecks(
440
- {
441
- patch: output.patch,
442
- testsPassed: output.testResult.passed,
443
- typecheckPassed: output.typecheckResult.passed
444
- },
445
- constraints
446
- );
447
- }
448
- };
413
+ const constraints = {
414
+ maxDiffLines: task.maxDiffLines ?? DEFAULT_MAX_DIFF_LINES,
415
+ forbiddenPaths: task.forbiddenPaths ?? []
416
+ };
417
+ return { async validate(output) {
418
+ return runCoderChecks({
419
+ patch: output.patch,
420
+ testsPassed: output.testResult.passed,
421
+ typecheckPassed: output.typecheckResult.passed
422
+ }, constraints);
423
+ } };
449
424
  }
450
425
  function defaultCoderValidator() {
451
- return createCoderValidator({
452
- goal: "",
453
- repoRoot: "",
454
- forbiddenPaths: [],
455
- maxDiffLines: DEFAULT_MAX_DIFF_LINES
456
- });
426
+ return createCoderValidator({
427
+ goal: "",
428
+ repoRoot: "",
429
+ forbiddenPaths: [],
430
+ maxDiffLines: DEFAULT_MAX_DIFF_LINES
431
+ });
457
432
  }
433
+ /**
434
+ * Walk the event stream and return the structured coder payload.
435
+ *
436
+ * A `result` / `final` event lifts the structured payload onto `data.result`. That payload is
437
+ * either the in-process executor's raw worktree-harness result (projected onto `CoderOutput`) or an
438
+ * LLM-emitted `CoderOutput`-shaped JSON. When neither is present, the scan accumulates ALL assistant
439
+ * text in stream order (any harness shape) and takes the last fenced JSON block that coerces —
440
+ * claude-code lifts whole text onto `data.text`/`data.delta`; opencode streams `message.part.updated`
441
+ * fragments, so the final block is split across many events and never whole in one.
442
+ */
458
443
  function parseCoderEvents(events) {
459
- for (let i = events.length - 1; i >= 0; i -= 1) {
460
- const event = events[i];
461
- if (!event) continue;
462
- const type = String(event.type ?? "");
463
- const data = isRecord(event.data) ? event.data : {};
464
- if (type === "result" || type === "final" || type === "coder.result") {
465
- const payload = data.result ?? data.output ?? data;
466
- const projected = projectWorktreeArtifact(payload);
467
- if (projected) return projected;
468
- const direct = coerceCoderOutput(payload);
469
- if (direct) return direct;
470
- }
471
- }
472
- const transcript = collectAssistantText(events);
473
- for (const candidate of fencedJsonBlocks(transcript)) {
474
- const coerced = coerceCoderOutput(candidate);
475
- if (coerced) return coerced;
476
- }
477
- return {
478
- branch: "",
479
- patch: "",
480
- testResult: { passed: false, output: "" },
481
- typecheckResult: { passed: false, output: "" },
482
- diffStats: { filesChanged: 0, insertions: 0, deletions: 0 }
483
- };
444
+ for (let i = events.length - 1; i >= 0; i -= 1) {
445
+ const event = events[i];
446
+ if (!event) continue;
447
+ const type = String(event.type ?? "");
448
+ const data = isRecord(event.data) ? event.data : {};
449
+ if (type === "result" || type === "final" || type === "coder.result") {
450
+ const payload = data.result ?? data.output ?? data;
451
+ const projected = projectWorktreeArtifact(payload);
452
+ if (projected) return projected;
453
+ const direct = coerceCoderOutput(payload);
454
+ if (direct) return direct;
455
+ }
456
+ }
457
+ const transcript = collectAssistantText(events);
458
+ for (const candidate of fencedJsonBlocks(transcript)) {
459
+ const coerced = coerceCoderOutput(candidate);
460
+ if (coerced) return coerced;
461
+ }
462
+ return {
463
+ branch: "",
464
+ patch: "",
465
+ testResult: {
466
+ passed: false,
467
+ output: ""
468
+ },
469
+ typecheckResult: {
470
+ passed: false,
471
+ output: ""
472
+ },
473
+ diffStats: {
474
+ filesChanged: 0,
475
+ insertions: 0,
476
+ deletions: 0
477
+ }
478
+ };
484
479
  }
480
+ /** Project the in-process executor's raw worktree-harness result (`{ branch, patch, stats, checks,
481
+ * harness }`) onto `CoderOutput`. A check that did not run is treated as passing (the executor
482
+ * simply didn't run that command). Returns undefined when the payload is not a worktree artifact. */
485
483
  function projectWorktreeArtifact(value) {
486
- if (!isRecord(value)) return void 0;
487
- const stats = value.stats;
488
- if (!isRecord(stats)) return void 0;
489
- const branch = pickString(value.branch) ?? "";
490
- const patch = pickString(value.patch) ?? "";
491
- const checks = isRecord(value.checks) ? value.checks : {};
492
- const tests = isRecord(checks.tests) ? checks.tests : void 0;
493
- const typecheck = isRecord(checks.typecheck) ? checks.typecheck : void 0;
494
- const harness = isRecord(value.harness) ? value.harness : void 0;
495
- const exitCode = harness ? toFiniteInt(harness.exitCode) : 0;
496
- const timedOut = harness?.timedOut === true;
497
- const harnessName = harness ? pickString(harness.name) ?? "harness" : "harness";
498
- return {
499
- branch,
500
- patch,
501
- testResult: {
502
- passed: tests ? tests.passed === true : true,
503
- output: tail(pickString(tests?.output) ?? "", 4e3)
504
- },
505
- typecheckResult: {
506
- passed: typecheck ? typecheck.passed === true : true,
507
- output: tail(pickString(typecheck?.output) ?? "", 4e3)
508
- },
509
- diffStats: {
510
- filesChanged: toFiniteInt(stats.filesChanged),
511
- insertions: toFiniteInt(stats.insertions),
512
- deletions: toFiniteInt(stats.deletions)
513
- },
514
- ...exitCode !== 0 ? {
515
- reviewerNotes: `harness ${harnessName} exited ${exitCode}${timedOut ? " (timed out)" : ""}`
516
- } : {}
517
- };
484
+ if (!isRecord(value)) return void 0;
485
+ const stats = value.stats;
486
+ if (!isRecord(stats)) return void 0;
487
+ const branch = pickString(value.branch) ?? "";
488
+ const patch = pickString(value.patch) ?? "";
489
+ const checks = isRecord(value.checks) ? value.checks : {};
490
+ const tests = isRecord(checks.tests) ? checks.tests : void 0;
491
+ const typecheck = isRecord(checks.typecheck) ? checks.typecheck : void 0;
492
+ const harness = isRecord(value.harness) ? value.harness : void 0;
493
+ const exitCode = harness ? toFiniteInt(harness.exitCode) : 0;
494
+ const timedOut = harness?.timedOut === true;
495
+ const harnessName = harness ? pickString(harness.name) ?? "harness" : "harness";
496
+ return {
497
+ branch,
498
+ patch,
499
+ testResult: {
500
+ passed: tests ? tests.passed === true : true,
501
+ output: tail(pickString(tests?.output) ?? "", 4e3)
502
+ },
503
+ typecheckResult: {
504
+ passed: typecheck ? typecheck.passed === true : true,
505
+ output: tail(pickString(typecheck?.output) ?? "", 4e3)
506
+ },
507
+ diffStats: {
508
+ filesChanged: toFiniteInt(stats.filesChanged),
509
+ insertions: toFiniteInt(stats.insertions),
510
+ deletions: toFiniteInt(stats.deletions)
511
+ },
512
+ ...exitCode !== 0 ? { reviewerNotes: `harness ${harnessName} exited ${exitCode}${timedOut ? " (timed out)" : ""}` } : {}
513
+ };
518
514
  }
519
515
  function isRecord(value) {
520
- return value !== null && typeof value === "object" && !Array.isArray(value);
516
+ return value !== null && typeof value === "object" && !Array.isArray(value);
521
517
  }
518
+ /** Keep the last `max` chars of a diagnostic string — harness stdout can be large; the gate reads
519
+ * `passed`, not this text, so only the tail is retained for traces/logs. */
522
520
  function tail(text, max) {
523
- return text.length <= max ? text : text.slice(text.length - max);
521
+ return text.length <= max ? text : text.slice(text.length - max);
524
522
  }
525
523
  function pickString(value) {
526
- return typeof value === "string" && value.length > 0 ? value : void 0;
524
+ return typeof value === "string" && value.length > 0 ? value : void 0;
527
525
  }
526
+ /**
527
+ * Concatenate assistant text across the event stream in arrival order, tolerating every harness
528
+ * shape: claude-code lifts text onto `data.text`/`data.delta`; opencode streams
529
+ * `message.part.updated` with `data.part.type === 'text'` carrying `data.delta`/`data.part.text`.
530
+ * Reasoning/thinking parts are excluded — only the final answer text carries the result JSON.
531
+ */
528
532
  function collectAssistantText(events) {
529
- const chunks = [];
530
- for (const event of events) {
531
- if (!event) continue;
532
- const data = isRecord(event.data) ? event.data : {};
533
- if (String(event.type ?? "") === "message.part.updated") {
534
- const part = isRecord(data.part) ? data.part : {};
535
- const partType = String(part.type ?? "");
536
- if (partType !== "text" && partType !== "") continue;
537
- const text2 = pickString(data.delta) ?? pickString(part.text);
538
- if (text2) chunks.push(text2);
539
- continue;
540
- }
541
- const text = pickString(data.text) ?? pickString(data.delta);
542
- if (text) chunks.push(text);
543
- }
544
- return chunks.join("");
533
+ const chunks = [];
534
+ for (const event of events) {
535
+ if (!event) continue;
536
+ const data = isRecord(event.data) ? event.data : {};
537
+ if (String(event.type ?? "") === "message.part.updated") {
538
+ const part = isRecord(data.part) ? data.part : {};
539
+ const partType = String(part.type ?? "");
540
+ if (partType !== "text" && partType !== "") continue;
541
+ const text = pickString(data.delta) ?? pickString(part.text);
542
+ if (text) chunks.push(text);
543
+ continue;
544
+ }
545
+ const text = pickString(data.text) ?? pickString(data.delta);
546
+ if (text) chunks.push(text);
547
+ }
548
+ return chunks.join("");
545
549
  }
550
+ /** All parseable fenced JSON blocks in `text`, last-first (the final result block the agent emits
551
+ * is the one we want). */
546
552
  function fencedJsonBlocks(text) {
547
- const out = [];
548
- const matches = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)];
549
- for (let i = matches.length - 1; i >= 0; i -= 1) {
550
- const body = (matches[i]?.[1] ?? "").trim();
551
- if (!body) continue;
552
- try {
553
- out.push(JSON.parse(body));
554
- } catch {
555
- }
556
- }
557
- return out;
553
+ const out = [];
554
+ const matches = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)];
555
+ for (let i = matches.length - 1; i >= 0; i -= 1) {
556
+ const body = (matches[i]?.[1] ?? "").trim();
557
+ if (!body) continue;
558
+ try {
559
+ out.push(JSON.parse(body));
560
+ } catch {}
561
+ }
562
+ return out;
558
563
  }
559
564
  function coerceCoderOutput(value) {
560
- if (!isRecord(value)) return void 0;
561
- const branch = pickString(value.branch);
562
- const patch = pickString(value.patch) ?? "";
563
- if (branch === void 0) return void 0;
564
- return {
565
- branch,
566
- patch,
567
- testResult: coerceCmdResult(value.testResult),
568
- typecheckResult: coerceCmdResult(value.typecheckResult),
569
- diffStats: coerceDiffStats(value.diffStats),
570
- reviewerNotes: pickString(value.reviewerNotes)
571
- };
565
+ if (!isRecord(value)) return void 0;
566
+ const branch = pickString(value.branch);
567
+ const patch = pickString(value.patch) ?? "";
568
+ if (branch === void 0) return void 0;
569
+ return {
570
+ branch,
571
+ patch,
572
+ testResult: coerceCmdResult(value.testResult),
573
+ typecheckResult: coerceCmdResult(value.typecheckResult),
574
+ diffStats: coerceDiffStats(value.diffStats),
575
+ reviewerNotes: pickString(value.reviewerNotes)
576
+ };
572
577
  }
573
578
  function coerceCmdResult(value) {
574
- if (!isRecord(value)) return { passed: false, output: "" };
575
- return { passed: value.passed === true, output: pickString(value.output) ?? "" };
579
+ if (!isRecord(value)) return {
580
+ passed: false,
581
+ output: ""
582
+ };
583
+ return {
584
+ passed: value.passed === true,
585
+ output: pickString(value.output) ?? ""
586
+ };
576
587
  }
577
588
  function coerceDiffStats(value) {
578
- if (!isRecord(value)) return { filesChanged: 0, insertions: 0, deletions: 0 };
579
- return {
580
- filesChanged: toFiniteInt(value.filesChanged),
581
- insertions: toFiniteInt(value.insertions),
582
- deletions: toFiniteInt(value.deletions)
583
- };
589
+ if (!isRecord(value)) return {
590
+ filesChanged: 0,
591
+ insertions: 0,
592
+ deletions: 0
593
+ };
594
+ return {
595
+ filesChanged: toFiniteInt(value.filesChanged),
596
+ insertions: toFiniteInt(value.insertions),
597
+ deletions: toFiniteInt(value.deletions)
598
+ };
584
599
  }
585
600
  function toFiniteInt(value) {
586
- if (typeof value !== "number") return 0;
587
- if (!Number.isFinite(value)) return 0;
588
- return Math.max(0, Math.trunc(value));
601
+ if (typeof value !== "number") return 0;
602
+ if (!Number.isFinite(value)) return 0;
603
+ return Math.max(0, Math.trunc(value));
589
604
  }
590
-
591
- // src/mcp/detached-turn.ts
592
- var DEFAULT_TICK_INTERVAL_MS = 5e3;
605
+ //#endregion
606
+ //#region src/mcp/detached-turn.ts
607
+ const DEFAULT_TICK_INTERVAL_MS = 5e3;
608
+ /**
609
+ * Encode ref parts into the JSON-safe string stored on the record:
610
+ * `session=<id>` before the box exists, `sandbox=<id>;session=<id>` once
611
+ * bound. Ids must not contain the `;`/`=` delimiters.
612
+ *
613
+ * @experimental
614
+ */
593
615
  function formatDetachedSessionRef(parts) {
594
- assertRefComponent("sessionId", parts.sessionId);
595
- if (parts.sandboxId === void 0) return `session=${parts.sessionId}`;
596
- assertRefComponent("sandboxId", parts.sandboxId);
597
- return `sandbox=${parts.sandboxId};session=${parts.sessionId}`;
616
+ assertRefComponent("sessionId", parts.sessionId);
617
+ if (parts.sandboxId === void 0) return `session=${parts.sessionId}`;
618
+ assertRefComponent("sandboxId", parts.sandboxId);
619
+ return `sandbox=${parts.sandboxId};session=${parts.sessionId}`;
598
620
  }
621
+ /** Parse a `detachedSessionRef` string back to parts; throws `ValidationError` on malformed input. @experimental */
599
622
  function parseDetachedSessionRef(raw) {
600
- const fields = /* @__PURE__ */ new Map();
601
- for (const pair of raw.split(";")) {
602
- const eq = pair.indexOf("=");
603
- const key = eq === -1 ? "" : pair.slice(0, eq);
604
- const value = eq === -1 ? "" : pair.slice(eq + 1);
605
- if (key !== "session" && key !== "sandbox" || value.length === 0 || fields.has(key)) {
606
- throw new ValidationError(
607
- `parseDetachedSessionRef: malformed detachedSessionRef ${JSON.stringify(raw)} \u2014 expected "session=<id>" or "sandbox=<id>;session=<id>"`
608
- );
609
- }
610
- fields.set(key, value);
611
- }
612
- const sessionId = fields.get("session");
613
- if (!sessionId) {
614
- throw new ValidationError(
615
- `parseDetachedSessionRef: detachedSessionRef ${JSON.stringify(raw)} carries no session id`
616
- );
617
- }
618
- const sandboxId = fields.get("sandbox");
619
- return { sessionId, ...sandboxId !== void 0 ? { sandboxId } : {} };
623
+ const fields = /* @__PURE__ */ new Map();
624
+ for (const pair of raw.split(";")) {
625
+ const eq = pair.indexOf("=");
626
+ const key = eq === -1 ? "" : pair.slice(0, eq);
627
+ const value = eq === -1 ? "" : pair.slice(eq + 1);
628
+ if (key !== "session" && key !== "sandbox" || value.length === 0 || fields.has(key)) throw new ValidationError(`parseDetachedSessionRef: malformed detachedSessionRef ${JSON.stringify(raw)} — expected "session=<id>" or "sandbox=<id>;session=<id>"`);
629
+ fields.set(key, value);
630
+ }
631
+ const sessionId = fields.get("session");
632
+ if (!sessionId) throw new ValidationError(`parseDetachedSessionRef: detachedSessionRef ${JSON.stringify(raw)} carries no session id`);
633
+ const sandboxId = fields.get("sandbox");
634
+ return {
635
+ sessionId,
636
+ ...sandboxId !== void 0 ? { sandboxId } : {}
637
+ };
620
638
  }
621
639
  function assertRefComponent(name, value) {
622
- if (value.length === 0 || value.includes(";") || value.includes("=")) {
623
- throw new ValidationError(
624
- `formatDetachedSessionRef: ${name} ${JSON.stringify(value)} must be non-empty and free of ";" / "="`
625
- );
626
- }
640
+ if (value.length === 0 || value.includes(";") || value.includes("=")) throw new ValidationError(`formatDetachedSessionRef: ${name} ${JSON.stringify(value)} must be non-empty and free of ";" / "="`);
627
641
  }
642
+ /**
643
+ * Synthesize the terminal event array a detached turn settles through. Shaped
644
+ * so the existing event-stream output adapters (coder, researcher) parse it:
645
+ * `data.result` for adapters that read a structured terminal record, `data.text`
646
+ * for adapters that scan assistant text for the fenced result block.
647
+ *
648
+ * @experimental
649
+ */
628
650
  function detachedTurnEvents(sessionId, turn) {
629
- return [
630
- {
631
- type: "result",
632
- id: sessionId,
633
- data: {
634
- text: turn.text,
635
- finalText: turn.text,
636
- success: true,
637
- result: turn.result
638
- }
639
- }
640
- ];
651
+ return [{
652
+ type: "result",
653
+ id: sessionId,
654
+ data: {
655
+ text: turn.text,
656
+ finalText: turn.text,
657
+ success: true,
658
+ result: turn.result
659
+ }
660
+ }];
641
661
  }
662
+ /**
663
+ * Dispatch one detached turn and advance it to a terminal state with
664
+ * `driveTurn` ticks. The first tick dispatches (idempotent on `sessionId`);
665
+ * subsequent ticks poll. On abort the remote session is cancelled via
666
+ * `_sessionCancel` when the box exposes it. The box is torn down on every
667
+ * in-process exit path (success, failure, abort) — only a process death skips
668
+ * teardown, which is exactly the case the resume driver re-attaches to.
669
+ *
670
+ * @experimental
671
+ */
642
672
  async function runDetachedTurn(options) {
643
- const intervalMs = options.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS;
644
- const trace = createDetachedTurnTrace(options);
645
- trace.started();
646
- const box = await createSandboxForSpec(options.client, options.spec, options.signal).catch(
647
- (err) => {
648
- trace.ended(err instanceof Error ? err.message : String(err));
649
- throw err;
650
- }
651
- );
652
- const drive = box;
653
- const onAbort = () => {
654
- void drive._sessionCancel?.(options.sessionId).catch(() => {
655
- });
656
- };
657
- try {
658
- if (typeof drive.driveTurn !== "function") {
659
- throw new ValidationError(
660
- "runDetachedTurn: the acquired sandbox exposes no driveTurn(message, { sessionId }) \u2014 detached dispatch requires @tangle-network/sandbox >= 0.6 and a session-backed placement (sibling/fleet); disable detached dispatch for this executor."
661
- );
662
- }
663
- const sandboxId = box.id;
664
- if (typeof sandboxId !== "string" || sandboxId.length === 0) {
665
- throw new ValidationError(
666
- "runDetachedTurn: the acquired sandbox carries no id \u2014 without it the detached run cannot be resumed after a restart, so refusing to dispatch detached."
667
- );
668
- }
669
- options.bindSandbox(sandboxId);
670
- trace.dispatched(sandboxId);
671
- options.signal.addEventListener("abort", onAbort, { once: true });
672
- for (; ; ) {
673
- throwIfAborted(options.signal);
674
- const tick = await drive.driveTurn(options.prompt, {
675
- sessionId: options.sessionId,
676
- turnId: options.sessionId,
677
- ...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
678
- });
679
- throwIfAborted(options.signal);
680
- if (tick.state === "completed") {
681
- trace.ended();
682
- return { text: tick.text, result: tick.result };
683
- }
684
- if (tick.state === "failed") {
685
- throw new Error(`detached turn ${options.sessionId} failed: ${tick.error}`);
686
- }
687
- options.report({ iteration: 0, phase: detachedRunningPhase(tick.elapsedMs) });
688
- await sleep(intervalMs, options.signal);
689
- }
690
- } catch (err) {
691
- trace.ended(err instanceof Error ? err.message : String(err));
692
- throw err;
693
- } finally {
694
- options.signal.removeEventListener("abort", onAbort);
695
- if (options.signal.aborted) onAbort();
696
- await deleteBoxSafe(box);
697
- }
673
+ const intervalMs = options.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS;
674
+ const trace = createDetachedTurnTrace(options);
675
+ trace.started();
676
+ const box = await createSandboxForSpec(options.client, options.spec, options.signal).catch((err) => {
677
+ trace.ended(err instanceof Error ? err.message : String(err));
678
+ throw err;
679
+ });
680
+ const drive = box;
681
+ const onAbort = () => {
682
+ drive._sessionCancel?.(options.sessionId).catch(() => {});
683
+ };
684
+ try {
685
+ if (typeof drive.driveTurn !== "function") throw new ValidationError("runDetachedTurn: the acquired sandbox exposes no driveTurn(message, { sessionId }) — detached dispatch requires @tangle-network/sandbox >= 0.6 and a session-backed placement (sibling/fleet); disable detached dispatch for this executor.");
686
+ const sandboxId = box.id;
687
+ if (typeof sandboxId !== "string" || sandboxId.length === 0) throw new ValidationError("runDetachedTurn: the acquired sandbox carries no id — without it the detached run cannot be resumed after a restart, so refusing to dispatch detached.");
688
+ options.bindSandbox(sandboxId);
689
+ trace.dispatched(sandboxId);
690
+ options.signal.addEventListener("abort", onAbort, { once: true });
691
+ for (;;) {
692
+ throwIfAborted(options.signal);
693
+ const tick = await drive.driveTurn(options.prompt, {
694
+ sessionId: options.sessionId,
695
+ turnId: options.sessionId,
696
+ ...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
697
+ });
698
+ throwIfAborted(options.signal);
699
+ if (tick.state === "completed") {
700
+ trace.ended();
701
+ return {
702
+ text: tick.text,
703
+ result: tick.result
704
+ };
705
+ }
706
+ if (tick.state === "failed") throw new Error(`detached turn ${options.sessionId} failed: ${tick.error}`);
707
+ options.report({
708
+ iteration: 0,
709
+ phase: detachedRunningPhase(tick.elapsedMs)
710
+ });
711
+ await sleep(intervalMs, options.signal);
712
+ }
713
+ } catch (err) {
714
+ trace.ended(err instanceof Error ? err.message : String(err));
715
+ throw err;
716
+ } finally {
717
+ options.signal.removeEventListener("abort", onAbort);
718
+ if (options.signal.aborted) onAbort();
719
+ await deleteBoxSafe(box);
720
+ }
698
721
  }
722
+ /**
723
+ * Synthesize the single-iteration loop event stream for one detached turn so
724
+ * the trace sinks (OTEL exporter, delegation journal) observe detached work
725
+ * exactly like a streamed `runAgentRounds` run. `runId` = the deterministic session
726
+ * id; cost/token figures are structurally unavailable on the `driveTurn`
727
+ * surface and emitted as 0 under the `'detached-turn'` driver tag.
728
+ */
699
729
  function createDetachedTurnTrace(options) {
700
- const emitter = options.traceEmitter;
701
- if (!emitter) {
702
- return { started() {
703
- }, dispatched() {
704
- }, ended() {
705
- } };
706
- }
707
- const runId = options.sessionId;
708
- const agentRunName = options.spec.name ?? options.spec.profile.name ?? "detached-turn";
709
- const startMs = Date.now();
710
- let done = false;
711
- const emit = (event) => {
712
- void emitter.emit(event);
713
- };
714
- return {
715
- started() {
716
- emit({
717
- kind: "loop.started",
718
- runId,
719
- timestamp: startMs,
720
- payload: {
721
- driver: "detached-turn",
722
- agentRunNames: [agentRunName],
723
- maxIterations: 1,
724
- maxConcurrency: 1
725
- }
726
- });
727
- emit({
728
- kind: "loop.iteration.started",
729
- runId,
730
- timestamp: startMs,
731
- payload: { iterationIndex: 0, agentRunName, taskHash: options.sessionId }
732
- });
733
- },
734
- dispatched(sandboxId) {
735
- emit({
736
- kind: "loop.iteration.dispatch",
737
- runId,
738
- timestamp: Date.now(),
739
- payload: {
740
- iterationIndex: 0,
741
- agentRunName,
742
- placement: options.placement ?? "sibling",
743
- sandboxId
744
- }
745
- });
746
- },
747
- ended(error) {
748
- if (done) return;
749
- done = true;
750
- const endMs = Date.now();
751
- emit({
752
- kind: "loop.iteration.ended",
753
- runId,
754
- timestamp: endMs,
755
- payload: {
756
- iterationIndex: 0,
757
- agentRunName,
758
- costUsd: 0,
759
- durationMs: endMs - startMs,
760
- ...error !== void 0 ? { error } : {}
761
- }
762
- });
763
- emit({
764
- kind: "loop.ended",
765
- runId,
766
- timestamp: endMs,
767
- payload: {
768
- ...error === void 0 ? { winnerIterationIndex: 0 } : {},
769
- totalCostUsd: 0,
770
- durationMs: endMs - startMs,
771
- iterations: 1
772
- }
773
- });
774
- }
775
- };
730
+ const emitter = options.traceEmitter;
731
+ if (!emitter) return {
732
+ started() {},
733
+ dispatched() {},
734
+ ended() {}
735
+ };
736
+ const runId = options.sessionId;
737
+ const agentRunName = options.spec.name ?? options.spec.profile.name ?? "detached-turn";
738
+ const startMs = Date.now();
739
+ let done = false;
740
+ const emit = (event) => {
741
+ emitter.emit(event);
742
+ };
743
+ return {
744
+ started() {
745
+ emit({
746
+ kind: "loop.started",
747
+ runId,
748
+ timestamp: startMs,
749
+ payload: {
750
+ driver: "detached-turn",
751
+ agentRunNames: [agentRunName],
752
+ maxIterations: 1,
753
+ maxConcurrency: 1
754
+ }
755
+ });
756
+ emit({
757
+ kind: "loop.iteration.started",
758
+ runId,
759
+ timestamp: startMs,
760
+ payload: {
761
+ iterationIndex: 0,
762
+ agentRunName,
763
+ taskHash: options.sessionId
764
+ }
765
+ });
766
+ },
767
+ dispatched(sandboxId) {
768
+ emit({
769
+ kind: "loop.iteration.dispatch",
770
+ runId,
771
+ timestamp: Date.now(),
772
+ payload: {
773
+ iterationIndex: 0,
774
+ agentRunName,
775
+ placement: options.placement ?? "sibling",
776
+ sandboxId
777
+ }
778
+ });
779
+ },
780
+ ended(error) {
781
+ if (done) return;
782
+ done = true;
783
+ const endMs = Date.now();
784
+ emit({
785
+ kind: "loop.iteration.ended",
786
+ runId,
787
+ timestamp: endMs,
788
+ payload: {
789
+ iterationIndex: 0,
790
+ agentRunName,
791
+ costUsd: 0,
792
+ durationMs: endMs - startMs,
793
+ ...error !== void 0 ? { error } : {}
794
+ }
795
+ });
796
+ emit({
797
+ kind: "loop.ended",
798
+ runId,
799
+ timestamp: endMs,
800
+ payload: {
801
+ ...error === void 0 ? { winnerIterationIndex: 0 } : {},
802
+ totalCostUsd: 0,
803
+ durationMs: endMs - startMs,
804
+ iterations: 1
805
+ }
806
+ });
807
+ }
808
+ };
776
809
  }
777
810
  function detachedRunningPhase(elapsedMs) {
778
- return elapsedMs === void 0 ? "detached-running" : `detached-running ${Math.round(elapsedMs / 1e3)}s`;
811
+ return elapsedMs === void 0 ? "detached-running" : `detached-running ${Math.round(elapsedMs / 1e3)}s`;
779
812
  }
813
+ /**
814
+ * Build the `driveTurn`-backed {@link DelegationResumeDriver}. Each `tick()`
815
+ * is one settle/poll/dispatch pass:
816
+ *
817
+ * - ref without a sandbox binding → `failed` (`DetachedSessionUnboundError`):
818
+ * the previous process died before a box existed; there is nothing to resume.
819
+ * - `driveTurn` `completed` → `settleOutput` → `completed` tick.
820
+ * - `running` → progress via `ctx.report`, `running` tick (queue re-ticks
821
+ * after `intervalMs`).
822
+ * - `failed` → `failed` tick (`DetachedTurnFailedError`) — terminal per the
823
+ * SDK's deterministic-failure contract.
824
+ *
825
+ * Abort: the queue stops ticking once `cancel()` flips the record, so remote
826
+ * cancellation is hooked onto `ctx.signal` (once per task) and fires
827
+ * `_sessionCancel` when the SDK surface exposes it. The driver never deletes
828
+ * boxes — it cannot know whether `sandboxId` is a disposable sibling or a
829
+ * fleet machine, and destroying a fleet machine would be unrecoverable.
830
+ *
831
+ * @experimental
832
+ */
780
833
  function createDetachedTurnResumeDriver(options) {
781
- const cancelHooked = /* @__PURE__ */ new Set();
782
- return {
783
- intervalMs: options.intervalMs ?? DEFAULT_TICK_INTERVAL_MS,
784
- async tick({ record, detachedSessionRef }, ctx) {
785
- const ref = parseDetachedSessionRef(detachedSessionRef);
786
- if (ref.sandboxId === void 0) {
787
- return {
788
- state: "failed",
789
- error: {
790
- message: `detached session "${ref.sessionId}" was never bound to a sandbox \u2014 the previous process died before the box was acquired, so the turn was never dispatched and cannot be resumed`,
791
- kind: "DetachedSessionUnboundError"
792
- }
793
- };
794
- }
795
- const box = await options.resolveSandbox(ref.sandboxId);
796
- if (!cancelHooked.has(record.taskId)) {
797
- cancelHooked.add(record.taskId);
798
- ctx.signal.addEventListener(
799
- "abort",
800
- () => {
801
- void box._sessionCancel?.(ref.sessionId).catch(() => {
802
- });
803
- },
804
- { once: true }
805
- );
806
- }
807
- if (ctx.signal.aborted) throwAbort();
808
- const tick = await box.driveTurn(options.buildMessage(record), {
809
- sessionId: ref.sessionId,
810
- turnId: ref.sessionId,
811
- ...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
812
- });
813
- if (tick.state === "completed") {
814
- const output = await options.settleOutput(
815
- { text: tick.text, result: tick.result },
816
- record,
817
- {
818
- signal: ctx.signal
819
- }
820
- );
821
- return { state: "completed", output };
822
- }
823
- if (tick.state === "failed") {
824
- return {
825
- state: "failed",
826
- error: {
827
- message: `detached turn ${ref.sessionId} failed: ${tick.error}`,
828
- kind: "DetachedTurnFailedError"
829
- }
830
- };
831
- }
832
- ctx.report({ iteration: 0, phase: detachedRunningPhase(tick.elapsedMs) });
833
- return { state: "running" };
834
- }
835
- };
834
+ const cancelHooked = /* @__PURE__ */ new Set();
835
+ return {
836
+ intervalMs: options.intervalMs ?? DEFAULT_TICK_INTERVAL_MS,
837
+ async tick({ record, detachedSessionRef }, ctx) {
838
+ const ref = parseDetachedSessionRef(detachedSessionRef);
839
+ if (ref.sandboxId === void 0) return {
840
+ state: "failed",
841
+ error: {
842
+ message: `detached session "${ref.sessionId}" was never bound to a sandbox — the previous process died before the box was acquired, so the turn was never dispatched and cannot be resumed`,
843
+ kind: "DetachedSessionUnboundError"
844
+ }
845
+ };
846
+ const box = await options.resolveSandbox(ref.sandboxId);
847
+ if (!cancelHooked.has(record.taskId)) {
848
+ cancelHooked.add(record.taskId);
849
+ ctx.signal.addEventListener("abort", () => {
850
+ box._sessionCancel?.(ref.sessionId).catch(() => {});
851
+ }, { once: true });
852
+ }
853
+ if (ctx.signal.aborted) throwAbort();
854
+ const tick = await box.driveTurn(options.buildMessage(record), {
855
+ sessionId: ref.sessionId,
856
+ turnId: ref.sessionId,
857
+ ...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
858
+ });
859
+ if (tick.state === "completed") return {
860
+ state: "completed",
861
+ output: await options.settleOutput({
862
+ text: tick.text,
863
+ result: tick.result
864
+ }, record, { signal: ctx.signal })
865
+ };
866
+ if (tick.state === "failed") return {
867
+ state: "failed",
868
+ error: {
869
+ message: `detached turn ${ref.sessionId} failed: ${tick.error}`,
870
+ kind: "DetachedTurnFailedError"
871
+ }
872
+ };
873
+ ctx.report({
874
+ iteration: 0,
875
+ phase: detachedRunningPhase(tick.elapsedMs)
876
+ });
877
+ return { state: "running" };
878
+ }
879
+ };
836
880
  }
837
-
838
- // src/mcp/delegates.ts
881
+ //#endregion
882
+ //#region src/mcp/delegates.ts
883
+ /**
884
+ * Build the sandbox-session coder delegate. It drives `runAgentRounds` against the project's
885
+ * sandbox client + coder profile; when `args.variants > 1` it switches to the multi-harness fanout
886
+ * topology.
887
+ *
888
+ * This is the SANDBOX-SESSION coder path: workers run the in-box harness via the
889
+ * `SandboxClient`'s `streamPrompt`, and single-variant turns can dispatch DETACHED
890
+ * (driveTurn ticks) so a durable queue resumes them across an MCP restart — a substrate
891
+ * the recursive worktree-CLI leaf does not yet have a journal-replay equivalent for.
892
+ *
893
+ * For NEW local-repo coding use `worktreeFanout` / `worktreeLoopRunner` (author an `AgentProfile`
894
+ * per harness → `createWorktreeCliExecutor` leaves → `gateOnDeliverable`). This delegate runs
895
+ * held-stream by default and only its OPTIONAL cross-restart resume (the `driveTurn` tick) is opt-in
896
+ * behind `MCP_ENABLE_DETACHED_RESUME`.
897
+ *
898
+ * @experimental
899
+ */
839
900
  function detachedSessionDelegate(options) {
840
- const executor = resolveExecutor(options);
841
- const sandboxClient = executor.client;
842
- const fanoutHarnesses = options.fanoutHarnesses;
843
- const maxConcurrency = options.maxConcurrency ?? 4;
844
- const traceEmitter = options.traceEmitter;
845
- return async (args, ctx) => {
846
- const task = coderTaskFromArgs(args);
847
- const variants = Math.max(1, Math.trunc(args.variants ?? 1));
848
- const loopEmitter = composeLoopTraceEmitters(traceEmitter, ctx.traceEmitter);
849
- ctx.report({ iteration: 0, phase: "starting" });
850
- if (variants <= 1) {
851
- const agentRunSpec = coderRunSpec({
852
- ...options.workerProfile ? { profile: options.workerProfile } : {},
853
- ...options.harness ? { harness: options.harness } : {},
854
- ...options.model ? { model: options.model } : {},
855
- ...options.systemPrompt ? { systemPrompt: options.systemPrompt } : {}
856
- });
857
- const output = coderOutputAdapter;
858
- const validator = createCoderValidator(task);
859
- if (ctx.detachedSessionRef !== void 0 && ctx.updateDetachedSessionRef) {
860
- const { sessionId } = parseDetachedSessionRef(ctx.detachedSessionRef);
861
- const rebind = ctx.updateDetachedSessionRef;
862
- const turn = await runDetachedTurn({
863
- client: sandboxClient,
864
- spec: agentRunSpec,
865
- prompt: agentRunSpec.taskToPrompt(task),
866
- sessionId,
867
- bindSandbox: (sandboxId) => rebind(formatDetachedSessionRef({ sandboxId, sessionId })),
868
- signal: ctx.signal,
869
- report: ctx.report,
870
- ...loopEmitter ? { traceEmitter: loopEmitter } : {},
871
- ...executor.placement === "fleet" ? { placement: "fleet" } : {},
872
- ...options.detachedTickIntervalMs !== void 0 ? { tickIntervalMs: options.detachedTickIntervalMs } : {},
873
- ...options.detachedWallCapMs !== void 0 ? { wallCapMs: options.detachedWallCapMs } : {}
874
- });
875
- const chosen3 = await settleDetachedCoderTurn(turn, {
876
- task,
877
- sessionId,
878
- signal: ctx.signal,
879
- ...options.harness ? { harness: options.harness } : {},
880
- ...options.model ? { model: options.model } : {},
881
- ...options.reviewer ? { reviewer: options.reviewer } : {}
882
- });
883
- ctx.report({ iteration: 1, phase: "completed" });
884
- return chosen3;
885
- }
886
- const result2 = await runAgentRounds({
887
- driver: singleShotDriver,
888
- agentRun: agentRunSpec,
889
- output,
890
- validator,
891
- task,
892
- ctx: {
893
- sandboxClient,
894
- signal: ctx.signal,
895
- ...loopEmitter ? { traceEmitter: loopEmitter } : {}
896
- },
897
- maxIterations: 1,
898
- maxConcurrency
899
- });
900
- const chosen2 = await pickCoderWinner({
901
- iterations: result2.iterations,
902
- reviewer: options.reviewer,
903
- selection: options.winnerSelection ?? "highest-score",
904
- task,
905
- signal: ctx.signal
906
- });
907
- if (!chosen2) throw new Error(noWinnerMessage(options.reviewer));
908
- ctx.report({ iteration: 1, phase: "completed" });
909
- return chosen2;
910
- }
911
- const fanout = multiHarnessCoderFanout({
912
- ...options.workerProfile ? { profile: options.workerProfile } : {},
913
- ...fanoutHarnesses && fanoutHarnesses.length > 0 ? { harnesses: fanoutHarnesses.slice(0, variants) } : {},
914
- ...options.fanoutModels ? { models: options.fanoutModels.slice(0, variants) } : {}
915
- });
916
- const agentRuns = fanout.agentRuns.slice(0, variants);
917
- const result = await runAgentRounds({
918
- driver: fanout.driver,
919
- agentRuns,
920
- output: fanout.output,
921
- validator: fanout.validator,
922
- task,
923
- ctx: {
924
- sandboxClient,
925
- signal: ctx.signal,
926
- ...loopEmitter ? { traceEmitter: loopEmitter } : {}
927
- },
928
- maxIterations: variants,
929
- maxConcurrency: Math.min(maxConcurrency, variants)
930
- });
931
- const chosen = await pickCoderWinner({
932
- iterations: result.iterations,
933
- reviewer: options.reviewer,
934
- selection: options.winnerSelection ?? "highest-score",
935
- task,
936
- signal: ctx.signal
937
- });
938
- if (!chosen) throw new Error(noWinnerMessage(options.reviewer));
939
- ctx.report({ iteration: agentRuns.length, phase: "completed" });
940
- return chosen;
941
- };
901
+ const executor = resolveExecutor(options);
902
+ const sandboxClient = executor.client;
903
+ const fanoutHarnesses = options.fanoutHarnesses;
904
+ const maxConcurrency = options.maxConcurrency ?? 4;
905
+ const traceEmitter = options.traceEmitter;
906
+ return async (args, ctx) => {
907
+ const task = coderTaskFromArgs(args);
908
+ const variants = Math.max(1, Math.trunc(args.variants ?? 1));
909
+ const loopEmitter = composeLoopTraceEmitters(traceEmitter, ctx.traceEmitter);
910
+ ctx.report({
911
+ iteration: 0,
912
+ phase: "starting"
913
+ });
914
+ if (variants <= 1) {
915
+ const agentRunSpec = coderRunSpec({
916
+ ...options.workerProfile ? { profile: options.workerProfile } : {},
917
+ ...options.harness ? { harness: options.harness } : {},
918
+ ...options.model ? { model: options.model } : {},
919
+ ...options.systemPrompt ? { systemPrompt: options.systemPrompt } : {}
920
+ });
921
+ const output = coderOutputAdapter;
922
+ const validator = createCoderValidator(task);
923
+ if (ctx.detachedSessionRef !== void 0 && ctx.updateDetachedSessionRef) {
924
+ const { sessionId } = parseDetachedSessionRef(ctx.detachedSessionRef);
925
+ const rebind = ctx.updateDetachedSessionRef;
926
+ const chosen = await settleDetachedCoderTurn(await runDetachedTurn({
927
+ client: sandboxClient,
928
+ spec: agentRunSpec,
929
+ prompt: agentRunSpec.taskToPrompt(task),
930
+ sessionId,
931
+ bindSandbox: (sandboxId) => rebind(formatDetachedSessionRef({
932
+ sandboxId,
933
+ sessionId
934
+ })),
935
+ signal: ctx.signal,
936
+ report: ctx.report,
937
+ ...loopEmitter ? { traceEmitter: loopEmitter } : {},
938
+ ...executor.placement === "fleet" ? { placement: "fleet" } : {},
939
+ ...options.detachedTickIntervalMs !== void 0 ? { tickIntervalMs: options.detachedTickIntervalMs } : {},
940
+ ...options.detachedWallCapMs !== void 0 ? { wallCapMs: options.detachedWallCapMs } : {}
941
+ }), {
942
+ task,
943
+ sessionId,
944
+ signal: ctx.signal,
945
+ ...options.harness ? { harness: options.harness } : {},
946
+ ...options.model ? { model: options.model } : {},
947
+ ...options.reviewer ? { reviewer: options.reviewer } : {}
948
+ });
949
+ ctx.report({
950
+ iteration: 1,
951
+ phase: "completed"
952
+ });
953
+ return chosen;
954
+ }
955
+ const chosen = await pickCoderWinner({
956
+ iterations: (await runAgentRounds({
957
+ driver: singleShotDriver,
958
+ agentRun: agentRunSpec,
959
+ output,
960
+ validator,
961
+ task,
962
+ ctx: {
963
+ sandboxClient,
964
+ signal: ctx.signal,
965
+ ...loopEmitter ? { traceEmitter: loopEmitter } : {}
966
+ },
967
+ maxIterations: 1,
968
+ maxConcurrency
969
+ })).iterations,
970
+ reviewer: options.reviewer,
971
+ selection: options.winnerSelection ?? "highest-score",
972
+ task,
973
+ signal: ctx.signal
974
+ });
975
+ if (!chosen) throw new Error(noWinnerMessage(options.reviewer));
976
+ ctx.report({
977
+ iteration: 1,
978
+ phase: "completed"
979
+ });
980
+ return chosen;
981
+ }
982
+ const fanout = multiHarnessCoderFanout({
983
+ ...options.workerProfile ? { profile: options.workerProfile } : {},
984
+ ...fanoutHarnesses && fanoutHarnesses.length > 0 ? { harnesses: fanoutHarnesses.slice(0, variants) } : {},
985
+ ...options.fanoutModels ? { models: options.fanoutModels.slice(0, variants) } : {}
986
+ });
987
+ const agentRuns = fanout.agentRuns.slice(0, variants);
988
+ const chosen = await pickCoderWinner({
989
+ iterations: (await runAgentRounds({
990
+ driver: fanout.driver,
991
+ agentRuns,
992
+ output: fanout.output,
993
+ validator: fanout.validator,
994
+ task,
995
+ ctx: {
996
+ sandboxClient,
997
+ signal: ctx.signal,
998
+ ...loopEmitter ? { traceEmitter: loopEmitter } : {}
999
+ },
1000
+ maxIterations: variants,
1001
+ maxConcurrency: Math.min(maxConcurrency, variants)
1002
+ })).iterations,
1003
+ reviewer: options.reviewer,
1004
+ selection: options.winnerSelection ?? "highest-score",
1005
+ task,
1006
+ signal: ctx.signal
1007
+ });
1008
+ if (!chosen) throw new Error(noWinnerMessage(options.reviewer));
1009
+ ctx.report({
1010
+ iteration: agentRuns.length,
1011
+ phase: "completed"
1012
+ });
1013
+ return chosen;
1014
+ };
942
1015
  }
1016
+ /**
1017
+ * Pick the winning coder candidate from a finished loop's iterations:
1018
+ * 1. keep only mechanically-VALID candidates (the validator already gated
1019
+ * tests/typecheck/forbidden/diff/no-op/secrets),
1020
+ * 2. if a `reviewer` is wired, keep only those it APPROVES,
1021
+ * 3. select among survivors via the shared `selectValidWinner` (base strategies) or, for the
1022
+ * reviewer-only `highest-readiness`, a readiness sort (the one strategy the generic selector
1023
+ * does not express — a documented capability of this sandbox-session path).
1024
+ * Returns `undefined` when nothing survives — the delegate fails loud.
1025
+ */
943
1026
  async function pickCoderWinner(args) {
944
- const eligible = [];
945
- for (const iter of args.iterations) {
946
- if (iter.output === void 0 || iter.error || iter.verdict?.valid !== true) continue;
947
- const readiness = iter.verdict.score ?? 0;
948
- if (args.reviewer) {
949
- const review = await args.reviewer(iter.output, args.task, { signal: args.signal });
950
- if (!review.approved) continue;
951
- eligible.push({ iter, readiness: review.readiness });
952
- } else {
953
- eligible.push({ iter, readiness });
954
- }
955
- }
956
- if (eligible.length === 0) return void 0;
957
- if (args.selection === "highest-readiness") {
958
- const sorted = [...eligible].sort(
959
- (a, b) => b.readiness - a.readiness || a.iter.index - b.iter.index
960
- );
961
- return sorted[0].iter.output;
962
- }
963
- const wrapped = eligible.map(({ iter }) => ({
964
- ...iter,
965
- output: { kind: "done", deliverable: iter.output }
966
- }));
967
- const winner = selectValidWinner({
968
- strategy: baseStrategy(args.selection),
969
- sizeOf: (o) => o.diffStats.insertions + o.diffStats.deletions
970
- })(wrapped);
971
- const out = winner?.output;
972
- if (!out || out.kind !== "done") return void 0;
973
- return out.deliverable;
1027
+ const eligible = [];
1028
+ for (const iter of args.iterations) {
1029
+ if (iter.output === void 0 || iter.error || iter.verdict?.valid !== true) continue;
1030
+ const readiness = iter.verdict.score ?? 0;
1031
+ if (args.reviewer) {
1032
+ const review = await args.reviewer(iter.output, args.task, { signal: args.signal });
1033
+ if (!review.approved) continue;
1034
+ eligible.push({
1035
+ iter,
1036
+ readiness: review.readiness
1037
+ });
1038
+ } else eligible.push({
1039
+ iter,
1040
+ readiness
1041
+ });
1042
+ }
1043
+ if (eligible.length === 0) return void 0;
1044
+ if (args.selection === "highest-readiness") return [...eligible].sort((a, b) => b.readiness - a.readiness || a.iter.index - b.iter.index)[0].iter.output;
1045
+ const wrapped = eligible.map(({ iter }) => ({
1046
+ ...iter,
1047
+ output: {
1048
+ kind: "done",
1049
+ deliverable: iter.output
1050
+ }
1051
+ }));
1052
+ const out = selectValidWinner({
1053
+ strategy: baseStrategy(args.selection),
1054
+ sizeOf: (o) => o.diffStats.insertions + o.diffStats.deletions
1055
+ })(wrapped)?.output;
1056
+ if (out?.kind !== "done") return void 0;
1057
+ return out.deliverable;
974
1058
  }
1059
+ /** Map the detached-session selection enum onto the shared `WinnerStrategy`. `first-approved`
1060
+ * reduces to `first-valid` over the already-approved set; `smallest-diff` to `smallest-artifact`. */
975
1061
  function baseStrategy(selection) {
976
- switch (selection) {
977
- case "smallest-diff":
978
- return "smallest-artifact";
979
- case "first-approved":
980
- return "first-valid";
981
- default:
982
- return "highest-score";
983
- }
1062
+ switch (selection) {
1063
+ case "smallest-diff": return "smallest-artifact";
1064
+ case "first-approved": return "first-valid";
1065
+ default: return "highest-score";
1066
+ }
984
1067
  }
985
1068
  function noWinnerMessage(reviewer) {
986
- return reviewer ? "coder delegate: no candidate passed validation + review" : "coder delegate: no candidate passed validation";
1069
+ return reviewer ? "coder delegate: no candidate passed validation + review" : "coder delegate: no candidate passed validation";
987
1070
  }
1071
+ /**
1072
+ * Canonical `DelegateCodeArgs` → `CoderTask` mapping — the single source for
1073
+ * the delegate's live dispatch AND the resume driver's settle/message
1074
+ * rebuilding, so a resumed record reproduces exactly the task the original
1075
+ * process dispatched.
1076
+ *
1077
+ * @experimental
1078
+ */
988
1079
  function coderTaskFromArgs(args) {
989
- return {
990
- goal: buildCoderGoal(args),
991
- repoRoot: args.repoRoot,
992
- testCmd: args.config?.testCmd,
993
- typecheckCmd: args.config?.typecheckCmd,
994
- forbiddenPaths: args.config?.forbiddenPaths,
995
- maxDiffLines: args.config?.maxDiffLines
996
- };
1080
+ return {
1081
+ goal: buildCoderGoal(args),
1082
+ repoRoot: args.repoRoot,
1083
+ testCmd: args.config?.testCmd,
1084
+ typecheckCmd: args.config?.typecheckCmd,
1085
+ forbiddenPaths: args.config?.forbiddenPaths,
1086
+ maxDiffLines: args.config?.maxDiffLines
1087
+ };
997
1088
  }
1089
+ /**
1090
+ * Settle a completed detached coder turn through the same gate the streaming
1091
+ * path applies: parse the terminal payload with the coder output adapter,
1092
+ * run the mechanical validator (tests/typecheck/forbidden/diff/no-op/secrets),
1093
+ * then the optional reviewer. Throws when nothing survives — a resumed or
1094
+ * detached run must not return an unvalidated patch.
1095
+ *
1096
+ * SCOPE NOTE (detached/resume): the detached `driveTurn`-tick + cross-restart resume path is
1097
+ * bound to the `runAgentRounds` + sandbox-session substrate. The recursive `Scope`/worktree-CLI leaf has
1098
+ * journal→replay but no driveTurn-over-a-detached-sandbox-session equivalent yet, so resume is NOT
1099
+ * advertised on the generic `worktreeFanout` path. This helper (with `coderTaskFromArgs` and
1100
+ * `createDetachedTurnResumeDriver`) stays as the resume seam `bin.ts` wires for in-flight records.
1101
+ *
1102
+ * @experimental
1103
+ */
998
1104
  async function settleDetachedCoderTurn(turn, options) {
999
- const parsed = coderOutputAdapter.parse(detachedTurnEvents(options.sessionId, turn));
1000
- const validator = createCoderValidator(options.task);
1001
- const verdict = await validator.validate(parsed, { iteration: 0, signal: options.signal });
1002
- if (verdict.valid !== true) throw new Error(noWinnerMessage(options.reviewer));
1003
- if (options.reviewer) {
1004
- const review = await options.reviewer(parsed, options.task, { signal: options.signal });
1005
- if (!review.approved) throw new Error(noWinnerMessage(options.reviewer));
1006
- }
1007
- return parsed;
1105
+ const parsed = coderOutputAdapter.parse(detachedTurnEvents(options.sessionId, turn));
1106
+ if ((await createCoderValidator(options.task).validate(parsed, {
1107
+ iteration: 0,
1108
+ signal: options.signal
1109
+ })).valid !== true) throw new Error(noWinnerMessage(options.reviewer));
1110
+ if (options.reviewer) {
1111
+ if (!(await options.reviewer(parsed, options.task, { signal: options.signal })).approved) throw new Error(noWinnerMessage(options.reviewer));
1112
+ }
1113
+ return parsed;
1008
1114
  }
1009
1115
  function buildCoderGoal(args) {
1010
- if (!args.contextHint) return args.goal;
1011
- return [args.goal, "", "## Context", args.contextHint].join("\n");
1116
+ if (!args.contextHint) return args.goal;
1117
+ return [
1118
+ args.goal,
1119
+ "",
1120
+ "## Context",
1121
+ args.contextHint
1122
+ ].join("\n");
1012
1123
  }
1013
1124
  function resolveExecutor(options) {
1014
- if (options.executor && options.sandboxClient) {
1015
- throw new Error("detachedSessionDelegate: pass exactly one of `executor` or `sandboxClient`");
1016
- }
1017
- if (options.executor) return options.executor;
1018
- if (options.sandboxClient) {
1019
- return createSiblingSandboxExecutor({ client: options.sandboxClient });
1020
- }
1021
- throw new Error("detachedSessionDelegate: `executor` or `sandboxClient` is required");
1125
+ if (options.executor && options.sandboxClient) throw new Error("detachedSessionDelegate: pass exactly one of `executor` or `sandboxClient`");
1126
+ if (options.executor) return options.executor;
1127
+ if (options.sandboxClient) return createSiblingSandboxExecutor({ client: options.sandboxClient });
1128
+ throw new Error("detachedSessionDelegate: `executor` or `sandboxClient` is required");
1022
1129
  }
1023
- var singleShotDriver = {
1024
- name: "mcp-single-shot",
1025
- async plan(task, history) {
1026
- return history.length === 0 ? [task] : [];
1027
- },
1028
- decide(history) {
1029
- return history.length > 0 ? "pick-winner" : "fail";
1030
- }
1130
+ /**
1131
+ * Single-shot driver — plan one task on iteration 0, stop after one
1132
+ * iteration. Used by the coder delegate when `variants <= 1`. Keeps the
1133
+ * runAgentRounds kernel-level accounting (timing, cost, trace emission) while
1134
+ * skipping fanout/refine topology overhead.
1135
+ */
1136
+ const singleShotDriver = {
1137
+ name: "mcp-single-shot",
1138
+ async plan(task, history) {
1139
+ return history.length === 0 ? [task] : [];
1140
+ },
1141
+ decide(history) {
1142
+ return history.length > 0 ? "pick-winner" : "fail";
1143
+ }
1031
1144
  };
1032
-
1033
- // src/mcp/tools/checks.ts
1034
- import {
1035
- makeFinding
1036
- } from "@tangle-network/agent-eval";
1037
- var ANALYST_SEVERITIES = ["critical", "high", "medium", "low", "info"];
1038
- var FINDING_SCHEMA_PROMPT = [
1039
- "Each finding is a JSON object with these fields:",
1040
- '- severity: one of "critical" | "high" | "medium" | "low" | "info"',
1041
- "- claim: one-sentence statement",
1042
- '- evidence_uri: REQUIRED, never blank \u2014 exactly one of "span://<trace>/<span>", "artifact://<path>",',
1043
- ' or "metric://<name>"; ALWAYS cite a real id from the trace. No citable id \u21D2 omit the finding.',
1044
- "- evidence_excerpt?: a short quote from the cited evidence",
1045
- "- confidence: number 0..1",
1046
- "- rationale?: one sentence of reasoning",
1047
- '- recommended_action?: a concrete imperative ("Add ...", "Replace ...", "Stop ...")',
1048
- "Emit an empty array when there is nothing to report. Never fabricate evidence."
1145
+ //#endregion
1146
+ //#region src/mcp/tools/checks.ts
1147
+ /**
1148
+ *
1149
+ * The trace-analyst KIND directory — the operator's lenses, as composable DATA.
1150
+ *
1151
+ * An analyst is not one question. A kind is ONE lens (completeness, correctness, policy, efficiency,
1152
+ * tool-use, …); each emits `AnalystFinding`s tagged by its `area`. The driver `list_analysts` to see
1153
+ * the menu, `run_analyst(kind, worker)` to apply a lens, and `define_analyst` to author a new one —
1154
+ * so at test time you compose the exact lenses a domain needs (maximum specificity), not one generic
1155
+ * reviewer. The kinds are data, the runner is generic, and the finding shape + firewall are reused
1156
+ * from agent-eval / the keystone never re-derived.
1157
+ *
1158
+ * A kind here is a lightweight lens (`Check`); it is a deliberate SUBSET of agent-eval's full
1159
+ * `TraceAnalystKindSpec`, so a kind that needs the heavy agentic actor (sub-agent recursion, tools,
1160
+ * goldens) upgrades to `createTraceAnalystKind` without changing this directory's surface.
1161
+ *
1162
+ * @experimental
1163
+ */
1164
+ const ANALYST_SEVERITIES = [
1165
+ "critical",
1166
+ "high",
1167
+ "medium",
1168
+ "low",
1169
+ "info"
1170
+ ];
1171
+ const FINDING_SCHEMA_PROMPT = [
1172
+ "Each finding is a JSON object with these fields:",
1173
+ "- severity: one of \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\"",
1174
+ "- claim: one-sentence statement",
1175
+ "- evidence_uri: REQUIRED, never blank — exactly one of \"span://<trace>/<span>\", \"artifact://<path>\",",
1176
+ " or \"metric://<name>\"; ALWAYS cite a real id from the trace. No citable id ⇒ omit the finding.",
1177
+ "- evidence_excerpt?: a short quote from the cited evidence",
1178
+ "- confidence: number 0..1",
1179
+ "- rationale?: one sentence of reasoning",
1180
+ "- recommended_action?: a concrete imperative (\"Add ...\", \"Replace ...\", \"Stop ...\")",
1181
+ "Emit an empty array when there is nothing to report. Never fabricate evidence."
1049
1182
  ].join("\n");
1183
+ /** Validate one raw finding row (the lightweight equivalent of agent-eval's `parseRawFinding`):
1184
+ * require a claim + a real trace evidence_uri; drop anything else. Returns null to discard. */
1050
1185
  function validateRawFinding(row) {
1051
- if (!row || typeof row !== "object") return null;
1052
- const r = row;
1053
- if (typeof r.claim !== "string" || r.claim.length === 0) return null;
1054
- if (typeof r.evidence_uri !== "string" || !/^(span|artifact|metric):\/\//.test(r.evidence_uri))
1055
- return null;
1056
- const sev = ANALYST_SEVERITIES.includes(r.severity) ? r.severity : "medium";
1057
- return {
1058
- severity: sev,
1059
- claim: r.claim,
1060
- evidence_uri: r.evidence_uri,
1061
- confidence: typeof r.confidence === "number" ? r.confidence : 0.5,
1062
- ...typeof r.evidence_excerpt === "string" ? { evidence_excerpt: r.evidence_excerpt } : {},
1063
- ...typeof r.rationale === "string" ? { rationale: r.rationale } : {},
1064
- ...typeof r.recommended_action === "string" ? { recommended_action: r.recommended_action } : {},
1065
- ...typeof r.subject === "string" ? { subject: r.subject } : {}
1066
- };
1186
+ if (!row || typeof row !== "object") return null;
1187
+ const r = row;
1188
+ if (typeof r.claim !== "string" || r.claim.length === 0) return null;
1189
+ if (typeof r.evidence_uri !== "string" || !/^(span|artifact|metric):\/\//.test(r.evidence_uri)) return null;
1190
+ return {
1191
+ severity: ANALYST_SEVERITIES.includes(r.severity) ? r.severity : "medium",
1192
+ claim: r.claim,
1193
+ evidence_uri: r.evidence_uri,
1194
+ confidence: typeof r.confidence === "number" ? r.confidence : .5,
1195
+ ...typeof r.evidence_excerpt === "string" ? { evidence_excerpt: r.evidence_excerpt } : {},
1196
+ ...typeof r.rationale === "string" ? { rationale: r.rationale } : {},
1197
+ ...typeof r.recommended_action === "string" ? { recommended_action: r.recommended_action } : {},
1198
+ ...typeof r.subject === "string" ? { subject: r.subject } : {}
1199
+ };
1067
1200
  }
1068
- var defaultChecks = {
1069
- completeness: {
1070
- id: "completeness",
1071
- description: "Required work the trace does not yet show done or verified.",
1072
- area: "failure-mode",
1073
- version: "1",
1074
- lookFor: "every change the task requires that the trace does NOT yet show completed AND verified by a tool result. One finding per missing/unverified requirement."
1075
- },
1076
- correctness: {
1077
- id: "correctness",
1078
- description: "Tool calls that produced wrong, erroring, or contradicted results.",
1079
- area: "correctness",
1080
- version: "1",
1081
- lookFor: "tool calls whose RESULT shows an error, a wrong value, or contradicts what the task required (e.g. set the wrong field, value did not take, an error was ignored)."
1082
- },
1083
- policy: {
1084
- id: "policy",
1085
- description: "Actions that violate a stated policy, constraint, or allow-list.",
1086
- area: "safety",
1087
- version: "1",
1088
- lookFor: "actions in the trace that violate a policy/constraint stated in the task or system prompt (forbidden tool, missing approval, out-of-scope mutation, skipped precondition)."
1089
- },
1090
- efficiency: {
1091
- id: "efficiency",
1092
- description: "Wasted, redundant, or looping work.",
1093
- area: "cost",
1094
- version: "1",
1095
- lookFor: "redundant or wasted actions \u2014 repeated identical calls, a stalled line retried the same way, work that produced no progress toward the goal."
1096
- },
1097
- "tool-use": {
1098
- id: "tool-use",
1099
- description: "Malformed or misused tool calls.",
1100
- area: "tool-use",
1101
- version: "1",
1102
- lookFor: "tool calls with malformed/invalid arguments, the wrong tool for the intent, or a tool used against its contract \u2014 judged from the call + its result."
1103
- }
1201
+ /** The built-in lens directory. Domain-blind (about any agent trace); compose at test time. */
1202
+ const defaultChecks = {
1203
+ completeness: {
1204
+ id: "completeness",
1205
+ description: "Required work the trace does not yet show done or verified.",
1206
+ area: "failure-mode",
1207
+ version: "1",
1208
+ lookFor: "every change the task requires that the trace does NOT yet show completed AND verified by a tool result. One finding per missing/unverified requirement."
1209
+ },
1210
+ correctness: {
1211
+ id: "correctness",
1212
+ description: "Tool calls that produced wrong, erroring, or contradicted results.",
1213
+ area: "correctness",
1214
+ version: "1",
1215
+ lookFor: "tool calls whose RESULT shows an error, a wrong value, or contradicts what the task required (e.g. set the wrong field, value did not take, an error was ignored)."
1216
+ },
1217
+ policy: {
1218
+ id: "policy",
1219
+ description: "Actions that violate a stated policy, constraint, or allow-list.",
1220
+ area: "safety",
1221
+ version: "1",
1222
+ lookFor: "actions in the trace that violate a policy/constraint stated in the task or system prompt (forbidden tool, missing approval, out-of-scope mutation, skipped precondition)."
1223
+ },
1224
+ efficiency: {
1225
+ id: "efficiency",
1226
+ description: "Wasted, redundant, or looping work.",
1227
+ area: "cost",
1228
+ version: "1",
1229
+ lookFor: "redundant or wasted actions — repeated identical calls, a stalled line retried the same way, work that produced no progress toward the goal."
1230
+ },
1231
+ "tool-use": {
1232
+ id: "tool-use",
1233
+ description: "Malformed or misused tool calls.",
1234
+ area: "tool-use",
1235
+ version: "1",
1236
+ lookFor: "tool calls with malformed/invalid arguments, the wrong tool for the intent, or a tool used against its contract — judged from the call + its result."
1237
+ }
1104
1238
  };
1239
+ /** Lift validated raw rows into `AnalystFinding`s (agent-eval `makeFinding` stamps `finding_id`/
1240
+ * `produced_at`), then enforce the trace-derived firewall (selector ≠ judge). Pure — no LLM. */
1105
1241
  function liftFindings(kind, rows, producedAt) {
1106
- const findings = [];
1107
- for (const row of rows) {
1108
- const raw = validateRawFinding(row);
1109
- if (!raw) continue;
1110
- findings.push(
1111
- makeFinding({
1112
- analyst_id: kind.id,
1113
- area: kind.area,
1114
- severity: raw.severity,
1115
- claim: raw.claim,
1116
- confidence: raw.confidence,
1117
- produced_at: producedAt,
1118
- evidence_refs: evidenceRefs(raw.evidence_uri, raw.evidence_excerpt),
1119
- ...raw.rationale ? { rationale: raw.rationale } : {},
1120
- ...raw.recommended_action ? { recommended_action: raw.recommended_action } : {},
1121
- ...raw.subject ? { subject: raw.subject } : {},
1122
- metadata: { kind_version: kind.version }
1123
- })
1124
- );
1125
- }
1126
- assertTraceDerivedFindings(findings);
1127
- return findings;
1242
+ const findings = [];
1243
+ for (const row of rows) {
1244
+ const raw = validateRawFinding(row);
1245
+ if (!raw) continue;
1246
+ findings.push(makeFinding({
1247
+ analyst_id: kind.id,
1248
+ area: kind.area,
1249
+ severity: raw.severity,
1250
+ claim: raw.claim,
1251
+ confidence: raw.confidence,
1252
+ produced_at: producedAt,
1253
+ evidence_refs: evidenceRefs(raw.evidence_uri, raw.evidence_excerpt),
1254
+ ...raw.rationale ? { rationale: raw.rationale } : {},
1255
+ ...raw.recommended_action ? { recommended_action: raw.recommended_action } : {},
1256
+ ...raw.subject ? { subject: raw.subject } : {},
1257
+ metadata: { kind_version: kind.version }
1258
+ }));
1259
+ }
1260
+ assertTraceDerivedFindings(findings);
1261
+ return findings;
1128
1262
  }
1263
+ /** Map a raw `evidence_uri` (span:// | artifact:// | metric://<name>) to a typed `EvidenceRef`. A
1264
+ * metric ref carries the bare NAME (the firewall checks the metric name for judge/verdict/score —
1265
+ * so a finding that cites a judge metric is rejected as not trace-derived). */
1129
1266
  function evidenceRefs(uri, excerpt) {
1130
- const scheme = uri.split("://", 1)[0];
1131
- if (scheme === "metric")
1132
- return [
1133
- { kind: "metric", uri: uri.replace(/^metric:\/\//, ""), ...excerpt ? { excerpt } : {} }
1134
- ];
1135
- const kind = scheme === "span" ? "span" : "artifact";
1136
- return [{ kind, uri, ...excerpt ? { excerpt } : {} }];
1267
+ const scheme = uri.split("://", 1)[0];
1268
+ if (scheme === "metric") return [{
1269
+ kind: "metric",
1270
+ uri: uri.replace(/^metric:\/\//, ""),
1271
+ ...excerpt ? { excerpt } : {}
1272
+ }];
1273
+ return [{
1274
+ kind: scheme === "span" ? "span" : "artifact",
1275
+ uri,
1276
+ ...excerpt ? { excerpt } : {}
1277
+ }];
1137
1278
  }
1279
+ /** Render a worker's trace (tool calls + results) into the text an analyst lens reads. Generic over
1280
+ * the trace shape: a `{ messages }` conversation, a bare message array, else stringified. */
1138
1281
  function renderTrace(trace) {
1139
- const messages = Array.isArray(trace) ? trace : trace && typeof trace === "object" && Array.isArray(trace.messages) ? trace.messages : void 0;
1140
- if (!messages) return JSON.stringify(trace ?? {}).slice(0, 8e3);
1141
- return messages.map((m) => {
1142
- const r = m;
1143
- if (r.role === "tool") return `RESULT ${String(r.content).slice(0, 300)}`;
1144
- const calls = r.tool_calls?.map((c) => `${c.function?.name}(${c.function?.arguments})`).join(", ");
1145
- return calls ? `CALL ${calls}` : `SAY ${String(r.content ?? "").slice(0, 200)}`;
1146
- }).join("\n").slice(0, 8e3);
1282
+ const messages = Array.isArray(trace) ? trace : trace && typeof trace === "object" && Array.isArray(trace.messages) ? trace.messages : void 0;
1283
+ if (!messages) return JSON.stringify(trace ?? {}).slice(0, 8e3);
1284
+ return messages.map((m) => {
1285
+ const r = m;
1286
+ if (r.role === "tool") return `RESULT ${String(r.content).slice(0, 300)}`;
1287
+ const calls = r.tool_calls?.map((c) => `${c.function?.name}(${c.function?.arguments})`).join(", ");
1288
+ return calls ? `CALL ${calls}` : `SAY ${String(r.content ?? "").slice(0, 200)}`;
1289
+ }).join("\n").slice(0, 8e3);
1147
1290
  }
1291
+ /** Run ONE lens over a trace → findings. Generic over any kind: prompt = the lens + the agent-eval
1292
+ * finding schema; the model's JSON array is parsed (`parseRawFinding`), lifted, and firewalled. */
1148
1293
  async function runCheck(kind, trace, opts, producedAt) {
1149
- const sys = `You are a trace analyst applying ONE lens: look for ${kind.lookFor}
1150
-
1151
- ${FINDING_SCHEMA_PROMPT}
1152
-
1153
- Return ONLY a fenced \`\`\`json array of finding objects (possibly empty).`;
1154
- const user = `WORKER TRACE:
1155
- ${renderTrace(trace)}
1156
-
1157
- Apply your lens and emit the findings array.`;
1158
- const chat = opts.chat ?? defaultChat(opts);
1159
- const content = await chat(sys, user);
1160
- const match = content.match(/```(?:json)?\s*([\s\S]*?)```/);
1161
- let rows = [];
1162
- try {
1163
- const parsed = JSON.parse((match?.[1] ?? content).trim());
1164
- rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.findings) ? parsed.findings : [];
1165
- } catch {
1166
- rows = [];
1167
- }
1168
- return liftFindings(kind, rows, producedAt);
1294
+ const sys = `You are a trace analyst applying ONE lens: look for ${kind.lookFor}\n\n${FINDING_SCHEMA_PROMPT}\n\nReturn ONLY a fenced \`\`\`json array of finding objects (possibly empty).`;
1295
+ const user = `WORKER TRACE:\n${renderTrace(trace)}\n\nApply your lens and emit the findings array.`;
1296
+ const content = await (opts.chat ?? defaultChat(opts))(sys, user);
1297
+ const match = content.match(/```(?:json)?\s*([\s\S]*?)```/);
1298
+ let rows = [];
1299
+ try {
1300
+ const parsed = JSON.parse((match?.[1] ?? content).trim());
1301
+ rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.findings) ? parsed.findings : [];
1302
+ } catch {
1303
+ rows = [];
1304
+ }
1305
+ return liftFindings(kind, rows, producedAt);
1169
1306
  }
1170
1307
  function defaultChat(opts) {
1171
- return async (system, user) => {
1172
- const res = await fetch(`${opts.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
1173
- method: "POST",
1174
- headers: { "content-type": "application/json", authorization: `Bearer ${opts.routerKey}` },
1175
- body: JSON.stringify({
1176
- model: opts.model,
1177
- messages: [
1178
- { role: "system", content: system },
1179
- { role: "user", content: user }
1180
- ],
1181
- temperature: 0.3
1182
- })
1183
- });
1184
- if (!res.ok) throw new Error(`analyst router ${res.status}`);
1185
- const data = await res.json();
1186
- return data.choices?.[0]?.message?.content ?? "";
1187
- };
1308
+ return async (system, user) => {
1309
+ const res = await fetch(`${opts.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
1310
+ method: "POST",
1311
+ headers: {
1312
+ "content-type": "application/json",
1313
+ authorization: `Bearer ${opts.routerKey}`
1314
+ },
1315
+ body: JSON.stringify({
1316
+ model: opts.model,
1317
+ messages: [{
1318
+ role: "system",
1319
+ content: system
1320
+ }, {
1321
+ role: "user",
1322
+ content: user
1323
+ }],
1324
+ temperature: .3
1325
+ })
1326
+ });
1327
+ if (!res.ok) throw new Error(`analyst router ${res.status}`);
1328
+ return (await res.json()).choices?.[0]?.message?.content ?? "";
1329
+ };
1188
1330
  }
1331
+ /**
1332
+ * Build a `run_analyst` runner over a kind directory.
1333
+ * Returns findings, or a typed error for an unknown kind. `producedAt` is
1334
+ * passed in because replay-safe paths must not read `Date.now`.
1335
+ */
1189
1336
  function makeCheckRunner(kinds, opts) {
1190
- return async (kindId, trace, producedAt) => {
1191
- const kind = kinds[kindId];
1192
- if (!kind)
1193
- return {
1194
- error: `unknown analyst kind ${JSON.stringify(kindId)} (have: ${Object.keys(kinds).join(", ")})`
1195
- };
1196
- return runCheck(kind, trace, opts, producedAt);
1197
- };
1337
+ return async (kindId, trace, producedAt) => {
1338
+ const kind = kinds[kindId];
1339
+ if (!kind) return { error: `unknown analyst kind ${JSON.stringify(kindId)} (have: ${Object.keys(kinds).join(", ")})` };
1340
+ return runCheck(kind, trace, opts, producedAt);
1341
+ };
1198
1342
  }
1199
- export {
1200
- CodexExecutionDiagnosticError,
1201
- DELEGATE_DESCRIPTION,
1202
- DELEGATE_FEEDBACK_DESCRIPTION,
1203
- DELEGATE_FEEDBACK_INPUT_SCHEMA,
1204
- DELEGATE_FEEDBACK_TOOL_NAME,
1205
- DELEGATE_INPUT_SCHEMA,
1206
- DELEGATE_TOOL_NAME,
1207
- DELEGATE_UI_AUDIT_DESCRIPTION,
1208
- DELEGATE_UI_AUDIT_INPUT_SCHEMA,
1209
- DELEGATE_UI_AUDIT_TOOL_NAME,
1210
- DELEGATION_HISTORY_DESCRIPTION,
1211
- DELEGATION_HISTORY_INPUT_SCHEMA,
1212
- DELEGATION_HISTORY_TOOL_NAME,
1213
- DELEGATION_STATUS_DESCRIPTION,
1214
- DELEGATION_STATUS_INPUT_SCHEMA,
1215
- DELEGATION_STATUS_TOOL_NAME,
1216
- DELEGATION_TRACE_MAX_BYTES,
1217
- DELEGATION_TRACE_MAX_SPANS,
1218
- DelegationPersistenceError,
1219
- DelegationStateCorruptError,
1220
- DelegationTaskQueue,
1221
- FileDelegationStore,
1222
- InMemoryDelegationStore,
1223
- InMemoryFeedbackStore,
1224
- MEMORY_FILE_ENV,
1225
- MEMORY_ITEMS_ENV,
1226
- MEMORY_LOG_ENV,
1227
- MEMORY_NAME_ENV,
1228
- buildDelegationTraceSpans,
1229
- capDelegationTrace,
1230
- captureWorktreeDiff,
1231
- coderTaskFromArgs,
1232
- composeLoopTraceEmitters,
1233
- createCoordinationTools,
1234
- createDelegateFeedbackHandler,
1235
- createDelegateHandler,
1236
- createDelegateUiAuditHandler,
1237
- createDelegationHistoryHandler,
1238
- createDelegationStatusHandler,
1239
- createDelegationTraceCollector,
1240
- createDetachedTurnResumeDriver,
1241
- createFleetWorkspaceExecutor,
1242
- createInProcessExecutor,
1243
- createInProcessTransport,
1244
- createKbGate,
1245
- createMcpServer,
1246
- createMemoryToolServer,
1247
- createPropagatingTraceEmitter,
1248
- createSiblingSandboxExecutor,
1249
- createStdioToolServer,
1250
- createWorktree,
1251
- defaultChecks,
1252
- detachedSessionDelegate,
1253
- detachedTurnEvents,
1254
- detectExecutor,
1255
- eventToSnapshot,
1256
- formatDetachedSessionRef,
1257
- hashIdempotencyInput,
1258
- liftFindings,
1259
- makeCheckRunner,
1260
- mcpToolsForRuntimeMcp,
1261
- mcpToolsForRuntimeMcpSubset,
1262
- parseCodexTokenUsage,
1263
- parseDetachedSessionRef,
1264
- parseMemoryItems,
1265
- readMemoryItemsFile,
1266
- readTraceContextFromEnv,
1267
- removeWorktree,
1268
- renderTrace,
1269
- resolveMemoryFromEnv,
1270
- runCheck,
1271
- runDetachedTurn,
1272
- runLocalHarness,
1273
- settleDetachedCoderTurn,
1274
- traceContextToEnv,
1275
- validateDelegateArgs,
1276
- validateDelegateFeedbackArgs,
1277
- validateDelegateUiAuditArgs,
1278
- validateDelegationHistoryArgs,
1279
- validateDelegationStatusArgs
1280
- };
1343
+ //#endregion
1344
+ export { CodexExecutionDiagnosticError, DEFAULT_AWAIT_EVENT_TIMEOUT_MS, DELEGATE_DESCRIPTION, DELEGATE_FEEDBACK_DESCRIPTION, DELEGATE_FEEDBACK_INPUT_SCHEMA, DELEGATE_FEEDBACK_TOOL_NAME, DELEGATE_INPUT_SCHEMA, DELEGATE_TOOL_NAME, DELEGATE_UI_AUDIT_DESCRIPTION, DELEGATE_UI_AUDIT_INPUT_SCHEMA, DELEGATE_UI_AUDIT_TOOL_NAME, DELEGATION_HISTORY_DESCRIPTION, DELEGATION_HISTORY_INPUT_SCHEMA, DELEGATION_HISTORY_TOOL_NAME, DELEGATION_STATUS_DESCRIPTION, DELEGATION_STATUS_INPUT_SCHEMA, DELEGATION_STATUS_TOOL_NAME, DELEGATION_TRACE_MAX_BYTES, DELEGATION_TRACE_MAX_SPANS, DelegationPersistenceError, DelegationStateCorruptError, DelegationTaskQueue, FileDelegationStore, InMemoryDelegationStore, InMemoryFeedbackStore, MEMORY_FILE_ENV, MEMORY_ITEMS_ENV, MEMORY_LOG_ENV, MEMORY_NAME_ENV, buildDelegationTraceSpans, capDelegationTrace, captureWorktreeDiff, coderTaskFromArgs, composeLoopTraceEmitters, createCoordinationTools, createDelegateFeedbackHandler, createDelegateHandler, createDelegateUiAuditHandler, createDelegationHistoryHandler, createDelegationStatusHandler, createDelegationTraceCollector, createDetachedTurnResumeDriver, createFleetWorkspaceExecutor, createInProcessExecutor, createInProcessTransport, createKbGate, createMcpServer, createMemoryToolServer, createPropagatingTraceEmitter, createSiblingSandboxExecutor, createStdioToolServer, createWorktree, defaultChecks, detachedSessionDelegate, detachedTurnEvents, detectExecutor, eventToSnapshot, formatDetachedSessionRef, hashIdempotencyInput, liftFindings, makeCheckRunner, mcpToolsForRuntimeMcp, mcpToolsForRuntimeMcpSubset, parseCodexTokenUsage, parseDetachedSessionRef, parseMemoryItems, readMemoryItemsFile, readTraceContextFromEnv, removeWorktree, renderTrace, resolveMemoryFromEnv, runCheck, runDetachedTurn, runLocalHarness, settleDetachedCoderTurn, traceContextToEnv, validateDelegateArgs, validateDelegateFeedbackArgs, validateDelegateUiAuditArgs, validateDelegationHistoryArgs, validateDelegationStatusArgs };
1345
+
1281
1346
  //# sourceMappingURL=index.js.map