@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/index.js CHANGED
@@ -1,2191 +1,2215 @@
1
- import {
2
- DELEGATED_LOOP_MODES,
3
- auditLoopRunner,
4
- isDelegatedLoopMode,
5
- parseLoopRunnerArgv,
6
- researchLoopRunner,
7
- runDelegatedLoop,
8
- runLoopRunnerCli,
9
- worktreeLoopRunner
10
- } from "./chunk-HNP72PNU.js";
11
- import {
12
- FileAgentCandidateExecutionClaimStore,
13
- buildAgentCandidateBundle,
14
- disposePreparedAgentCandidateExecution,
15
- recoverExpiredAgentCandidateExecution
16
- } from "./chunk-CTRA64LY.js";
17
- import {
18
- mcpToolsForRuntimeMcp,
19
- mcpToolsForRuntimeMcpSubset
20
- } from "./chunk-C5RTIBNZ.js";
21
- import "./chunk-SGKPNBXE.js";
22
- import {
23
- DEFAULT_ROUTER_BASE_URL,
24
- cleanModelId,
25
- getModels,
26
- resolveChatModel,
27
- resolveRouterBaseUrl,
28
- validateChatModelId
29
- } from "./chunk-NURIQBJQ.js";
30
- import {
31
- CircuitBreakerState,
32
- CircuitOpenError,
33
- DEFAULT_MAX_DEPTH,
34
- DeadlineExceededError,
35
- FORWARD_HEADERS,
36
- FileConversationJournal,
37
- InMemoryConversationJournal,
38
- SqlConversationJournal,
39
- buildForwardHeaders,
40
- computeBackoff,
41
- createConversationBackend,
42
- d1ToSqlAdapter,
43
- defaultIsRetryable,
44
- defineConversation,
45
- isDepthExceeded,
46
- makePerAttemptSignal,
47
- readDepth,
48
- runConversation,
49
- runConversationStream,
50
- runPersonaConversation,
51
- runPersonaDispatch,
52
- sleep,
53
- slugifySpeaker,
54
- turnId
55
- } from "./chunk-2KGAN2HM.js";
56
- import {
57
- defaultRedactor,
58
- defaultRedactorIdentityMaterial,
59
- resolveRedactor
60
- } from "./chunk-OPVWXJ2H.js";
61
- import {
62
- createProtectedAgentCandidateModelPort,
63
- exactProcessProviderAsCandidateExecutor
64
- } from "./chunk-H5QPIZNX.js";
65
- import {
66
- McpSpawnFault,
67
- connectStdioMcp
68
- } from "./chunk-WRTOVNN4.js";
69
- import {
70
- InMemoryRuntimeSessionStore,
71
- createIterableBackend,
72
- createOpenAICompatibleBackend,
73
- createSandboxPromptBackend,
74
- normalizeBackendStreamEvent,
75
- nowIso,
76
- startOrResumeRuntimeSession,
77
- touchSession
78
- } from "./chunk-Q2JSAVQ3.js";
79
- import {
80
- RESEARCH_SUPERVISOR_SYSTEM_PROMPT,
81
- buildKnowledgeImprovementExperimentBundles,
82
- createAgentKnowledgeReadinessCheck,
83
- createKnowledgeImprovementActivationExecutor,
84
- createSupervisedKnowledgeUpdater,
85
- formatSupervisedKnowledgeTask,
86
- knowledgeReadinessDeliverable,
87
- runKnowledgeImprovementJob,
88
- runSupervisedKnowledgeUpdate
89
- } from "./chunk-WMTCUOQL.js";
90
- import "./chunk-5QOB7H74.js";
91
- import {
92
- captureAgentCandidateWorkspace,
93
- captureAgentCandidateWorkspaceFiles,
94
- createAgentCandidateWorkspacePort,
95
- sealAgentCandidateBundle
96
- } from "./chunk-RDOAVVHY.js";
97
- import "./chunk-VISA6CI3.js";
98
- import {
99
- AGENTIC_PROFILE_RESOURCE_ROOT,
100
- LIFTED_FINDING_ANALYST_ID,
101
- ROLLOUT_POLICY_EXTENSION,
102
- agenticGenerator,
103
- applyRolloutPolicyToProfile,
104
- commandVerifier,
105
- defaultBuildPrompt,
106
- improve,
107
- isAnalystFinding,
108
- normalizeRolloutPolicy,
109
- parseRolloutPolicy,
110
- rawTraceDistiller,
111
- rawTraceEvidenceProblem,
112
- requiresRawTraceEvidence,
113
- serializeRolloutPolicy,
114
- structuralRolloutPolicyFromProfile,
115
- summarizeFindings,
116
- toAnalystFindings,
117
- withMethodRuntimeControls,
118
- worktreeChangedPaths
119
- } from "./chunk-UHEZW5BU.js";
120
- import {
121
- AGENT_CANDIDATE_EXECUTION_SUPPORT,
122
- CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV,
123
- CANDIDATE_KNOWLEDGE_ROOT_ENV,
124
- CANDIDATE_TRACE_ENV,
125
- CANDIDATE_TRACE_TAGS,
126
- InMemoryAgentCandidateExecutionClaimStore,
127
- applyExactAgentProfileDiff,
128
- assertCandidateProfileBinding,
129
- candidateExecutionClaim,
130
- candidateKnowledgeExecutionPaths,
131
- canonicalCandidateDigest,
132
- executePreparedAgentCandidate,
133
- parseExactAgentProfile,
134
- parseExactAgentProfileDiff,
135
- persistCandidateOutputArtifact,
136
- prepareAgentCandidateExecution,
137
- verifyAgentCandidateBundle
138
- } from "./chunk-WSTRQZYQ.js";
139
- import "./chunk-XBG2W2VW.js";
140
- import "./chunk-DPEUKJRO.js";
141
- import {
142
- buildDriverSystem,
143
- optimizerMethod,
144
- researchDriverNote,
145
- strategyAuthorMethod
146
- } from "./chunk-YJZA2BIK.js";
147
- import "./chunk-UPWGXKXB.js";
148
- import {
149
- INTELLIGENCE_WIRE_VERSION,
150
- buildLoopOtelSpans,
151
- buildLoopSpanNodes,
152
- buildRuntimeEventOtelSpans,
153
- createOtelExporter,
154
- createRuntimeEventCollector,
155
- createRuntimeStreamEventCollector,
156
- exportEvalRuns,
157
- loopEventToOtelSpan,
158
- sanitizeAgentRuntimeEvent,
159
- sanitizeKnowledgeReadinessReport,
160
- sanitizeRuntimeStreamEvent
161
- } from "./chunk-2VZYRB55.js";
162
- import {
163
- composeRuntimeHooks,
164
- defineRuntimeHooks,
165
- notifyRuntimeDecisionPoint,
166
- notifyRuntimeHookEvent,
167
- runBrainLoop,
168
- runLocalHarness
169
- } from "./chunk-HLKC4UYB.js";
170
- import "./chunk-ISPWRSEC.js";
171
- import "./chunk-KRBFHMV6.js";
172
- import "./chunk-3MDZX7YU.js";
173
- import "./chunk-FD2MBMOH.js";
174
- import "./chunk-M22PL4GH.js";
175
- import {
176
- AgentEvalError,
177
- BackendTransportError,
178
- ConfigError,
179
- JudgeError,
180
- NotFoundError,
181
- PlannerError,
182
- RuntimeRunStateError,
183
- ValidationError
184
- } from "./chunk-YEJR7IXO.js";
185
-
186
- // src/durable/chat-engine.ts
187
- var encoder = new TextEncoder();
1
+ import { a as JudgeError, c as RuntimeRunStateError, i as ConfigError, o as NotFoundError, r as BackendTransportError, s as PlannerError, t as AgentEvalError, u as ValidationError } from "./errors-DEAvWQPy.js";
2
+ import { a as InMemoryRuntimeSessionStore, c as startOrResumeRuntimeSession, i as normalizeBackendStreamEvent, l as touchSession, n as createOpenAICompatibleBackend, r as createSandboxPromptBackend, s as nowIso, t as createIterableBackend } from "./backends-CiOCyRHb.js";
3
+ import { $ as parseExactAgentProfile, K as applyExactAgentProfileDiff, S as CANDIDATE_TRACE_TAGS, _ as candidateExecutionClaim, a as persistCandidateOutputArtifact, c as CANDIDATE_KNOWLEDGE_ROOT_ENV, et as parseExactAgentProfileDiff, f as verifyAgentCandidateBundle, h as InMemoryAgentCandidateExecutionClaimStore, l as candidateKnowledgeExecutionPaths, n as executePreparedAgentCandidate, q as assertCandidateProfileBinding, rt as canonicalCandidateDigest, s as CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV, t as prepareAgentCandidateExecution, u as AGENT_CANDIDATE_EXECUTION_SUPPORT, x as CANDIDATE_TRACE_ENV } from "./prepare-B53sPV7M.js";
4
+ import { i as sealAgentCandidateBundle, n as captureAgentCandidateWorkspaceFiles, r as createAgentCandidateWorkspacePort, t as captureAgentCandidateWorkspace } from "./workspace-archive-BosQiotA.js";
5
+ import { i as buildAgentCandidateBundle, n as disposePreparedAgentCandidateExecution, r as FileAgentCandidateExecutionClaimStore, t as recoverExpiredAgentCandidateExecution } from "./candidate-execution-DqMbv1rU.js";
6
+ import { n as exactProcessProviderAsCandidateExecutor, t as createProtectedAgentCandidateModelPort } from "./protected-model-port-BLPH5VVv.js";
7
+ import { C as makePerAttemptSignal, S as defaultIsRetryable, _ as readDepth, a as FileConversationJournal, b as DeadlineExceededError, c as createConversationBackend, d as slugifySpeaker, f as turnId, g as isDepthExceeded, h as buildForwardHeaders, i as d1ToSqlAdapter, l as runConversation, m as FORWARD_HEADERS, n as runPersonaDispatch, o as InMemoryConversationJournal, p as DEFAULT_MAX_DEPTH, r as SqlConversationJournal, s as defineConversation, t as runPersonaConversation, u as runConversationStream, v as CircuitBreakerState, w as sleep, x as computeBackoff, y as CircuitOpenError } from "./conversation-BpLQZGPH.js";
8
+ import { N as runLocalHarness, _ as defineRuntimeHooks, g as composeRuntimeHooks, v as notifyRuntimeDecisionPoint, w as runBrainLoop, y as notifyRuntimeHookEvent } from "./supervisor-DTKhF-RV.js";
9
+ import { D as researchDriverNote, E as optimizerMethod, O as strategyAuthorMethod, T as buildDriverSystem } from "./structural-rollout-IXUEplky.js";
10
+ import { A as applyRolloutPolicyToProfile, B as commandVerifier, D as withMethodRuntimeControls, E as improve, F as LIFTED_FINDING_ANALYST_ID, G as worktreeChangedPaths, H as rawTraceEvidenceProblem, I as isAnalystFinding, L as toAnalystFindings, M as parseRolloutPolicy, N as serializeRolloutPolicy, O as rawTraceDistiller, P as structuralRolloutPolicyFromProfile, R as AGENTIC_PROFILE_RESOURCE_ROOT, U as requiresRawTraceEvidence, V as defaultBuildPrompt, W as summarizeFindings, j as normalizeRolloutPolicy, k as ROLLOUT_POLICY_EXTENSION, z as agenticGenerator } from "./improvement-cycle-C095kS_g.js";
11
+ import { bt as connectStdioMcp, yt as McpSpawnFault } from "./runtime-5zM5XxLr.js";
12
+ import { n as defaultRedactorIdentityMaterial, r as resolveRedactor, t as defaultRedactor } from "./redact-D-u-rrcn.js";
13
+ import { a as createSupervisedKnowledgeUpdater, c as runSupervisedKnowledgeUpdate, i as RESEARCH_SUPERVISOR_SYSTEM_PROMPT, l as createKnowledgeImprovementActivationExecutor, n as createAgentKnowledgeReadinessCheck, o as formatSupervisedKnowledgeTask, r as runKnowledgeImprovementJob, s as knowledgeReadinessDeliverable, t as buildKnowledgeImprovementExperimentBundles } from "./knowledge-Ku-mPP4f.js";
14
+ import { a as createOtelExporter, c as loopEventToOtelSpan, d as sanitizeAgentRuntimeEvent, f as sanitizeKnowledgeReadinessReport, i as buildRuntimeEventOtelSpans, l as createRuntimeEventCollector, n as buildLoopOtelSpans, o as exportEvalRuns, p as sanitizeRuntimeStreamEvent, r as buildLoopSpanNodes, t as INTELLIGENCE_WIRE_VERSION, u as createRuntimeStreamEventCollector } from "./otel-export-D34IQW1C.js";
15
+ import { a as isDelegatedLoopMode, c as worktreeLoopRunner, i as auditLoopRunner, n as runLoopRunnerCli, o as researchLoopRunner, r as DELEGATED_LOOP_MODES, s as runDelegatedLoop, t as parseLoopRunnerArgv } from "./loop-runner-bin-CRagyuNb.js";
16
+ import { n as mcpToolsForRuntimeMcpSubset, t as mcpToolsForRuntimeMcp } from "./openai-tools-CKLy1C7M.js";
17
+ import { a as resolveRouterBaseUrl, i as resolveChatModel, n as cleanModelId, o as validateChatModelId, r as getModels, t as DEFAULT_ROUTER_BASE_URL } from "./model-resolution-Btd9iIKV.js";
18
+ import { FAILURE_CLASSES, acquisitionPlansForKnowledgeGaps, blockingKnowledgeEval, canonicalJson, runAgentControlLoop, scoreKnowledgeReadiness, userQuestionsForKnowledgeGaps } from "@tangle-network/agent-eval";
19
+ import { gepaOptimizationMethod, skillOptOptimizationMethod } from "@tangle-network/agent-eval/campaign";
20
+ import { readFileSync, statSync } from "node:fs";
21
+ import { resolve, sep } from "node:path";
22
+ import { spawnSync } from "node:child_process";
23
+ import { isDeepStrictEqual } from "node:util";
24
+ //#region src/durable/chat-engine.ts
25
+ const encoder = new TextEncoder();
188
26
  function encodeLine(event) {
189
- return encoder.encode(`${JSON.stringify(event)}
190
- `);
27
+ return encoder.encode(`${JSON.stringify(event)}\n`);
191
28
  }
192
29
  function defaultLog(message, meta) {
193
- if (meta) console.error(message, meta);
194
- else console.error(message);
195
- }
30
+ if (meta) console.error(message, meta);
31
+ else console.error(message);
32
+ }
33
+ /**
34
+ * Run one chat turn. Returns immediately with a `ReadableStream` body;
35
+ * the turn executes as the body is pulled. Never rejects — backend
36
+ * failures surface as `error` + `session.run.failed` events.
37
+ */
196
38
  function handleChatTurn(input) {
197
- const log = input.log ?? defaultLog;
198
- const { identity, hooks } = input;
199
- const body = new ReadableStream({
200
- start: async (controller) => {
201
- const emit2 = async (event) => {
202
- controller.enqueue(encodeLine(event));
203
- if (hooks.onEvent) {
204
- try {
205
- await hooks.onEvent(event);
206
- } catch (err) {
207
- log("[chat-engine] onEvent hook threw", {
208
- error: err instanceof Error ? err.message : String(err)
209
- });
210
- }
211
- }
212
- };
213
- try {
214
- await emit2({
215
- type: "session.run.started",
216
- data: {
217
- sessionId: identity.sessionId,
218
- tenantId: identity.tenantId,
219
- turnIndex: identity.turnIndex
220
- }
221
- });
222
- const producer = hooks.produce();
223
- for await (const event of producer.stream) {
224
- await emit2(event);
225
- }
226
- const rawFinal = producer.finalText();
227
- const finalText = hooks.transformFinalText ? await hooks.transformFinalText(rawFinal) : rawFinal;
228
- await hooks.persistAssistantMessage({ identity, finalText });
229
- if (hooks.onTurnComplete) {
230
- try {
231
- await hooks.onTurnComplete({ identity, finalText });
232
- } catch (err) {
233
- log("[chat-engine] onTurnComplete threw", {
234
- error: err instanceof Error ? err.message : String(err)
235
- });
236
- }
237
- }
238
- await emit2({
239
- type: "session.run.completed",
240
- data: { sessionId: identity.sessionId }
241
- });
242
- } catch (err) {
243
- const message = err instanceof Error ? err.message : String(err);
244
- log("[chat-engine] turn failed", { error: message });
245
- await emit2({ type: "error", data: { message } });
246
- await emit2({
247
- type: "session.run.failed",
248
- data: { sessionId: identity.sessionId, message }
249
- });
250
- } finally {
251
- if (hooks.traceFlush) {
252
- const flush = hooks.traceFlush().catch(
253
- (err) => log("[chat-engine] traceFlush threw", {
254
- error: err instanceof Error ? err.message : String(err)
255
- })
256
- );
257
- if (input.waitUntil) input.waitUntil(flush);
258
- else await flush;
259
- }
260
- controller.close();
261
- }
262
- }
263
- });
264
- return { body, contentType: "application/x-ndjson" };
265
- }
266
-
267
- // src/durable/execution-handle.ts
39
+ const log = input.log ?? defaultLog;
40
+ const { identity, hooks } = input;
41
+ return {
42
+ body: new ReadableStream({ start: async (controller) => {
43
+ const emit = async (event) => {
44
+ controller.enqueue(encodeLine(event));
45
+ if (hooks.onEvent) try {
46
+ await hooks.onEvent(event);
47
+ } catch (err) {
48
+ log("[chat-engine] onEvent hook threw", { error: err instanceof Error ? err.message : String(err) });
49
+ }
50
+ };
51
+ try {
52
+ await emit({
53
+ type: "session.run.started",
54
+ data: {
55
+ sessionId: identity.sessionId,
56
+ tenantId: identity.tenantId,
57
+ turnIndex: identity.turnIndex
58
+ }
59
+ });
60
+ const producer = hooks.produce();
61
+ for await (const event of producer.stream) await emit(event);
62
+ const rawFinal = producer.finalText();
63
+ const finalText = hooks.transformFinalText ? await hooks.transformFinalText(rawFinal) : rawFinal;
64
+ await hooks.persistAssistantMessage({
65
+ identity,
66
+ finalText
67
+ });
68
+ if (hooks.onTurnComplete) try {
69
+ await hooks.onTurnComplete({
70
+ identity,
71
+ finalText
72
+ });
73
+ } catch (err) {
74
+ log("[chat-engine] onTurnComplete threw", { error: err instanceof Error ? err.message : String(err) });
75
+ }
76
+ await emit({
77
+ type: "session.run.completed",
78
+ data: { sessionId: identity.sessionId }
79
+ });
80
+ } catch (err) {
81
+ const message = err instanceof Error ? err.message : String(err);
82
+ log("[chat-engine] turn failed", { error: message });
83
+ await emit({
84
+ type: "error",
85
+ data: { message }
86
+ });
87
+ await emit({
88
+ type: "session.run.failed",
89
+ data: {
90
+ sessionId: identity.sessionId,
91
+ message
92
+ }
93
+ });
94
+ } finally {
95
+ if (hooks.traceFlush) {
96
+ const flush = hooks.traceFlush().catch((err) => log("[chat-engine] traceFlush threw", { error: err instanceof Error ? err.message : String(err) }));
97
+ if (input.waitUntil) input.waitUntil(flush);
98
+ else await flush;
99
+ }
100
+ controller.close();
101
+ }
102
+ } }),
103
+ contentType: "application/x-ndjson"
104
+ };
105
+ }
106
+ //#endregion
107
+ //#region src/durable/execution-handle.ts
108
+ /**
109
+ * Derive a stable executionId from the run identity. The same
110
+ * `(projectId, sessionId, turnIndex)` tuple yields the same id — so a
111
+ * client retry of the same turn lands on the same substrate execution
112
+ * and the orchestrator's buffer replays instead of starting a second
113
+ * prompt.
114
+ *
115
+ * Format is readable, not hashed: operators grepping orchestrator logs
116
+ * for `gtm-agent:thread-abc:3` find the run without translating an
117
+ * opaque id. Substrate executionIds are not a secrecy boundary.
118
+ *
119
+ * Wire integration:
120
+ * - Sandbox PromptOptions accepts `executionId` and `lastEventId`.
121
+ * Products pass this id to make cross-process reconnect land on the
122
+ * same substrate execution instead of spawning a duplicate run.
123
+ */
268
124
  function deriveExecutionId(input) {
269
- return `${input.projectId}:${input.sessionId}:${input.turnIndex}`;
125
+ return `${input.projectId}:${input.sessionId}:${input.turnIndex}`;
270
126
  }
271
-
272
- // src/improvement/build-prompts.ts
127
+ //#endregion
128
+ //#region src/improvement/build-prompts.ts
129
+ /** Render findings as the ranked-evidence block every build prompt ends with. */
273
130
  function findingLines(findings) {
274
- return findings.map((f) => {
275
- const where = f.subject ? ` [${f.subject}]` : "";
276
- const action = f.recommended_action ? ` \u2192 ${f.recommended_action}` : "";
277
- return `- (${f.severity})${where} ${f.claim}${action}`;
278
- });
131
+ return findings.map((f) => {
132
+ const where = f.subject ? ` [${f.subject}]` : "";
133
+ const action = f.recommended_action ? ` ${f.recommended_action}` : "";
134
+ return `- (${f.severity})${where} ${f.claim}${action}`;
135
+ });
279
136
  }
137
+ /** Build the starting instruction for a coder agent tasked with implementing a new tool. */
280
138
  function toolBuildPrompt(args) {
281
- return [
282
- "You are building a new TOOL for this codebase \u2014 a capability the agent measurably lacks,",
283
- "evidenced by the failure findings at the bottom. The tool is an experiment: after it is",
284
- "built and verified, its marginal lift is measured on held-out tasks, and only a real lift",
285
- "promotes it.",
286
- "",
287
- optimizerMethod,
288
- "",
289
- "THE SURFACE \u2014 what a deliverable tool looks like here:",
290
- "- ONE small, self-contained module PLUS tests that exercise its contract (what callers rely",
291
- " on), not its internals. The tests are the experiment for sub-goal correctness \u2014 write the",
292
- " test that would fail if your hypothesis about the gap were wrong.",
293
- "- It must compile and its tests must pass \u2014 they run automatically; on failure you get the",
294
- " verifier output and another attempt, resuming on top of your own edits (fix in place, do",
295
- " not start over).",
296
- "- Match the codebase grain: reuse its existing helpers, style, and test framework; a tool",
297
- " that fights the codebase is the wrong tool even if it passes.",
298
- "- Do not commit; leave the changes in the working tree.",
299
- "",
300
- "FINDINGS \u2014 ranked evidence from real failed runs (the gaps the tool must close):",
301
- ...findingLines(args.findings)
302
- ].join("\n");
303
- }
139
+ return [
140
+ "You are building a new TOOL for this codebase a capability the agent measurably lacks,",
141
+ "evidenced by the failure findings at the bottom. The tool is an experiment: after it is",
142
+ "built and verified, its marginal lift is measured on held-out tasks, and only a real lift",
143
+ "promotes it.",
144
+ "",
145
+ optimizerMethod,
146
+ "",
147
+ "THE SURFACE what a deliverable tool looks like here:",
148
+ "- ONE small, self-contained module PLUS tests that exercise its contract (what callers rely",
149
+ " on), not its internals. The tests are the experiment for sub-goal correctness write the",
150
+ " test that would fail if your hypothesis about the gap were wrong.",
151
+ "- It must compile and its tests must pass they run automatically; on failure you get the",
152
+ " verifier output and another attempt, resuming on top of your own edits (fix in place, do",
153
+ " not start over).",
154
+ "- Match the codebase grain: reuse its existing helpers, style, and test framework; a tool",
155
+ " that fights the codebase is the wrong tool even if it passes.",
156
+ "- Do not commit; leave the changes in the working tree.",
157
+ "",
158
+ "FINDINGS ranked evidence from real failed runs (the gaps the tool must close):",
159
+ ...findingLines(args.findings)
160
+ ].join("\n");
161
+ }
162
+ /** Build the starting instruction for a coder agent tasked with implementing a new MCP server. */
304
163
  function mcpBuildPrompt(args) {
305
- return [
306
- "You are building a new MCP SERVER (Model Context Protocol) exposing tool(s) that close the",
307
- "capability gaps evidenced by the failure findings at the bottom, so any harness can mount",
308
- "them. The server is an experiment: after it is built and boot-verified, its marginal lift is",
309
- "measured on held-out tasks, and only a real lift promotes it.",
310
- "",
311
- optimizerMethod,
312
- "",
313
- "RESEARCH FIRST \u2014 ADOPT BEFORE BUILD: you may discover and ADOPT an existing external MCP",
314
- "server if it fits the gaps better than building one. Registries and vendor docs list",
315
- "maintained servers for most common capabilities (web search, fetch, GitHub, filesystems,",
316
- "databases). To adopt, deliver a short adoption note instead of an implementation: the",
317
- "server's launch command or HTTP endpoint, and the API key it needs BY NAME (e.g.",
318
- "EXA_API_KEY) \u2014 never a key value; provisioning injects the value at materialize time. If",
319
- "your environment has no web access, decide from what you already know and say so.",
320
- "",
321
- "THE SURFACE \u2014 what a deliverable MCP server looks like here (checked by BOOTING it):",
322
- "- it starts over stdio and answers the MCP `initialize` handshake,",
323
- "- `tools/list` returns at least one tool with a valid input schema,",
324
- "- newline-delimited JSON-RPC 2.0, protocol version 2024-11-05,",
325
- "- a clear start command (a package.json `start` script or an obvious entrypoint).",
326
- "Design the tool surface for the FINDINGS, not for generality: each exposed tool should map to",
327
- "a named failure mechanism, with a description that tells the agent when to reach for it (a",
328
- "tool the agent never calls measures zero). If the boot-and-probe fails you get the error and",
329
- "another attempt, resuming on top of your own edits. Do not commit; leave the changes in the",
330
- "working tree.",
331
- "",
332
- "FINDINGS \u2014 ranked evidence from real failed runs (the capabilities the server must provide):",
333
- ...findingLines(args.findings)
334
- ].join("\n");
335
- }
336
-
337
- // src/improvement/driver-loop-generator.ts
338
- import { spawnSync } from "child_process";
339
- import { readFileSync, statSync } from "fs";
340
- import { resolve, sep } from "path";
341
- var workerOutputTailChars = 2e3;
342
- var diffMaxChars = 6e3;
343
- var readFileDefaultBytes = 8192;
344
- var researchResultMaxChars = 8e3;
164
+ return [
165
+ "You are building a new MCP SERVER (Model Context Protocol) exposing tool(s) that close the",
166
+ "capability gaps evidenced by the failure findings at the bottom, so any harness can mount",
167
+ "them. The server is an experiment: after it is built and boot-verified, its marginal lift is",
168
+ "measured on held-out tasks, and only a real lift promotes it.",
169
+ "",
170
+ optimizerMethod,
171
+ "",
172
+ "RESEARCH FIRST ADOPT BEFORE BUILD: you may discover and ADOPT an existing external MCP",
173
+ "server if it fits the gaps better than building one. Registries and vendor docs list",
174
+ "maintained servers for most common capabilities (web search, fetch, GitHub, filesystems,",
175
+ "databases). To adopt, deliver a short adoption note instead of an implementation: the",
176
+ "server's launch command or HTTP endpoint, and the API key it needs BY NAME (e.g.",
177
+ "EXA_API_KEY) never a key value; provisioning injects the value at materialize time. If",
178
+ "your environment has no web access, decide from what you already know and say so.",
179
+ "",
180
+ "THE SURFACE what a deliverable MCP server looks like here (checked by BOOTING it):",
181
+ "- it starts over stdio and answers the MCP `initialize` handshake,",
182
+ "- `tools/list` returns at least one tool with a valid input schema,",
183
+ "- newline-delimited JSON-RPC 2.0, protocol version 2024-11-05,",
184
+ "- a clear start command (a package.json `start` script or an obvious entrypoint).",
185
+ "Design the tool surface for the FINDINGS, not for generality: each exposed tool should map to",
186
+ "a named failure mechanism, with a description that tells the agent when to reach for it (a",
187
+ "tool the agent never calls measures zero). If the boot-and-probe fails you get the error and",
188
+ "another attempt, resuming on top of your own edits. Do not commit; leave the changes in the",
189
+ "working tree.",
190
+ "",
191
+ "FINDINGS ranked evidence from real failed runs (the capabilities the server must provide):",
192
+ ...findingLines(args.findings)
193
+ ].join("\n");
194
+ }
195
+ //#endregion
196
+ //#region src/improvement/driver-loop-generator.ts
197
+ /**
198
+ * `driverLoopGenerator` the driver→worker `CandidateGenerator`: the build
199
+ * loop run by the ATOM instead of the canned respawn.
200
+ *
201
+ * `agenticGenerator` steers with three hardcoded conditions picking a canned
202
+ * note (`EMPTY_TREE_NOTE` / `failureNote`) and respawns. This generator swaps
203
+ * that respawn brain for a real driver: an LLM on the canonical tool-loop seam
204
+ * (`runBrainLoop` + `ToolLoopChat` — the exact loop `driverAgent` runs its
205
+ * brain on) that AUTHORS each worker instruction, OBSERVES what the session
206
+ * actually produced (diff, files, verifier output), RATES it, and DECIDES
207
+ * refine / re-scope / decompose — prompted with the senior scientific-method
208
+ * doctrine (`buildDriverSystem`).
209
+ *
210
+ * The worker stays the proven primitive: `runLocalHarness` in the candidate
211
+ * worktree, same as `agenticGenerator` — only the brain between sessions
212
+ * changes. The worktree machinery (`worktreeBuildCandidate`) and verifiers
213
+ * (`commandVerifier` / `mcpServeVerifier`) are reused verbatim.
214
+ *
215
+ * Completion-oracle invariant (the supervisor doctrine, kept): the driver's
216
+ * prose NEVER decides the outcome. After the loop, code re-checks ground
217
+ * truth — tree dirty, raw-trace evidence present, verifier green — and only
218
+ * that decides `applied`. A driver that claims success over a failing verifier
219
+ * produces a discarded candidate, not a shipped one.
220
+ *
221
+ * @experimental
222
+ */
223
+ const workerOutputTailChars = 2e3;
224
+ const diffMaxChars = 6e3;
225
+ const readFileDefaultBytes = 8192;
226
+ const researchResultMaxChars = 8e3;
227
+ /** Driver→worker `CandidateGenerator`: an LLM driver on the canonical tool-loop authors, observes, rates, and steers coding-harness sessions in the worktree until the verifier passes or the session budget is spent. */
345
228
  function driverLoopGenerator(opts) {
346
- const harness = opts.harness ?? "claude";
347
- const buildPrompt = opts.buildPrompt ?? defaultBuildPrompt;
348
- const run = opts.runHarness ?? runLocalHarness;
349
- const changed = opts.changedPaths ?? worktreeChangedPaths;
350
- const readDiff = opts.readDiff ?? worktreeDiff;
351
- const verify = opts.verify;
352
- return {
353
- kind: `driver-loop:${harness}`,
354
- async generate({ worktreePath, report, findings, maxShots, signal }) {
355
- signal.throwIfAborted();
356
- const briefing = buildPrompt({ report, findings });
357
- const needsRawTraceEvidence = requiresRawTraceEvidence(findings);
358
- const sessionCap = Math.max(1, maxShots);
359
- let sessionsUsed = 0;
360
- const groundVerify = async () => {
361
- signal.throwIfAborted();
362
- if (changed(worktreePath).length === 0) {
363
- return { ok: false, feedback: "the working tree has no changes \u2014 nothing to verify" };
364
- }
365
- if (needsRawTraceEvidence) {
366
- const problem = rawTraceEvidenceProblem(worktreePath, findings);
367
- if (problem) return { ok: false, feedback: problem };
368
- }
369
- if (!verify) {
370
- return { ok: true, feedback: "no verifier configured: a dirty tree is the candidate" };
371
- }
372
- const result = await verify(worktreePath, signal);
373
- signal.throwIfAborted();
374
- return result;
375
- };
376
- const execute = async (name, args) => {
377
- signal.throwIfAborted();
378
- switch (name) {
379
- case "run_worker": {
380
- const instruction = typeof args.instruction === "string" ? args.instruction.trim() : "";
381
- if (instruction.length === 0) {
382
- return "error: run_worker requires a non-empty `instruction`";
383
- }
384
- if (sessionsUsed >= sessionCap) {
385
- return `error: worker-session budget exhausted (${sessionsUsed}/${sessionCap} used). Inspect and verify what exists, then stop with your final assessment.`;
386
- }
387
- sessionsUsed += 1;
388
- const result = await run({
389
- harness,
390
- cwd: worktreePath,
391
- taskPrompt: instruction,
392
- ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
393
- signal
394
- });
395
- signal.throwIfAborted();
396
- if (result.aborted) {
397
- throw new Error("driverLoopGenerator: worker session was cancelled by the caller");
398
- }
399
- return JSON.stringify({
400
- session: `${sessionsUsed}/${sessionCap}`,
401
- exitCode: result.exitCode,
402
- timedOut: result.timedOut,
403
- aborted: result.aborted ?? false,
404
- killedBySignal: result.killedBySignal,
405
- durationMs: result.durationMs,
406
- changedPaths: changed(worktreePath),
407
- stdoutTail: tail(result.stdout, workerOutputTailChars),
408
- stderrTail: tail(result.stderr, workerOutputTailChars)
409
- });
410
- }
411
- case "inspect_worktree": {
412
- const paths = changed(worktreePath);
413
- const diff = truncate(readDiff(worktreePath), diffMaxChars);
414
- return JSON.stringify({
415
- changedPaths: paths,
416
- diff: diff.length > 0 ? diff : "(no tracked-file diff \u2014 new files are untracked; read_file them)"
417
- });
418
- }
419
- case "read_file":
420
- return readWorktreeFile(worktreePath, args);
421
- case "research": {
422
- if (!opts.research) return "error: research tool is not provisioned in this run";
423
- const query = typeof args.query === "string" ? args.query.trim() : "";
424
- if (query.length === 0) return "error: research requires a non-empty `query`";
425
- const result = await opts.research(query);
426
- signal.throwIfAborted();
427
- return truncate(result, researchResultMaxChars);
428
- }
429
- case "run_verifier": {
430
- const result = await groundVerify();
431
- return JSON.stringify({
432
- ok: result.ok,
433
- feedback: truncate(result.feedback ?? "", 4e3)
434
- });
435
- }
436
- default:
437
- return `error: unknown tool: ${name}`;
438
- }
439
- };
440
- await runBrainLoop({
441
- chat: opts.brain,
442
- tools: opts.research ? [...driverToolSpecs, researchToolSpec] : driverToolSpecs,
443
- execute,
444
- initialMessages: [
445
- {
446
- role: "system",
447
- content: opts.research ? `${buildDriverSystem}
448
-
449
- ${researchDriverNote}` : buildDriverSystem
450
- },
451
- {
452
- role: "user",
453
- content: [
454
- `THE BUILD BRIEF (the contract your workers must satisfy \u2014 fold what each needs into its instruction; workers never see this brief):`,
455
- "",
456
- briefing,
457
- "",
458
- `Worker-session budget: ${sessionCap}. The worktree is a fresh checkout at ${worktreePath}.`
459
- ].join("\n")
460
- }
461
- ],
462
- maxTurns: opts.maxTurns ?? Math.max(8, 2 + sessionCap * 3),
463
- hooks: { stopBefore: () => signal.aborted }
464
- });
465
- signal.throwIfAborted();
466
- const verdict = await groundVerify();
467
- signal.throwIfAborted();
468
- if (!verdict.ok) return { applied: false, summary: "" };
469
- return { applied: true, summary: summarizeFindings(findings) };
470
- }
471
- };
472
- }
473
- var driverToolSpecs = [
474
- {
475
- type: "function",
476
- function: {
477
- name: "run_worker",
478
- description: "Run ONE coding-harness session in the worktree with your instruction as its entire goal. The worktree persists between sessions. Sessions are capped \u2014 author each instruction richly (outcome, context, placement, the check it is held to).",
479
- parameters: {
480
- type: "object",
481
- properties: {
482
- instruction: {
483
- type: "string",
484
- description: "The complete, self-contained goal for this worker session."
485
- }
486
- },
487
- required: ["instruction"]
488
- }
489
- }
490
- },
491
- {
492
- type: "function",
493
- function: {
494
- name: "inspect_worktree",
495
- description: "Current git state of the worktree: changed paths + the tracked-file diff (truncated). New untracked files show in changedPaths only \u2014 read_file them.",
496
- parameters: { type: "object", properties: {} }
497
- }
498
- },
499
- {
500
- type: "function",
501
- function: {
502
- name: "read_file",
503
- description: "Read one file from the worktree (paths are worktree-relative).",
504
- parameters: {
505
- type: "object",
506
- properties: {
507
- path: { type: "string", description: "Worktree-relative file path." },
508
- maxBytes: { type: "number", description: "Byte cap (default 8192)." }
509
- },
510
- required: ["path"]
511
- }
512
- }
513
- },
514
- {
515
- type: "function",
516
- function: {
517
- name: "run_verifier",
518
- description: "Run the intrinsic check of the surface (compile+tests / boot-and-probe). Its result \u2014 not your judgment \u2014 decides whether the candidate is kept.",
519
- parameters: { type: "object", properties: {} }
520
- }
521
- }
229
+ const harness = opts.harness ?? "claude";
230
+ const buildPrompt = opts.buildPrompt ?? defaultBuildPrompt;
231
+ const run = opts.runHarness ?? runLocalHarness;
232
+ const changed = opts.changedPaths ?? worktreeChangedPaths;
233
+ const readDiff = opts.readDiff ?? worktreeDiff;
234
+ const verify = opts.verify;
235
+ return {
236
+ kind: `driver-loop:${harness}`,
237
+ async generate({ worktreePath, report, findings, maxShots, signal }) {
238
+ signal.throwIfAborted();
239
+ const briefing = buildPrompt({
240
+ report,
241
+ findings
242
+ });
243
+ const needsRawTraceEvidence = requiresRawTraceEvidence(findings);
244
+ const sessionCap = Math.max(1, maxShots);
245
+ let sessionsUsed = 0;
246
+ const groundVerify = async () => {
247
+ signal.throwIfAborted();
248
+ if (changed(worktreePath).length === 0) return {
249
+ ok: false,
250
+ feedback: "the working tree has no changes nothing to verify"
251
+ };
252
+ if (needsRawTraceEvidence) {
253
+ const problem = rawTraceEvidenceProblem(worktreePath, findings);
254
+ if (problem) return {
255
+ ok: false,
256
+ feedback: problem
257
+ };
258
+ }
259
+ if (!verify) return {
260
+ ok: true,
261
+ feedback: "no verifier configured: a dirty tree is the candidate"
262
+ };
263
+ const result = await verify(worktreePath, signal);
264
+ signal.throwIfAborted();
265
+ return result;
266
+ };
267
+ const execute = async (name, args) => {
268
+ signal.throwIfAborted();
269
+ switch (name) {
270
+ case "run_worker": {
271
+ const instruction = typeof args.instruction === "string" ? args.instruction.trim() : "";
272
+ if (instruction.length === 0) return "error: run_worker requires a non-empty `instruction`";
273
+ if (sessionsUsed >= sessionCap) return `error: worker-session budget exhausted (${sessionsUsed}/${sessionCap} used). Inspect and verify what exists, then stop with your final assessment.`;
274
+ sessionsUsed += 1;
275
+ const result = await run({
276
+ harness,
277
+ cwd: worktreePath,
278
+ taskPrompt: instruction,
279
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
280
+ signal
281
+ });
282
+ signal.throwIfAborted();
283
+ if (result.aborted) throw new Error("driverLoopGenerator: worker session was cancelled by the caller");
284
+ return JSON.stringify({
285
+ session: `${sessionsUsed}/${sessionCap}`,
286
+ exitCode: result.exitCode,
287
+ timedOut: result.timedOut,
288
+ aborted: result.aborted ?? false,
289
+ killedBySignal: result.killedBySignal,
290
+ durationMs: result.durationMs,
291
+ changedPaths: changed(worktreePath),
292
+ stdoutTail: tail(result.stdout, workerOutputTailChars),
293
+ stderrTail: tail(result.stderr, workerOutputTailChars)
294
+ });
295
+ }
296
+ case "inspect_worktree": {
297
+ const paths = changed(worktreePath);
298
+ const diff = truncate(readDiff(worktreePath), diffMaxChars);
299
+ return JSON.stringify({
300
+ changedPaths: paths,
301
+ diff: diff.length > 0 ? diff : "(no tracked-file diff — new files are untracked; read_file them)"
302
+ });
303
+ }
304
+ case "read_file": return readWorktreeFile(worktreePath, args);
305
+ case "research": {
306
+ if (!opts.research) return "error: research tool is not provisioned in this run";
307
+ const query = typeof args.query === "string" ? args.query.trim() : "";
308
+ if (query.length === 0) return "error: research requires a non-empty `query`";
309
+ const result = await opts.research(query);
310
+ signal.throwIfAborted();
311
+ return truncate(result, researchResultMaxChars);
312
+ }
313
+ case "run_verifier": {
314
+ const result = await groundVerify();
315
+ return JSON.stringify({
316
+ ok: result.ok,
317
+ feedback: truncate(result.feedback ?? "", 4e3)
318
+ });
319
+ }
320
+ default: return `error: unknown tool: ${name}`;
321
+ }
322
+ };
323
+ await runBrainLoop({
324
+ chat: opts.brain,
325
+ tools: opts.research ? [...driverToolSpecs, researchToolSpec] : driverToolSpecs,
326
+ execute,
327
+ initialMessages: [{
328
+ role: "system",
329
+ content: opts.research ? `${buildDriverSystem}\n\n${researchDriverNote}` : buildDriverSystem
330
+ }, {
331
+ role: "user",
332
+ content: [
333
+ `THE BUILD BRIEF (the contract your workers must satisfy — fold what each needs into its instruction; workers never see this brief):`,
334
+ "",
335
+ briefing,
336
+ "",
337
+ `Worker-session budget: ${sessionCap}. The worktree is a fresh checkout at ${worktreePath}.`
338
+ ].join("\n")
339
+ }],
340
+ maxTurns: opts.maxTurns ?? Math.max(8, 2 + sessionCap * 3),
341
+ hooks: { stopBefore: () => signal.aborted }
342
+ });
343
+ signal.throwIfAborted();
344
+ const verdict = await groundVerify();
345
+ signal.throwIfAborted();
346
+ if (!verdict.ok) return {
347
+ applied: false,
348
+ summary: ""
349
+ };
350
+ return {
351
+ applied: true,
352
+ summary: summarizeFindings(findings)
353
+ };
354
+ }
355
+ };
356
+ }
357
+ const driverToolSpecs = [
358
+ {
359
+ type: "function",
360
+ function: {
361
+ name: "run_worker",
362
+ description: "Run ONE coding-harness session in the worktree with your instruction as its entire goal. The worktree persists between sessions. Sessions are capped — author each instruction richly (outcome, context, placement, the check it is held to).",
363
+ parameters: {
364
+ type: "object",
365
+ properties: { instruction: {
366
+ type: "string",
367
+ description: "The complete, self-contained goal for this worker session."
368
+ } },
369
+ required: ["instruction"]
370
+ }
371
+ }
372
+ },
373
+ {
374
+ type: "function",
375
+ function: {
376
+ name: "inspect_worktree",
377
+ description: "Current git state of the worktree: changed paths + the tracked-file diff (truncated). New untracked files show in changedPaths only — read_file them.",
378
+ parameters: {
379
+ type: "object",
380
+ properties: {}
381
+ }
382
+ }
383
+ },
384
+ {
385
+ type: "function",
386
+ function: {
387
+ name: "read_file",
388
+ description: "Read one file from the worktree (paths are worktree-relative).",
389
+ parameters: {
390
+ type: "object",
391
+ properties: {
392
+ path: {
393
+ type: "string",
394
+ description: "Worktree-relative file path."
395
+ },
396
+ maxBytes: {
397
+ type: "number",
398
+ description: "Byte cap (default 8192)."
399
+ }
400
+ },
401
+ required: ["path"]
402
+ }
403
+ }
404
+ },
405
+ {
406
+ type: "function",
407
+ function: {
408
+ name: "run_verifier",
409
+ description: "Run the intrinsic check of the surface (compile+tests / boot-and-probe). Its result — not your judgment — decides whether the candidate is kept.",
410
+ parameters: {
411
+ type: "object",
412
+ properties: {}
413
+ }
414
+ }
415
+ }
522
416
  ];
523
- var researchToolSpec = {
524
- type: "function",
525
- function: {
526
- name: "research",
527
- description: "Search external sources (MCP registries, vendor docs) for an EXISTING server that closes the capability gap \u2014 the adopt-not-build check. Returns text findings.",
528
- parameters: {
529
- type: "object",
530
- properties: {
531
- query: { type: "string", description: "What capability / server to search for." }
532
- },
533
- required: ["query"]
534
- }
535
- }
417
+ /** Only offered when `opts.research` is wired — a tool the driver cannot call
418
+ * must never appear in its tool list. */
419
+ const researchToolSpec = {
420
+ type: "function",
421
+ function: {
422
+ name: "research",
423
+ description: "Search external sources (MCP registries, vendor docs) for an EXISTING server that closes the capability gap — the adopt-not-build check. Returns text findings.",
424
+ parameters: {
425
+ type: "object",
426
+ properties: { query: {
427
+ type: "string",
428
+ description: "What capability / server to search for."
429
+ } },
430
+ required: ["query"]
431
+ }
432
+ }
536
433
  };
434
+ /** `git diff` over the worktree (tracked files). Fails loud like `worktreeChangedPaths` — a git
435
+ * fault on a fresh checkout is a broken setup, not an empty diff. */
537
436
  function worktreeDiff(worktreePath) {
538
- const result = spawnSync("git", ["diff"], { cwd: worktreePath, encoding: "utf-8" });
539
- if (result.error) {
540
- throw new Error(
541
- `driverLoopGenerator: git diff failed to spawn in ${worktreePath}: ${result.error.message}`
542
- );
543
- }
544
- if (result.status !== 0) {
545
- throw new Error(
546
- `driverLoopGenerator: git diff exited ${result.status} in ${worktreePath}: ${result.stderr.trim()}`
547
- );
548
- }
549
- return result.stdout;
550
- }
437
+ const result = spawnSync("git", ["diff"], {
438
+ cwd: worktreePath,
439
+ encoding: "utf-8"
440
+ });
441
+ if (result.error) throw new Error(`driverLoopGenerator: git diff failed to spawn in ${worktreePath}: ${result.error.message}`);
442
+ if (result.status !== 0) throw new Error(`driverLoopGenerator: git diff exited ${result.status} in ${worktreePath}: ${result.stderr.trim()}`);
443
+ return result.stdout;
444
+ }
445
+ /** Bounded, worktree-jailed file read for the driver's `read_file`. A path escaping the worktree
446
+ * is refused (the driver only rates work in the candidate tree; it has no business elsewhere). */
551
447
  function readWorktreeFile(worktreePath, args) {
552
- const rel = typeof args.path === "string" ? args.path : "";
553
- if (rel.length === 0) return "error: read_file requires `path`";
554
- const root = resolve(worktreePath);
555
- const target = resolve(root, rel);
556
- if (target !== root && !target.startsWith(root + sep)) {
557
- return `error: path escapes the worktree: ${rel}`;
558
- }
559
- const maxBytes = typeof args.maxBytes === "number" && args.maxBytes > 0 ? Math.min(args.maxBytes, 65536) : readFileDefaultBytes;
560
- try {
561
- const size = statSync(target).size;
562
- const body = readFileSync(target, "utf-8").slice(0, maxBytes);
563
- return size > maxBytes ? `${body}
564
- \u2026 (${size - maxBytes} bytes truncated)` : body;
565
- } catch (e) {
566
- return `error: ${e instanceof Error ? e.message : String(e)}`;
567
- }
448
+ const rel = typeof args.path === "string" ? args.path : "";
449
+ if (rel.length === 0) return "error: read_file requires `path`";
450
+ const root = resolve(worktreePath);
451
+ const target = resolve(root, rel);
452
+ if (target !== root && !target.startsWith(root + sep)) return `error: path escapes the worktree: ${rel}`;
453
+ const maxBytes = typeof args.maxBytes === "number" && args.maxBytes > 0 ? Math.min(args.maxBytes, 65536) : readFileDefaultBytes;
454
+ try {
455
+ const size = statSync(target).size;
456
+ const body = readFileSync(target, "utf-8").slice(0, maxBytes);
457
+ return size > maxBytes ? `${body}\n… (${size - maxBytes} bytes truncated)` : body;
458
+ } catch (e) {
459
+ return `error: ${e instanceof Error ? e.message : String(e)}`;
460
+ }
568
461
  }
569
462
  function tail(s, n) {
570
- const trimmed = s.trim();
571
- return trimmed.length <= n ? trimmed : `\u2026${trimmed.slice(-n)}`;
463
+ const trimmed = s.trim();
464
+ return trimmed.length <= n ? trimmed : `…${trimmed.slice(-n)}`;
572
465
  }
573
466
  function truncate(s, n) {
574
- return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
575
- }
576
-
577
- // src/improvement/mcp-serve-verifier.ts
467
+ return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
468
+ }
469
+ //#endregion
470
+ //#region src/improvement/mcp-serve-verifier.ts
471
+ /**
472
+ * `mcpServeVerifier` — the intrinsic verifier for a built MCP server: the
473
+ * boot-and-probe checker named in docs/artifact-lifecycle-frontier.md. A
474
+ * generated MCP server is only a candidate if it actually *serves* — so this
475
+ * boots it over stdio (the default local MCP transport) and runs the real
476
+ * handshake: `initialize` → `notifications/initialized` → `tools/list`, and
477
+ * asserts the server answers with at least `minTools` tools.
478
+ *
479
+ * The spawn + handshake is the SHARED same-host stdio connection
480
+ * (`connectStdioMcp`) — the same code path that later serves the built server
481
+ * LIVE to a scored run (`materializeLocalMcp`), so "verified it serves" and
482
+ * "served while scored" can never drift apart.
483
+ *
484
+ * Outcomes follow the `Verifier` contract: a server that fails to start, exits
485
+ * early, errors the handshake, times out, or exposes no tools is a FAILED
486
+ * candidate (`{ok:false}`, fed back into the next generation shot); a missing
487
+ * start binary or spawn fault THROWS (a setup bug, never a silent fallback).
488
+ */
489
+ /** Build a `Verifier` that boots a generated MCP server over stdio and checks it exposes tools. */
578
490
  function mcpServeVerifier(spec) {
579
- const minTools = spec.minTools ?? 1;
580
- return async (worktreePath) => {
581
- let conn;
582
- try {
583
- conn = await connectStdioMcp({
584
- command: spec.command,
585
- ...spec.args ? { args: spec.args } : {},
586
- cwd: worktreePath,
587
- ...spec.env ? { env: spec.env } : {},
588
- ...spec.timeoutMs !== void 0 ? { timeoutMs: spec.timeoutMs } : {}
589
- });
590
- } catch (err) {
591
- if (err instanceof McpSpawnFault) {
592
- throw new Error(`mcpServeVerifier: ${err.message}`);
593
- }
594
- return { ok: false, feedback: err instanceof Error ? err.message : String(err) };
595
- }
596
- try {
597
- if (conn.tools.length < minTools) {
598
- return {
599
- ok: false,
600
- feedback: `tools/list returned ${conn.tools.length} tool(s), need >= ${minTools}`
601
- };
602
- }
603
- return { ok: true };
604
- } finally {
605
- await conn.close();
606
- }
607
- };
608
- }
609
-
610
- // src/improvement/official-optimizers.ts
611
- import { isDeepStrictEqual } from "util";
612
- import { canonicalJson } from "@tangle-network/agent-eval";
613
- import {
614
- gepaOptimizationMethod,
615
- skillOptOptimizationMethod
616
- } from "@tangle-network/agent-eval/campaign";
617
- var defaultMaxFindingsChars = 5e4;
618
- var pythonClientDocs = "https://github.com/tangle-network/agent-eval/tree/main/clients/python";
619
- var bridgeInstall = '`python -m pip install "agent-eval-rpc==0.126.6"`';
620
- var gepaWheelInstall = '`python -m pip install "gepa[full]==0.1.4"`';
621
- var gepaSourceInstall = '`python -m pip install "gepa[full] @ git+https://github.com/gepa-ai/gepa.git@f919db0a622e2e9f9204779b81fe00cc1b2d808f"`';
622
- var skillOptInstall = `${bridgeInstall}, then \`python -m pip install "skillopt @ git+https://github.com/microsoft/SkillOpt.git@61735e3922efc2b90c6d6cab561e62e98452ca90"\``;
491
+ const minTools = spec.minTools ?? 1;
492
+ return async (worktreePath) => {
493
+ let conn;
494
+ try {
495
+ conn = await connectStdioMcp({
496
+ command: spec.command,
497
+ ...spec.args ? { args: spec.args } : {},
498
+ cwd: worktreePath,
499
+ ...spec.env ? { env: spec.env } : {},
500
+ ...spec.timeoutMs !== void 0 ? { timeoutMs: spec.timeoutMs } : {}
501
+ });
502
+ } catch (err) {
503
+ if (err instanceof McpSpawnFault) throw new Error(`mcpServeVerifier: ${err.message}`);
504
+ return {
505
+ ok: false,
506
+ feedback: err instanceof Error ? err.message : String(err)
507
+ };
508
+ }
509
+ try {
510
+ if (conn.tools.length < minTools) return {
511
+ ok: false,
512
+ feedback: `tools/list returned ${conn.tools.length} tool(s), need >= ${minTools}`
513
+ };
514
+ return { ok: true };
515
+ } finally {
516
+ await conn.close();
517
+ }
518
+ };
519
+ }
520
+ //#endregion
521
+ //#region src/improvement/official-optimizers.ts
522
+ const defaultMaxFindingsChars = 5e4;
523
+ const pythonClientDocs = "https://github.com/tangle-network/agent-eval/tree/main/clients/python";
524
+ const bridgeInstall = "`python -m pip install \"agent-eval-rpc==0.131.0\"`";
525
+ const gepaWheelInstall = "`python -m pip install \"gepa[full]==0.1.4\"`";
526
+ const gepaSourceInstall = "`python -m pip install \"gepa[full] @ git+https://github.com/gepa-ai/gepa.git@f919db0a622e2e9f9204779b81fe00cc1b2d808f\"`";
527
+ const skillOptInstall = `${bridgeInstall}, then \`python -m pip install "skillopt @ git+https://github.com/microsoft/SkillOpt.git@61735e3922efc2b90c6d6cab561e62e98452ca90"\``;
528
+ /** Missing optional Python dependencies for an official optimizer. */
623
529
  var OfficialOptimizerUnavailableError = class extends ConfigError {
624
- optimizer;
625
- constructor(optimizer, cause) {
626
- const detail = cause instanceof Error ? cause.message : String(cause);
627
- const install = optimizer === "gepa" ? [
628
- `Install the Python bridge: ${bridgeInstall}.`,
629
- `The direct GEPA engine uses the published wheel: ${gepaWheelInstall}.`,
630
- `Composed recipes and source-only engines use the tested source revision: ${gepaSourceInstall}.`
631
- ].join(" ") : `Install Microsoft SkillOpt: ${skillOptInstall}.`;
632
- super(
633
- [
634
- `Official ${optimizer === "gepa" ? "GEPA" : "SkillOpt"} could not start.`,
635
- "Runtime did not use a local fallback.",
636
- install,
637
- `Setup: ${pythonClientDocs}.`,
638
- `Cause: ${detail}`
639
- ].join(" "),
640
- { cause }
641
- );
642
- this.optimizer = optimizer;
643
- }
530
+ optimizer;
531
+ constructor(optimizer, cause) {
532
+ const detail = cause instanceof Error ? cause.message : String(cause);
533
+ const install = optimizer === "gepa" ? [
534
+ `Install the Python bridge: ${bridgeInstall}.`,
535
+ `The direct GEPA engine uses the published wheel: ${gepaWheelInstall}.`,
536
+ `Composed recipes and source-only engines use the tested source revision: ${gepaSourceInstall}.`
537
+ ].join(" ") : `Install Microsoft SkillOpt: ${skillOptInstall}.`;
538
+ super([
539
+ `Official ${optimizer === "gepa" ? "GEPA" : "SkillOpt"} could not start.`,
540
+ "Runtime did not use a local fallback.",
541
+ install,
542
+ `Setup: ${pythonClientDocs}.`,
543
+ `Cause: ${detail}`
544
+ ].join(" "), { cause });
545
+ this.optimizer = optimizer;
546
+ }
644
547
  };
548
+ /**
549
+ * Build a complete method backed by GEPA's official Optimize Anything API.
550
+ *
551
+ * The recipe is passed through unchanged. Use `engine`, `sequential`,
552
+ * `adaptive-sequential`, `best-of`, `vote`, or `omni` explicitly.
553
+ */
645
554
  function officialGepa(options) {
646
- const {
647
- background,
648
- includeFindings = true,
649
- maxFindingsChars,
650
- describeScenario,
651
- describeArtifact,
652
- redact,
653
- authorizeSensitiveCandidate,
654
- ...config
655
- } = options;
656
- const redactor = resolveRedactor(redact);
657
- const redactionPolicyRef = optimizerRedactionPolicyRef(redact);
658
- assertMaxFindingsChars("officialGepa", maxFindingsChars);
659
- const objective = redactOptimizerText("officialGepa", "objective", config.objective, redactor);
660
- return (context) => {
661
- const externalEvaluationRef = optimizerEvidencePolicyRef({
662
- runtimeEvaluationRef: context.evaluationRef,
663
- redactionPolicyRef,
664
- describeScenario,
665
- describeArtifact,
666
- authorizeSensitiveCandidate
667
- });
668
- const method = withDependencyHelp(
669
- "gepa",
670
- externalEvaluationRef,
671
- redactor,
672
- redactionPolicyRef,
673
- gepaOptimizationMethod({
674
- ...config,
675
- objective,
676
- evaluationId: externalEvaluationRef,
677
- background: methodBackground({
678
- context,
679
- background,
680
- includeFindings,
681
- maxFindingsChars,
682
- label: "officialGepa",
683
- redactor
684
- }),
685
- ...describeScenario ? {
686
- describeScenario: (scenario) => redactOptimizerEvidence(
687
- "officialGepa",
688
- "scenario descriptor",
689
- describeScenario(scenario),
690
- redactor
691
- )
692
- } : {},
693
- ...describeArtifact ? {
694
- describeArtifact: (artifact, scenario) => redactOptimizerEvidence(
695
- "officialGepa",
696
- "artifact descriptor",
697
- describeArtifact(artifact, scenario),
698
- redactor
699
- )
700
- } : {}
701
- })
702
- );
703
- return withMethodRuntimeControls(method, {
704
- costAttribution: "optimizer-run",
705
- validateCandidate: (input) => assertSafeOptimizerCandidate("officialGepa", input, authorizeSensitiveCandidate)
706
- });
707
- };
708
- }
555
+ const { background, includeFindings = true, maxFindingsChars, describeScenario, describeArtifact, redact, authorizeSensitiveCandidate, ...config } = options;
556
+ const redactor = resolveRedactor(redact);
557
+ const redactionPolicyRef = optimizerRedactionPolicyRef(redact);
558
+ assertMaxFindingsChars("officialGepa", maxFindingsChars);
559
+ const objective = redactOptimizerText("officialGepa", "objective", config.objective, redactor);
560
+ return (context) => {
561
+ const externalEvaluationRef = optimizerEvidencePolicyRef({
562
+ runtimeEvaluationRef: context.evaluationRef,
563
+ redactionPolicyRef,
564
+ describeScenario,
565
+ describeArtifact,
566
+ authorizeSensitiveCandidate
567
+ });
568
+ return withMethodRuntimeControls(withDependencyHelp("gepa", externalEvaluationRef, redactor, redactionPolicyRef, gepaOptimizationMethod({
569
+ ...config,
570
+ objective,
571
+ evaluationId: externalEvaluationRef,
572
+ background: methodBackground({
573
+ context,
574
+ background,
575
+ includeFindings,
576
+ maxFindingsChars,
577
+ label: "officialGepa",
578
+ redactor
579
+ }),
580
+ ...describeScenario ? { describeScenario: (scenario) => redactOptimizerEvidence("officialGepa", "scenario descriptor", describeScenario(scenario), redactor) } : {},
581
+ ...describeArtifact ? { describeArtifact: (artifact, scenario) => redactOptimizerEvidence("officialGepa", "artifact descriptor", describeArtifact(artifact, scenario), redactor) } : {}
582
+ })), {
583
+ costAttribution: "optimizer-run",
584
+ validateCandidate: (input) => assertSafeOptimizerCandidate("officialGepa", input, authorizeSensitiveCandidate)
585
+ });
586
+ };
587
+ }
588
+ /** Build a complete method backed by Microsoft's official SkillOpt trainer. */
709
589
  function officialSkillOpt(options) {
710
- const {
711
- background,
712
- includeFindings = true,
713
- maxFindingsChars,
714
- describeScenario,
715
- describeArtifact,
716
- redact,
717
- authorizeSensitiveCandidate,
718
- ...config
719
- } = options;
720
- const redactor = resolveRedactor(redact);
721
- const redactionPolicyRef = optimizerRedactionPolicyRef(redact);
722
- assertMaxFindingsChars("officialSkillOpt", maxFindingsChars);
723
- const objective = redactOptimizerText("officialSkillOpt", "objective", config.objective, redactor);
724
- return (context) => {
725
- const externalEvaluationRef = optimizerEvidencePolicyRef({
726
- runtimeEvaluationRef: context.evaluationRef,
727
- redactionPolicyRef,
728
- describeScenario,
729
- describeArtifact,
730
- authorizeSensitiveCandidate
731
- });
732
- const method = withDependencyHelp(
733
- "skillopt",
734
- externalEvaluationRef,
735
- redactor,
736
- redactionPolicyRef,
737
- skillOptOptimizationMethod({
738
- ...config,
739
- objective,
740
- evaluationId: externalEvaluationRef,
741
- background: methodBackground({
742
- context,
743
- background,
744
- includeFindings,
745
- maxFindingsChars,
746
- label: "officialSkillOpt",
747
- redactor
748
- }),
749
- ...describeScenario ? {
750
- describeScenario: (scenario) => redactOptimizerEvidence(
751
- "officialSkillOpt",
752
- "scenario descriptor",
753
- describeScenario(scenario),
754
- redactor
755
- )
756
- } : {},
757
- ...describeArtifact ? {
758
- describeArtifact: (artifact, scenario) => redactOptimizerEvidence(
759
- "officialSkillOpt",
760
- "artifact descriptor",
761
- describeArtifact(artifact, scenario),
762
- redactor
763
- )
764
- } : {}
765
- })
766
- );
767
- return withMethodRuntimeControls(method, {
768
- costAttribution: "optimizer-run",
769
- validateCandidate: (input) => assertSafeOptimizerCandidate("officialSkillOpt", input, authorizeSensitiveCandidate)
770
- });
771
- };
590
+ const { background, includeFindings = true, maxFindingsChars, describeScenario, describeArtifact, redact, authorizeSensitiveCandidate, ...config } = options;
591
+ const redactor = resolveRedactor(redact);
592
+ const redactionPolicyRef = optimizerRedactionPolicyRef(redact);
593
+ assertMaxFindingsChars("officialSkillOpt", maxFindingsChars);
594
+ const objective = redactOptimizerText("officialSkillOpt", "objective", config.objective, redactor);
595
+ return (context) => {
596
+ const externalEvaluationRef = optimizerEvidencePolicyRef({
597
+ runtimeEvaluationRef: context.evaluationRef,
598
+ redactionPolicyRef,
599
+ describeScenario,
600
+ describeArtifact,
601
+ authorizeSensitiveCandidate
602
+ });
603
+ return withMethodRuntimeControls(withDependencyHelp("skillopt", externalEvaluationRef, redactor, redactionPolicyRef, skillOptOptimizationMethod({
604
+ ...config,
605
+ objective,
606
+ evaluationId: externalEvaluationRef,
607
+ background: methodBackground({
608
+ context,
609
+ background,
610
+ includeFindings,
611
+ maxFindingsChars,
612
+ label: "officialSkillOpt",
613
+ redactor
614
+ }),
615
+ ...describeScenario ? { describeScenario: (scenario) => redactOptimizerEvidence("officialSkillOpt", "scenario descriptor", describeScenario(scenario), redactor) } : {},
616
+ ...describeArtifact ? { describeArtifact: (artifact, scenario) => redactOptimizerEvidence("officialSkillOpt", "artifact descriptor", describeArtifact(artifact, scenario), redactor) } : {}
617
+ })), {
618
+ costAttribution: "optimizer-run",
619
+ validateCandidate: (input) => assertSafeOptimizerCandidate("officialSkillOpt", input, authorizeSensitiveCandidate)
620
+ });
621
+ };
772
622
  }
773
623
  function assertMaxFindingsChars(label, value) {
774
- if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) {
775
- throw new ConfigError(`${label}: maxFindingsChars must be a positive safe integer`);
776
- }
624
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new ConfigError(`${label}: maxFindingsChars must be a positive safe integer`);
777
625
  }
778
626
  function methodBackground(options) {
779
- const {
780
- context,
781
- background,
782
- includeFindings,
783
- maxFindingsChars = defaultMaxFindingsChars,
784
- label,
785
- redactor
786
- } = options;
787
- const safeBackground = background === void 0 ? void 0 : redactOptimizerText(label, "background", background, redactor);
788
- const safeProfileName = context.profile.name === void 0 ? void 0 : redactOptimizerText(label, "profile name", context.profile.name, redactor);
789
- const sections = [
790
- safeBackground?.trim(),
791
- safeProfileName ? `Agent profile: ${safeProfileName}. Surface: ${context.surface}.` : `Agent surface: ${context.surface}.`
792
- ].filter((value) => Boolean(value));
793
- if (includeFindings && context.findings.length > 0) {
794
- let serialized;
795
- try {
796
- serialized = canonicalJson(
797
- redactOptimizerEvidence(label, "findings", context.findings, redactor)
798
- );
799
- } catch (cause) {
800
- throw new ConfigError(`${label}: findings must be JSON-serializable`, { cause });
801
- }
802
- if (serialized.length > maxFindingsChars) {
803
- throw new ConfigError(
804
- `${label}: serialized findings exceed maxFindingsChars (${serialized.length} > ${maxFindingsChars})`
805
- );
806
- }
807
- sections.push(`Observed failures:
808
- ${serialized}`);
809
- }
810
- return sections.join("\n\n");
627
+ const { context, background, includeFindings, maxFindingsChars = defaultMaxFindingsChars, label, redactor } = options;
628
+ const safeBackground = background === void 0 ? void 0 : redactOptimizerText(label, "background", background, redactor);
629
+ const safeProfileName = context.profile.name === void 0 ? void 0 : redactOptimizerText(label, "profile name", context.profile.name, redactor);
630
+ const sections = [safeBackground?.trim(), safeProfileName ? `Agent profile: ${safeProfileName}. Surface: ${context.surface}.` : `Agent surface: ${context.surface}.`].filter((value) => Boolean(value));
631
+ if (includeFindings && context.findings.length > 0) {
632
+ let serialized;
633
+ try {
634
+ serialized = canonicalJson(redactOptimizerEvidence(label, "findings", context.findings, redactor));
635
+ } catch (cause) {
636
+ throw new ConfigError(`${label}: findings must be JSON-serializable`, { cause });
637
+ }
638
+ if (serialized.length > maxFindingsChars) throw new ConfigError(`${label}: serialized findings exceed maxFindingsChars (${serialized.length} > ${maxFindingsChars})`);
639
+ sections.push(`Observed failures:\n${serialized}`);
640
+ }
641
+ return sections.join("\n\n");
811
642
  }
812
643
  function assertSafeOptimizerCandidate(label, input, authorizeSensitiveCandidate) {
813
- const redactedValue = defaultRedactor(input.value);
814
- const redactedSurface = defaultRedactor(input.candidateSurface);
815
- if (!isDeepStrictEqual(input.value, redactedValue) || !isDeepStrictEqual(input.candidateSurface, redactedSurface)) {
816
- throw new ConfigError(
817
- `${label}: the selected profile surface contains a common credential or private value. Store live credentials as provider references, or remove private data before starting an external optimizer.`
818
- );
819
- }
820
- const sensitivePaths = sensitiveProfileSurfacePaths(input);
821
- if (sensitivePaths.length === 0) return;
822
- let authorized = false;
823
- if (authorizeSensitiveCandidate) {
824
- try {
825
- authorized = authorizeSensitiveCandidate(
826
- Object.freeze({
827
- ...input,
828
- sensitivePaths: Object.freeze([...sensitivePaths])
829
- })
830
- ) === true;
831
- } catch (cause) {
832
- throw new ConfigError(`${label}: sensitive candidate authorization failed`, { cause });
833
- }
834
- }
835
- if (!authorized) {
836
- throw new ConfigError(
837
- `${label}: the selected profile surface contains fields that may carry private values: ${sensitivePaths.slice(0, 8).join(", ")}. Remove them, replace values with safe references, or authorize the exact profile with authorizeSensitiveCandidate.`
838
- );
839
- }
644
+ const redactedValue = defaultRedactor(input.value);
645
+ const redactedSurface = defaultRedactor(input.candidateSurface);
646
+ if (!isDeepStrictEqual(input.value, redactedValue) || !isDeepStrictEqual(input.candidateSurface, redactedSurface)) throw new ConfigError(`${label}: the selected profile surface contains a common credential or private value. Store live credentials as provider references, or remove private data before starting an external optimizer.`);
647
+ const sensitivePaths = sensitiveProfileSurfacePaths(input);
648
+ if (sensitivePaths.length === 0) return;
649
+ let authorized = false;
650
+ if (authorizeSensitiveCandidate) try {
651
+ authorized = authorizeSensitiveCandidate(Object.freeze({
652
+ ...input,
653
+ sensitivePaths: Object.freeze([...sensitivePaths])
654
+ })) === true;
655
+ } catch (cause) {
656
+ throw new ConfigError(`${label}: sensitive candidate authorization failed`, { cause });
657
+ }
658
+ if (!authorized) throw new ConfigError(`${label}: the selected profile surface contains fields that may carry private values: ${sensitivePaths.slice(0, 8).join(", ")}. Remove them, replace values with safe references, or authorize the exact profile with authorizeSensitiveCandidate.`);
840
659
  }
841
660
  function sensitiveProfileSurfacePaths(input) {
842
- const paths = /* @__PURE__ */ new Set();
843
- if (input.surface === "tools" || input.surface === "mcp" || input.surface === "hooks" || input.surface === "subagents" || input.surface === "agent-profile") {
844
- paths.add("$");
845
- }
846
- const seen = /* @__PURE__ */ new WeakSet();
847
- const visit = (current, path) => {
848
- if (current === null || typeof current !== "object") return;
849
- if (seen.has(current)) return;
850
- seen.add(current);
851
- if (Array.isArray(current)) {
852
- current.forEach((child, index) => {
853
- visit(child, `${path}[${index}]`);
854
- });
855
- return;
856
- }
857
- for (const [key, child] of Object.entries(current)) {
858
- const childPath = `${path}.${key}`;
859
- if (["env", "headers", "url", "metadata", "extensions"].includes(key.toLowerCase())) {
860
- paths.add(childPath);
861
- continue;
862
- }
863
- visit(child, childPath);
864
- }
865
- };
866
- visit(input.value, "$");
867
- return [...paths];
661
+ const paths = /* @__PURE__ */ new Set();
662
+ if (input.surface === "tools" || input.surface === "mcp" || input.surface === "hooks" || input.surface === "subagents" || input.surface === "agent-profile") paths.add("$");
663
+ const seen = /* @__PURE__ */ new WeakSet();
664
+ const visit = (current, path) => {
665
+ if (current === null || typeof current !== "object") return;
666
+ if (seen.has(current)) return;
667
+ seen.add(current);
668
+ if (Array.isArray(current)) {
669
+ current.forEach((child, index) => {
670
+ visit(child, `${path}[${index}]`);
671
+ });
672
+ return;
673
+ }
674
+ for (const [key, child] of Object.entries(current)) {
675
+ const childPath = `${path}.${key}`;
676
+ if ([
677
+ "env",
678
+ "headers",
679
+ "url",
680
+ "metadata",
681
+ "extensions"
682
+ ].includes(key.toLowerCase())) {
683
+ paths.add(childPath);
684
+ continue;
685
+ }
686
+ visit(child, childPath);
687
+ }
688
+ };
689
+ visit(input.value, "$");
690
+ return [...paths];
868
691
  }
869
692
  function redactOptimizerEvidence(label, field, value, redactor) {
870
- try {
871
- return redactor(value);
872
- } catch (cause) {
873
- throw new ConfigError(`${label}: ${field} redaction failed`, { cause });
874
- }
693
+ try {
694
+ return redactor(value);
695
+ } catch (cause) {
696
+ throw new ConfigError(`${label}: ${field} redaction failed`, { cause });
697
+ }
875
698
  }
876
699
  function redactOptimizerText(label, field, value, redactor) {
877
- const redacted = redactOptimizerEvidence(label, field, value, redactor);
878
- if (typeof redacted !== "string" || !redacted.trim()) {
879
- throw new ConfigError(`${label}: ${field} redaction must return a non-empty string`);
880
- }
881
- return redacted;
700
+ const redacted = redactOptimizerEvidence(label, field, value, redactor);
701
+ if (typeof redacted !== "string" || !redacted.trim()) throw new ConfigError(`${label}: ${field} redaction must return a non-empty string`);
702
+ return redacted;
882
703
  }
883
704
  function redactJudgeScore(score, redactor) {
884
- const notes = redactOptimizerEvidence("official optimizer", "judge notes", score.notes, redactor);
885
- return {
886
- ...score,
887
- notes: typeof notes === "string" ? notes : "[redacted]"
888
- };
705
+ const notes = redactOptimizerEvidence("official optimizer", "judge notes", score.notes, redactor);
706
+ return {
707
+ ...score,
708
+ notes: typeof notes === "string" ? notes : "[redacted]"
709
+ };
889
710
  }
890
711
  function optimizerRedactionPolicyRef(redact, builtInIdentity = defaultRedactorIdentityMaterial()) {
891
- if (redact === false) return "caller-approved-raw";
892
- return canonicalCandidateDigest({
893
- kind: redact === void 0 ? "default-redactor" : "caller-redactor-with-default",
894
- builtIn: builtInIdentity,
895
- ...redact === void 0 ? {} : {
896
- callerSource: Function.prototype.toString.call(redact),
897
- composition: Function.prototype.toString.call(resolveRedactor)
898
- }
899
- });
712
+ if (redact === false) return "caller-approved-raw";
713
+ return canonicalCandidateDigest({
714
+ kind: redact === void 0 ? "default-redactor" : "caller-redactor-with-default",
715
+ builtIn: builtInIdentity,
716
+ ...redact === void 0 ? {} : {
717
+ callerSource: Function.prototype.toString.call(redact),
718
+ composition: Function.prototype.toString.call(resolveRedactor)
719
+ }
720
+ });
900
721
  }
901
722
  function optimizerEvidencePolicyRef(input) {
902
- return canonicalCandidateDigest({
903
- runtimeEvaluationRef: input.runtimeEvaluationRef,
904
- redactionPolicyRef: input.redactionPolicyRef,
905
- describeScenario: callbackSource(input.describeScenario),
906
- describeArtifact: callbackSource(input.describeArtifact),
907
- authorizeSensitiveCandidate: callbackSource(input.authorizeSensitiveCandidate)
908
- });
723
+ return canonicalCandidateDigest({
724
+ runtimeEvaluationRef: input.runtimeEvaluationRef,
725
+ redactionPolicyRef: input.redactionPolicyRef,
726
+ describeScenario: callbackSource(input.describeScenario),
727
+ describeArtifact: callbackSource(input.describeArtifact),
728
+ authorizeSensitiveCandidate: callbackSource(input.authorizeSensitiveCandidate)
729
+ });
909
730
  }
910
731
  function callbackSource(callback) {
911
- return typeof callback === "function" ? Function.prototype.toString.call(callback) : null;
732
+ return typeof callback === "function" ? Function.prototype.toString.call(callback) : null;
912
733
  }
913
734
  function withDependencyHelp(optimizer, evaluationRef, redactor, redactionPolicyRef, method) {
914
- return {
915
- ...method,
916
- async optimize(input) {
917
- try {
918
- const judges = input.judges.map(
919
- (judge) => Object.freeze({
920
- ...judge,
921
- judgeVersion: canonicalCandidateDigest({
922
- evaluationRef,
923
- name: judge.name,
924
- dimensions: judge.dimensions,
925
- judgeVersion: judge.judgeVersion ?? null,
926
- outwardEvidence: redactionPolicyRef
927
- }),
928
- async score(scoreInput) {
929
- return redactJudgeScore(await judge.score(scoreInput), redactor);
930
- }
931
- })
932
- );
933
- return await method.optimize({
934
- ...input,
935
- judges: Object.freeze(judges)
936
- });
937
- } catch (cause) {
938
- if (isMissingDependency(optimizer, cause)) {
939
- throw new OfficialOptimizerUnavailableError(optimizer, cause);
940
- }
941
- throw cause;
942
- }
943
- }
944
- };
735
+ return {
736
+ ...method,
737
+ async optimize(input) {
738
+ try {
739
+ const judges = input.judges.map((judge) => Object.freeze({
740
+ ...judge,
741
+ judgeVersion: canonicalCandidateDigest({
742
+ evaluationRef,
743
+ name: judge.name,
744
+ dimensions: judge.dimensions,
745
+ judgeVersion: judge.judgeVersion ?? null,
746
+ outwardEvidence: redactionPolicyRef
747
+ }),
748
+ async score(scoreInput) {
749
+ return redactJudgeScore(await judge.score(scoreInput), redactor);
750
+ }
751
+ }));
752
+ return await method.optimize({
753
+ ...input,
754
+ judges: Object.freeze(judges)
755
+ });
756
+ } catch (cause) {
757
+ if (isMissingDependency(optimizer, cause)) throw new OfficialOptimizerUnavailableError(optimizer, cause);
758
+ throw cause;
759
+ }
760
+ }
761
+ };
945
762
  }
946
763
  function isMissingDependency(optimizer, cause) {
947
- const message = cause instanceof Error ? cause.message : String(cause);
948
- const common = [
949
- `${optimizer === "gepa" ? "GEPA" : "SkillOpt"} bridge could not start`,
950
- "source inspection could not start",
951
- "No module named 'agent_eval_rpc'",
952
- `No module named 'agent_eval_rpc.${optimizer === "gepa" ? "gepa_bridge" : "skillopt_bridge"}'`
953
- ];
954
- const specific = optimizer === "gepa" ? [
955
- "requires GEPA",
956
- "requires GEPA's Optimize Anything",
957
- "No module named 'gepa'",
958
- "gepa is importable but its package metadata is unavailable"
959
- ] : [
960
- "requires skillopt",
961
- "requires SkillOpt",
962
- "No module named 'skillopt'",
963
- "skillopt is importable but its package metadata is unavailable"
964
- ];
965
- return [...common, ...specific].some((fragment) => message.includes(fragment));
966
- }
967
-
968
- // src/improvement/reflective-generator.ts
969
- import { spawnSync as spawnSync2 } from "child_process";
764
+ const message = cause instanceof Error ? cause.message : String(cause);
765
+ const common = [
766
+ `${optimizer === "gepa" ? "GEPA" : "SkillOpt"} bridge could not start`,
767
+ "source inspection could not start",
768
+ "No module named 'agent_eval_rpc'",
769
+ `No module named 'agent_eval_rpc.${optimizer === "gepa" ? "gepa_bridge" : "skillopt_bridge"}'`
770
+ ];
771
+ const specific = optimizer === "gepa" ? [
772
+ "requires GEPA",
773
+ "requires GEPA's Optimize Anything",
774
+ "No module named 'gepa'",
775
+ "gepa is importable but its package metadata is unavailable"
776
+ ] : [
777
+ "requires skillopt",
778
+ "requires SkillOpt",
779
+ "No module named 'skillopt'",
780
+ "skillopt is importable but its package metadata is unavailable"
781
+ ];
782
+ return [...common, ...specific].some((fragment) => message.includes(fragment));
783
+ }
784
+ //#endregion
785
+ //#region src/improvement/reflective-generator.ts
786
+ /**
787
+ *
788
+ * `reflectiveGenerator` — the cheap, no-sandbox `CandidateGenerator`. It drafts
789
+ * surface edits via the existing improvement proposer (`proposeFromFindings`,
790
+ * one LLM patch per finding) and applies them as ONE coherent improvement into
791
+ * the candidate worktree. `maxShots` is ignored — reflection is single-shot by
792
+ * construction (the patches are already drafted).
793
+ *
794
+ * This is the `shots=1, sandbox=off` code-candidate setting.
795
+ * `agenticGenerator` supplies the multi-shot verify-in-session setting.
796
+ *
797
+ * @experimental
798
+ */
799
+ /** Cheap no-sandbox `CandidateGenerator` (the `shots=1` setting): draft surface edits via the improvement adapter and apply them as one coherent candidate. */
970
800
  function reflectiveGenerator(opts) {
971
- return {
972
- kind: "reflective",
973
- async generate({ worktreePath, findings }) {
974
- const batch = await opts.improvementProposalSource.proposeFromFindings(findings);
975
- if (batch.edits.length === 0) return { applied: false, summary: "" };
976
- let applied = 0;
977
- for (const edit of batch.edits) {
978
- if (applyPatch(edit.patch, worktreePath)) applied++;
979
- }
980
- if (applied === 0) return { applied: false, summary: "" };
981
- const summary = batch.edits.length === 1 ? batch.edits[0].summary : `analyst: ${applied} surface edit${applied === 1 ? "" : "s"}`;
982
- return { applied: true, summary };
983
- }
984
- };
985
- }
801
+ return {
802
+ kind: "reflective",
803
+ async generate({ worktreePath, findings }) {
804
+ const batch = await opts.improvementProposalSource.proposeFromFindings(findings);
805
+ if (batch.edits.length === 0) return {
806
+ applied: false,
807
+ summary: ""
808
+ };
809
+ let applied = 0;
810
+ for (const edit of batch.edits) if (applyPatch(edit.patch, worktreePath)) applied++;
811
+ if (applied === 0) return {
812
+ applied: false,
813
+ summary: ""
814
+ };
815
+ return {
816
+ applied: true,
817
+ summary: batch.edits.length === 1 ? batch.edits[0].summary : `analyst: ${applied} surface edit${applied === 1 ? "" : "s"}`
818
+ };
819
+ }
820
+ };
821
+ }
822
+ /** Apply a proposed patch inside the isolated candidate worktree.
823
+ * candidate worktree (a fresh checkout of baseRef, so `-p0` paths match). */
986
824
  function applyPatch(patch, cwd) {
987
- const result = spawnSync2("git", ["apply", "--whitespace=fix", "-p0", "-"], {
988
- cwd,
989
- input: patch,
990
- encoding: "utf-8"
991
- });
992
- return result.status === 0;
993
- }
994
-
995
- // src/readiness.ts
996
- var DEFAULT_MINIMUM_READINESS_SCORE = 0.7;
825
+ return spawnSync("git", [
826
+ "apply",
827
+ "--whitespace=fix",
828
+ "-p0",
829
+ "-"
830
+ ], {
831
+ cwd,
832
+ input: patch,
833
+ encoding: "utf-8"
834
+ }).status === 0;
835
+ }
836
+ //#endregion
837
+ //#region src/readiness.ts
838
+ const DEFAULT_MINIMUM_READINESS_SCORE = .7;
839
+ /**
840
+ * Map a `KnowledgeReadinessReport` to a three-state branch (`ready` / `blocked` / `caveat`) the runtime, route handlers, and UI shells all switch on.
841
+ *
842
+ * @stable
843
+ */
997
844
  function decideKnowledgeReadiness(report, options = {}) {
998
- const minimumScore = options.minimumScore ?? DEFAULT_MINIMUM_READINESS_SCORE;
999
- if (!Number.isFinite(minimumScore) || minimumScore < 0 || minimumScore > 1) {
1000
- throw new ValidationError(
1001
- `minimumScore must be a finite number in [0, 1]; received ${String(minimumScore)}`
1002
- );
1003
- }
1004
- const blockingGapIds = report.blockingMissingRequirements.map((requirement) => requirement.id);
1005
- const nonBlockingGapIds = report.nonBlockingGaps.map((requirement) => requirement.id);
1006
- if (blockingGapIds.length > 0) {
1007
- return {
1008
- passed: false,
1009
- status: "blocked",
1010
- reason: report.reason,
1011
- readinessScore: report.readinessScore,
1012
- recommendedAction: report.recommendedAction,
1013
- severity: report.severity,
1014
- blockingGapIds,
1015
- nonBlockingGapIds
1016
- };
1017
- }
1018
- if (report.readinessScore < minimumScore) {
1019
- return {
1020
- passed: false,
1021
- status: "caveat",
1022
- reason: `Knowledge readiness score ${report.readinessScore.toFixed(3)} is below minimum ${minimumScore.toFixed(3)}.`,
1023
- readinessScore: report.readinessScore,
1024
- recommendedAction: report.recommendedAction,
1025
- severity: report.severity,
1026
- blockingGapIds,
1027
- nonBlockingGapIds
1028
- };
1029
- }
1030
- return {
1031
- passed: true,
1032
- status: "ready",
1033
- reason: report.reason,
1034
- readinessScore: report.readinessScore,
1035
- recommendedAction: report.recommendedAction,
1036
- severity: report.severity,
1037
- blockingGapIds,
1038
- nonBlockingGapIds
1039
- };
1040
- }
1041
-
1042
- // src/resolve-agent-backend.ts
845
+ const minimumScore = options.minimumScore ?? DEFAULT_MINIMUM_READINESS_SCORE;
846
+ if (!Number.isFinite(minimumScore) || minimumScore < 0 || minimumScore > 1) throw new ValidationError(`minimumScore must be a finite number in [0, 1]; received ${String(minimumScore)}`);
847
+ const blockingGapIds = report.blockingMissingRequirements.map((requirement) => requirement.id);
848
+ const nonBlockingGapIds = report.nonBlockingGaps.map((requirement) => requirement.id);
849
+ if (blockingGapIds.length > 0) return {
850
+ passed: false,
851
+ status: "blocked",
852
+ reason: report.reason,
853
+ readinessScore: report.readinessScore,
854
+ recommendedAction: report.recommendedAction,
855
+ severity: report.severity,
856
+ blockingGapIds,
857
+ nonBlockingGapIds
858
+ };
859
+ if (report.readinessScore < minimumScore) return {
860
+ passed: false,
861
+ status: "caveat",
862
+ reason: `Knowledge readiness score ${report.readinessScore.toFixed(3)} is below minimum ${minimumScore.toFixed(3)}.`,
863
+ readinessScore: report.readinessScore,
864
+ recommendedAction: report.recommendedAction,
865
+ severity: report.severity,
866
+ blockingGapIds,
867
+ nonBlockingGapIds
868
+ };
869
+ return {
870
+ passed: true,
871
+ status: "ready",
872
+ reason: report.reason,
873
+ readinessScore: report.readinessScore,
874
+ recommendedAction: report.recommendedAction,
875
+ severity: report.severity,
876
+ blockingGapIds,
877
+ nonBlockingGapIds
878
+ };
879
+ }
880
+ //#endregion
881
+ //#region src/resolve-agent-backend.ts
882
+ /**
883
+ * The product-facing backend selector for `runChatThroughRuntime` /
884
+ * `runAgentTaskStream`: one call turns a `--backend {router,tcloud,cli-bridge,
885
+ * sandbox}` choice into the `AgentExecutionBackend` the chat leg runs on.
886
+ *
887
+ * It is the `AgentExecutionBackend` sibling of `resolveSandboxClient` (which
888
+ * resolves the `SandboxClient` a `runAgentRounds` drives). Both exist for the same
889
+ * reason: every in-process eval product hand-rolled the identical
890
+ * "`backend-name` → `createOpenAICompatibleBackend`" branch, and the copies
891
+ * drift. This is the single generic resolver they share.
892
+ *
893
+ * - `router` / `tcloud` / `cli-bridge` → OpenAI-compatible chat completions.
894
+ * All three speak `POST {baseUrl}/chat/completions` in OpenAI's SSE shape —
895
+ * the router (a.k.a. tcloud) IS that endpoint, and cli-bridge fronts a
896
+ * harness CLI behind the same protocol at its own `/v1`. They differ only
897
+ * in `baseUrl` / `apiKey` and the `kind` label a product wants on its
898
+ * traces. cli-bridge REQUIRES `model` in the request body, so it MUST route
899
+ * through `createOpenAICompatibleBackend` (which sends it), never a
900
+ * transport that drops the field.
901
+ * - `sandbox` → the caller's own domain backend. The sandbox variant carries
902
+ * product specifics (system prompt, workspace id, in-box D1 executor) that
903
+ * do NOT belong in the substrate, so the product passes a `sandboxBackend()`
904
+ * seam that this resolver simply invokes.
905
+ *
906
+ * This resolver is PURE backend selection. Product concerns — credit hard-cuts,
907
+ * fetch-capture shims, D1 platform wiring — stay as product-side WRAPPERS
908
+ * around the returned backend. The OpenAI-compat passthrough fields (`tools`,
909
+ * `toolChoice`, `responseFormat`, `temperature`, `maxTokens`, `fetchImpl`,
910
+ * `retry`) are forwarded verbatim so a product can advertise its app tools,
911
+ * preserve generation settings, or install a capturing fetch without
912
+ * re-opening the branch this consolidation closes.
913
+ */
914
+ /**
915
+ * Resolve the `AgentExecutionBackend` for the chosen `kind`. Reuse this instead
916
+ * of hand-rolling the `createOpenAICompatibleBackend` branch in each product.
917
+ */
1043
918
  function resolveAgentBackend(opts) {
1044
- switch (opts.kind) {
1045
- case "router":
1046
- case "tcloud":
1047
- case "cli-bridge": {
1048
- const passthrough = {};
1049
- if (opts.tools !== void 0) passthrough.tools = opts.tools;
1050
- if (opts.toolChoice !== void 0) passthrough.toolChoice = opts.toolChoice;
1051
- if (opts.responseFormat !== void 0) passthrough.responseFormat = opts.responseFormat;
1052
- if (opts.temperature !== void 0) passthrough.temperature = opts.temperature;
1053
- if (opts.maxTokens !== void 0) passthrough.maxTokens = opts.maxTokens;
1054
- if (opts.fetchImpl !== void 0) passthrough.fetchImpl = opts.fetchImpl;
1055
- if (opts.retry !== void 0) passthrough.retry = opts.retry;
1056
- return createOpenAICompatibleBackend({
1057
- apiKey: opts.apiKey,
1058
- baseUrl: opts.baseUrl,
1059
- model: opts.model,
1060
- kind: opts.label ?? opts.kind,
1061
- ...passthrough
1062
- });
1063
- }
1064
- case "sandbox": {
1065
- if (!opts.sandboxBackend) {
1066
- throw new Error("resolveAgentBackend: kind 'sandbox' requires opts.sandboxBackend");
1067
- }
1068
- return opts.sandboxBackend();
1069
- }
1070
- }
1071
- }
1072
-
1073
- // src/run.ts
1074
- import {
1075
- acquisitionPlansForKnowledgeGaps,
1076
- blockingKnowledgeEval,
1077
- FAILURE_CLASSES,
1078
- runAgentControlLoop,
1079
- scoreKnowledgeReadiness,
1080
- userQuestionsForKnowledgeGaps
1081
- } from "@tangle-network/agent-eval";
1082
- var FAILURE_CLASS_SET = new Set(FAILURE_CLASSES);
919
+ switch (opts.kind) {
920
+ case "router":
921
+ case "tcloud":
922
+ case "cli-bridge": {
923
+ const passthrough = {};
924
+ if (opts.tools !== void 0) passthrough.tools = opts.tools;
925
+ if (opts.toolChoice !== void 0) passthrough.toolChoice = opts.toolChoice;
926
+ if (opts.responseFormat !== void 0) passthrough.responseFormat = opts.responseFormat;
927
+ if (opts.temperature !== void 0) passthrough.temperature = opts.temperature;
928
+ if (opts.maxTokens !== void 0) passthrough.maxTokens = opts.maxTokens;
929
+ if (opts.fetchImpl !== void 0) passthrough.fetchImpl = opts.fetchImpl;
930
+ if (opts.retry !== void 0) passthrough.retry = opts.retry;
931
+ return createOpenAICompatibleBackend({
932
+ apiKey: opts.apiKey,
933
+ baseUrl: opts.baseUrl,
934
+ model: opts.model,
935
+ kind: opts.label ?? opts.kind,
936
+ ...passthrough
937
+ });
938
+ }
939
+ case "sandbox":
940
+ if (!opts.sandboxBackend) throw new Error("resolveAgentBackend: kind 'sandbox' requires opts.sandboxBackend");
941
+ return opts.sandboxBackend();
942
+ }
943
+ }
944
+ //#endregion
945
+ //#region src/run.ts
946
+ /**
947
+ *
948
+ * The two top-level entry points:
949
+ *
950
+ * - `runAgentTask` — single-shot lifecycle for adapter-driven tasks.
951
+ * - `runAgentTaskStream` — streaming lifecycle that delegates execution to an
952
+ * `AgentExecutionBackend` (model API, sandbox, or custom iterable).
953
+ *
954
+ * Both gate the run on `KnowledgeReadinessReport` from `agent-eval`, emit the
955
+ * same lifecycle event vocabulary (under different shapes — see `types.ts`),
956
+ * and route session lifecycle through a pluggable `RuntimeSessionStore`.
957
+ *
958
+ * @stable
959
+ */
960
+ const FAILURE_CLASS_SET = new Set(FAILURE_CLASSES);
961
+ /** True when a free-form control failure string is a canonical taxonomy
962
+ * class — so only real taxonomy tags are promoted to the cross-agent
963
+ * `RunRecord.failureClass` key; novel strings stay as `failureMode` detail. */
1083
964
  function asFailureClass(value) {
1084
- return value && FAILURE_CLASS_SET.has(value) ? value : void 0;
1085
- }
965
+ return value && FAILURE_CLASS_SET.has(value) ? value : void 0;
966
+ }
967
+ /** Stamp cross-cutting defaults onto adapter-projected RunRecords without
968
+ * overriding anything the adapter set explicitly:
969
+ * - `scenarioId` — the run's scenario, when the record omits one.
970
+ * - `failureClass` — the control layer's failure classification promoted
971
+ * onto the canonical cross-agent key, but ONLY when it's a real taxonomy
972
+ * class. This is what lets the substrate aggregate failures across every
973
+ * agent in one vocabulary instead of per-agent ad-hoc strings. */
1086
974
  function applyRunRecordDefaults(records, scenarioId, controlFailureClass) {
1087
- const fc = asFailureClass(controlFailureClass);
1088
- return records.map((record) => {
1089
- let r = record;
1090
- if (r.scenarioId === void 0) r = { ...r, scenarioId };
1091
- if (r.failureClass === void 0 && fc) r = { ...r, failureClass: fc };
1092
- return r;
1093
- });
1094
- }
975
+ const fc = asFailureClass(controlFailureClass);
976
+ return records.map((record) => {
977
+ let r = record;
978
+ if (r.scenarioId === void 0) r = {
979
+ ...r,
980
+ scenarioId
981
+ };
982
+ if (r.failureClass === void 0 && fc) r = {
983
+ ...r,
984
+ failureClass: fc
985
+ };
986
+ return r;
987
+ });
988
+ }
989
+ /**
990
+ * Single-shot task lifecycle for adapter-driven tasks: readiness-gated, emits the runtime lifecycle event vocabulary, session-store pluggable.
991
+ *
992
+ * @stable
993
+ */
1095
994
  async function runAgentTask(options) {
1096
- const task = options.task;
1097
- await emit(options.onEvent, { type: "task_start", task });
1098
- await emit(options.onEvent, { type: "readiness_start", task });
1099
- let knowledge = await buildReadiness(task, options.knowledge);
1100
- await emit(options.onEvent, { type: "readiness_end", task, knowledge });
1101
- const questions = userQuestionsForKnowledgeGaps(knowledge.blockingMissingRequirements);
1102
- const acquisitionPlans = acquisitionPlansForKnowledgeGaps([
1103
- ...knowledge.blockingMissingRequirements,
1104
- ...knowledge.nonBlockingGaps
1105
- ]);
1106
- const preflight = await runKnowledgePreflight(
1107
- task,
1108
- questions,
1109
- acquisitionPlans,
1110
- options.knowledge,
1111
- options.onEvent
1112
- );
1113
- if (options.knowledge?.refreshReadiness && (Object.keys(preflight.userAnswers).length > 0 || preflight.acquiredEvidenceIds.length > 0)) {
1114
- await emit(options.onEvent, { type: "readiness_start", task });
1115
- knowledge = await options.knowledge.refreshReadiness({
1116
- task,
1117
- previous: knowledge,
1118
- userAnswers: preflight.userAnswers,
1119
- acquiredEvidenceIds: preflight.acquiredEvidenceIds
1120
- });
1121
- await emit(options.onEvent, { type: "readiness_end", task, knowledge });
1122
- }
1123
- await emit(options.onEvent, { type: "control_start", task, knowledge });
1124
- const scenarioId = options.scenarioId ?? task.id;
1125
- const control = await runAgentControlLoop({
1126
- intent: task.intent,
1127
- budget: task.budget,
1128
- signal: options.signal,
1129
- store: options.store,
1130
- scenarioId,
1131
- projectId: options.projectId,
1132
- variantId: options.variantId,
1133
- observe: ({ history, abortSignal }) => options.adapter.observe({ task, knowledge, history, abortSignal }),
1134
- validate: async ({ state, history, abortSignal }) => {
1135
- const readinessEval = blockingKnowledgeEval(knowledge, {
1136
- minimumScore: options.minimumReadinessScore
1137
- });
1138
- const evals = await options.adapter.validate({
1139
- task,
1140
- knowledge,
1141
- state,
1142
- history,
1143
- abortSignal
1144
- });
1145
- return [readinessEval, ...evals];
1146
- },
1147
- decide: (ctx) => {
1148
- if (isKnowledgeBlocked(ctx.evals)) {
1149
- return options.adapter.onKnowledgeBlocked?.({
1150
- task,
1151
- knowledge,
1152
- questions,
1153
- acquisitionPlans
1154
- }) ?? {
1155
- type: "stop",
1156
- pass: false,
1157
- score: knowledge.readinessScore,
1158
- reason: `knowledge readiness blocked: ${knowledge.reason}`
1159
- };
1160
- }
1161
- return options.adapter.decide(toAgentContext(task, knowledge, ctx));
1162
- },
1163
- act: (action, ctx) => options.adapter.act(action, toAgentContext(task, knowledge, ctx)),
1164
- shouldStop: options.adapter.shouldStop ? (ctx) => options.adapter.shouldStop(toAgentContext(task, knowledge, ctx)) : void 0,
1165
- getActionCostUsd: options.adapter.getActionCostUsd ? ({ action, result, state, evals, history }) => options.adapter.getActionCostUsd({ action, result, task, state, evals, history }) : void 0,
1166
- onStep: (step) => emit(options.onEvent, { type: "control_step", task, step })
1167
- });
1168
- await emit(options.onEvent, { type: "control_end", task, control });
1169
- const status = statusFromControl(control);
1170
- await emit(options.onEvent, { type: "task_end", task, status, reason: control.reason });
1171
- return {
1172
- task,
1173
- status,
1174
- knowledge,
1175
- questions,
1176
- acquisitionPlans,
1177
- userAnswers: preflight.userAnswers,
1178
- acquiredEvidenceIds: preflight.acquiredEvidenceIds,
1179
- control,
1180
- runRecords: applyRunRecordDefaults(
1181
- options.adapter.projectRunRecords?.(control, task) ?? [],
1182
- scenarioId,
1183
- control.failureClass
1184
- )
1185
- };
1186
- }
995
+ const task = options.task;
996
+ await emit(options.onEvent, {
997
+ type: "task_start",
998
+ task
999
+ });
1000
+ await emit(options.onEvent, {
1001
+ type: "readiness_start",
1002
+ task
1003
+ });
1004
+ let knowledge = await buildReadiness(task, options.knowledge);
1005
+ await emit(options.onEvent, {
1006
+ type: "readiness_end",
1007
+ task,
1008
+ knowledge
1009
+ });
1010
+ const questions = userQuestionsForKnowledgeGaps(knowledge.blockingMissingRequirements);
1011
+ const acquisitionPlans = acquisitionPlansForKnowledgeGaps([...knowledge.blockingMissingRequirements, ...knowledge.nonBlockingGaps]);
1012
+ const preflight = await runKnowledgePreflight(task, questions, acquisitionPlans, options.knowledge, options.onEvent);
1013
+ if (options.knowledge?.refreshReadiness && (Object.keys(preflight.userAnswers).length > 0 || preflight.acquiredEvidenceIds.length > 0)) {
1014
+ await emit(options.onEvent, {
1015
+ type: "readiness_start",
1016
+ task
1017
+ });
1018
+ knowledge = await options.knowledge.refreshReadiness({
1019
+ task,
1020
+ previous: knowledge,
1021
+ userAnswers: preflight.userAnswers,
1022
+ acquiredEvidenceIds: preflight.acquiredEvidenceIds
1023
+ });
1024
+ await emit(options.onEvent, {
1025
+ type: "readiness_end",
1026
+ task,
1027
+ knowledge
1028
+ });
1029
+ }
1030
+ await emit(options.onEvent, {
1031
+ type: "control_start",
1032
+ task,
1033
+ knowledge
1034
+ });
1035
+ const scenarioId = options.scenarioId ?? task.id;
1036
+ const control = await runAgentControlLoop({
1037
+ intent: task.intent,
1038
+ budget: task.budget,
1039
+ signal: options.signal,
1040
+ store: options.store,
1041
+ scenarioId,
1042
+ projectId: options.projectId,
1043
+ variantId: options.variantId,
1044
+ observe: ({ history, abortSignal }) => options.adapter.observe({
1045
+ task,
1046
+ knowledge,
1047
+ history,
1048
+ abortSignal
1049
+ }),
1050
+ validate: async ({ state, history, abortSignal }) => {
1051
+ return [blockingKnowledgeEval(knowledge, { minimumScore: options.minimumReadinessScore }), ...await options.adapter.validate({
1052
+ task,
1053
+ knowledge,
1054
+ state,
1055
+ history,
1056
+ abortSignal
1057
+ })];
1058
+ },
1059
+ decide: (ctx) => {
1060
+ if (isKnowledgeBlocked(ctx.evals)) return options.adapter.onKnowledgeBlocked?.({
1061
+ task,
1062
+ knowledge,
1063
+ questions,
1064
+ acquisitionPlans
1065
+ }) ?? {
1066
+ type: "stop",
1067
+ pass: false,
1068
+ score: knowledge.readinessScore,
1069
+ reason: `knowledge readiness blocked: ${knowledge.reason}`
1070
+ };
1071
+ return options.adapter.decide(toAgentContext(task, knowledge, ctx));
1072
+ },
1073
+ act: (action, ctx) => options.adapter.act(action, toAgentContext(task, knowledge, ctx)),
1074
+ shouldStop: options.adapter.shouldStop ? (ctx) => options.adapter.shouldStop(toAgentContext(task, knowledge, ctx)) : void 0,
1075
+ getActionCostUsd: options.adapter.getActionCostUsd ? ({ action, result, state, evals, history }) => options.adapter.getActionCostUsd({
1076
+ action,
1077
+ result,
1078
+ task,
1079
+ state,
1080
+ evals,
1081
+ history
1082
+ }) : void 0,
1083
+ onStep: (step) => emit(options.onEvent, {
1084
+ type: "control_step",
1085
+ task,
1086
+ step
1087
+ })
1088
+ });
1089
+ await emit(options.onEvent, {
1090
+ type: "control_end",
1091
+ task,
1092
+ control
1093
+ });
1094
+ const status = statusFromControl(control);
1095
+ await emit(options.onEvent, {
1096
+ type: "task_end",
1097
+ task,
1098
+ status,
1099
+ reason: control.reason
1100
+ });
1101
+ return {
1102
+ task,
1103
+ status,
1104
+ knowledge,
1105
+ questions,
1106
+ acquisitionPlans,
1107
+ userAnswers: preflight.userAnswers,
1108
+ acquiredEvidenceIds: preflight.acquiredEvidenceIds,
1109
+ control,
1110
+ runRecords: applyRunRecordDefaults(options.adapter.projectRunRecords?.(control, task) ?? [], scenarioId, control.failureClass)
1111
+ };
1112
+ }
1113
+ /**
1114
+ * Streaming task lifecycle: delegates execution to an `AgentExecutionBackend` (model API, sandbox, or custom iterable) and yields lifecycle events as they happen.
1115
+ *
1116
+ * @stable
1117
+ */
1187
1118
  async function* runAgentTaskStream(options) {
1188
- const task = options.task;
1189
- const input = { task, ...options.input ?? {} };
1190
- yield streamEvent({ type: "task_start", task });
1191
- yield streamEvent({ type: "readiness_start", task });
1192
- let knowledge = await buildReadiness(task, options.knowledge);
1193
- const questions = userQuestionsForKnowledgeGaps(knowledge.blockingMissingRequirements);
1194
- const acquisitionPlans = acquisitionPlansForKnowledgeGaps([
1195
- ...knowledge.blockingMissingRequirements,
1196
- ...knowledge.nonBlockingGaps
1197
- ]);
1198
- const preflight = await runKnowledgePreflightStream(
1199
- task,
1200
- questions,
1201
- acquisitionPlans,
1202
- options.knowledge
1203
- );
1204
- for (const event of preflight.events) yield event;
1205
- if (options.knowledge?.refreshReadiness && (Object.keys(preflight.userAnswers).length > 0 || preflight.acquiredEvidenceIds.length > 0)) {
1206
- yield streamEvent({ type: "readiness_start", task });
1207
- knowledge = await options.knowledge.refreshReadiness({
1208
- task,
1209
- previous: knowledge,
1210
- userAnswers: preflight.userAnswers,
1211
- acquiredEvidenceIds: preflight.acquiredEvidenceIds
1212
- });
1213
- }
1214
- const decision = decideKnowledgeReadiness(knowledge, {
1215
- minimumScore: options.minimumReadinessScore
1216
- });
1217
- yield streamEvent({ type: "readiness_end", task, knowledge, decision });
1218
- if (!decision.passed && decision.status === "blocked") {
1219
- const reason = `knowledge readiness blocked: ${decision.reason}`;
1220
- yield streamEvent({ type: "task_end", task, status: "blocked", reason });
1221
- yield streamEvent({ type: "final", task, status: "blocked", reason });
1222
- return;
1223
- }
1224
- const store = options.sessionStore;
1225
- const opened = await startOrResumeRuntimeSession({
1226
- backend: options.backend,
1227
- input,
1228
- context: { task, knowledge, signal: options.signal },
1229
- store,
1230
- sessionId: options.sessionId,
1231
- resume: options.resume
1232
- });
1233
- let session = opened.session;
1234
- const shouldResume = opened.resumed;
1235
- const sessionEvent = streamEvent({
1236
- type: shouldResume ? "session_resumed" : "session_created",
1237
- task,
1238
- session
1239
- });
1240
- await store?.appendEvent?.(session.id, sessionEvent);
1241
- yield sessionEvent;
1242
- const backendStart = streamEvent({
1243
- type: "backend_start",
1244
- task,
1245
- session,
1246
- backend: options.backend.kind
1247
- });
1248
- await store?.appendEvent?.(session.id, backendStart);
1249
- yield backendStart;
1250
- let finalText = "";
1251
- try {
1252
- for await (const rawEvent of options.backend.stream(opened.input, {
1253
- task,
1254
- knowledge,
1255
- session,
1256
- signal: options.signal
1257
- })) {
1258
- const event = normalizeBackendStreamEvent(rawEvent, task, session);
1259
- if (event.type === "text_delta") finalText += event.text;
1260
- await store?.appendEvent?.(session.id, event);
1261
- yield event;
1262
- }
1263
- const completedStatus = "completed";
1264
- session = touchSession({ ...session, status: completedStatus });
1265
- await store?.put(session);
1266
- const backendEnd = streamEvent({
1267
- type: "backend_end",
1268
- task,
1269
- session,
1270
- backend: options.backend.kind
1271
- });
1272
- await store?.appendEvent?.(session.id, backendEnd);
1273
- yield backendEnd;
1274
- const reason = "backend completed";
1275
- const taskEnd = streamEvent({ type: "task_end", task, status: completedStatus, reason });
1276
- await store?.appendEvent?.(session.id, taskEnd);
1277
- yield taskEnd;
1278
- const final = streamEvent({
1279
- type: "final",
1280
- task,
1281
- session,
1282
- status: completedStatus,
1283
- reason,
1284
- text: finalText || void 0
1285
- });
1286
- await store?.appendEvent?.(session.id, final);
1287
- yield final;
1288
- } catch (err) {
1289
- const message = err instanceof Error ? err.message : String(err);
1290
- session = touchSession({ ...session, status: options.signal?.aborted ? "aborted" : "failed" });
1291
- await store?.put(session);
1292
- let stopErrorMessage;
1293
- try {
1294
- await options.backend.stop?.(session, message);
1295
- } catch (stopErr) {
1296
- stopErrorMessage = stopErr instanceof Error ? stopErr.message : String(stopErr);
1297
- }
1298
- const combinedMessage = stopErrorMessage ? `${message}; backend stop failed: ${stopErrorMessage}` : message;
1299
- const errorDetail = err instanceof BackendTransportError ? {
1300
- kind: "transport",
1301
- message: combinedMessage,
1302
- status: err.status,
1303
- body: err.body
1304
- } : { kind: "backend", message: combinedMessage };
1305
- const backendError = streamEvent({
1306
- type: "backend_error",
1307
- task,
1308
- session,
1309
- backend: options.backend.kind,
1310
- message: combinedMessage,
1311
- recoverable: !options.signal?.aborted,
1312
- error: errorDetail
1313
- });
1314
- await store?.appendEvent?.(session.id, backendError);
1315
- yield backendError;
1316
- const status = options.signal?.aborted ? "aborted" : "failed";
1317
- const taskEnd = streamEvent({ type: "task_end", task, status, reason: message });
1318
- await store?.appendEvent?.(session.id, taskEnd);
1319
- yield taskEnd;
1320
- const final = streamEvent({
1321
- type: "final",
1322
- task,
1323
- session,
1324
- status,
1325
- reason: message,
1326
- text: finalText || void 0,
1327
- error: errorDetail
1328
- });
1329
- await store?.appendEvent?.(session.id, final);
1330
- yield final;
1331
- }
1119
+ const task = options.task;
1120
+ const input = {
1121
+ task,
1122
+ ...options.input ?? {}
1123
+ };
1124
+ yield streamEvent({
1125
+ type: "task_start",
1126
+ task
1127
+ });
1128
+ yield streamEvent({
1129
+ type: "readiness_start",
1130
+ task
1131
+ });
1132
+ let knowledge = await buildReadiness(task, options.knowledge);
1133
+ const preflight = await runKnowledgePreflightStream(task, userQuestionsForKnowledgeGaps(knowledge.blockingMissingRequirements), acquisitionPlansForKnowledgeGaps([...knowledge.blockingMissingRequirements, ...knowledge.nonBlockingGaps]), options.knowledge);
1134
+ for (const event of preflight.events) yield event;
1135
+ if (options.knowledge?.refreshReadiness && (Object.keys(preflight.userAnswers).length > 0 || preflight.acquiredEvidenceIds.length > 0)) {
1136
+ yield streamEvent({
1137
+ type: "readiness_start",
1138
+ task
1139
+ });
1140
+ knowledge = await options.knowledge.refreshReadiness({
1141
+ task,
1142
+ previous: knowledge,
1143
+ userAnswers: preflight.userAnswers,
1144
+ acquiredEvidenceIds: preflight.acquiredEvidenceIds
1145
+ });
1146
+ }
1147
+ const decision = decideKnowledgeReadiness(knowledge, { minimumScore: options.minimumReadinessScore });
1148
+ yield streamEvent({
1149
+ type: "readiness_end",
1150
+ task,
1151
+ knowledge,
1152
+ decision
1153
+ });
1154
+ if (!decision.passed && decision.status === "blocked") {
1155
+ const reason = `knowledge readiness blocked: ${decision.reason}`;
1156
+ yield streamEvent({
1157
+ type: "task_end",
1158
+ task,
1159
+ status: "blocked",
1160
+ reason
1161
+ });
1162
+ yield streamEvent({
1163
+ type: "final",
1164
+ task,
1165
+ status: "blocked",
1166
+ reason
1167
+ });
1168
+ return;
1169
+ }
1170
+ const store = options.sessionStore;
1171
+ const opened = await startOrResumeRuntimeSession({
1172
+ backend: options.backend,
1173
+ input,
1174
+ context: {
1175
+ task,
1176
+ knowledge,
1177
+ signal: options.signal
1178
+ },
1179
+ store,
1180
+ sessionId: options.sessionId,
1181
+ resume: options.resume
1182
+ });
1183
+ let session = opened.session;
1184
+ const shouldResume = opened.resumed;
1185
+ const sessionEvent = streamEvent({
1186
+ type: shouldResume ? "session_resumed" : "session_created",
1187
+ task,
1188
+ session
1189
+ });
1190
+ await store?.appendEvent?.(session.id, sessionEvent);
1191
+ yield sessionEvent;
1192
+ const backendStart = streamEvent({
1193
+ type: "backend_start",
1194
+ task,
1195
+ session,
1196
+ backend: options.backend.kind
1197
+ });
1198
+ await store?.appendEvent?.(session.id, backendStart);
1199
+ yield backendStart;
1200
+ let finalText = "";
1201
+ try {
1202
+ for await (const rawEvent of options.backend.stream(opened.input, {
1203
+ task,
1204
+ knowledge,
1205
+ session,
1206
+ signal: options.signal
1207
+ })) {
1208
+ const event = normalizeBackendStreamEvent(rawEvent, task, session);
1209
+ if (event.type === "text_delta") finalText += event.text;
1210
+ await store?.appendEvent?.(session.id, event);
1211
+ yield event;
1212
+ }
1213
+ const completedStatus = "completed";
1214
+ session = touchSession({
1215
+ ...session,
1216
+ status: completedStatus
1217
+ });
1218
+ await store?.put(session);
1219
+ const backendEnd = streamEvent({
1220
+ type: "backend_end",
1221
+ task,
1222
+ session,
1223
+ backend: options.backend.kind
1224
+ });
1225
+ await store?.appendEvent?.(session.id, backendEnd);
1226
+ yield backendEnd;
1227
+ const reason = "backend completed";
1228
+ const taskEnd = streamEvent({
1229
+ type: "task_end",
1230
+ task,
1231
+ status: completedStatus,
1232
+ reason
1233
+ });
1234
+ await store?.appendEvent?.(session.id, taskEnd);
1235
+ yield taskEnd;
1236
+ const final = streamEvent({
1237
+ type: "final",
1238
+ task,
1239
+ session,
1240
+ status: completedStatus,
1241
+ reason,
1242
+ text: finalText || void 0
1243
+ });
1244
+ await store?.appendEvent?.(session.id, final);
1245
+ yield final;
1246
+ } catch (err) {
1247
+ const message = err instanceof Error ? err.message : String(err);
1248
+ session = touchSession({
1249
+ ...session,
1250
+ status: options.signal?.aborted ? "aborted" : "failed"
1251
+ });
1252
+ await store?.put(session);
1253
+ let stopErrorMessage;
1254
+ try {
1255
+ await options.backend.stop?.(session, message);
1256
+ } catch (stopErr) {
1257
+ stopErrorMessage = stopErr instanceof Error ? stopErr.message : String(stopErr);
1258
+ }
1259
+ const combinedMessage = stopErrorMessage ? `${message}; backend stop failed: ${stopErrorMessage}` : message;
1260
+ const errorDetail = err instanceof BackendTransportError ? {
1261
+ kind: "transport",
1262
+ message: combinedMessage,
1263
+ status: err.status,
1264
+ body: err.body
1265
+ } : {
1266
+ kind: "backend",
1267
+ message: combinedMessage
1268
+ };
1269
+ const backendError = streamEvent({
1270
+ type: "backend_error",
1271
+ task,
1272
+ session,
1273
+ backend: options.backend.kind,
1274
+ message: combinedMessage,
1275
+ recoverable: !options.signal?.aborted,
1276
+ error: errorDetail
1277
+ });
1278
+ await store?.appendEvent?.(session.id, backendError);
1279
+ yield backendError;
1280
+ const status = options.signal?.aborted ? "aborted" : "failed";
1281
+ const taskEnd = streamEvent({
1282
+ type: "task_end",
1283
+ task,
1284
+ status,
1285
+ reason: message
1286
+ });
1287
+ await store?.appendEvent?.(session.id, taskEnd);
1288
+ yield taskEnd;
1289
+ const final = streamEvent({
1290
+ type: "final",
1291
+ task,
1292
+ session,
1293
+ status,
1294
+ reason: message,
1295
+ text: finalText || void 0,
1296
+ error: errorDetail
1297
+ });
1298
+ await store?.appendEvent?.(session.id, final);
1299
+ yield final;
1300
+ }
1332
1301
  }
1333
1302
  async function runKnowledgePreflight(task, questions, acquisitionPlans, provider, onEvent) {
1334
- let userAnswers = {};
1335
- let acquiredEvidenceIds = [];
1336
- if (questions.length > 0 && provider?.answerQuestions) {
1337
- await emit(onEvent, { type: "questions_start", task, questions });
1338
- userAnswers = await provider.answerQuestions(questions, task);
1339
- await emit(onEvent, { type: "questions_end", task, questions, userAnswers });
1340
- }
1341
- if (acquisitionPlans.length > 0 && provider?.executeAcquisitionPlans) {
1342
- await emit(onEvent, { type: "acquisition_start", task, acquisitionPlans });
1343
- acquiredEvidenceIds = await provider.executeAcquisitionPlans(acquisitionPlans, task);
1344
- await emit(onEvent, {
1345
- type: "acquisition_end",
1346
- task,
1347
- acquisitionPlans,
1348
- acquiredEvidenceIds
1349
- });
1350
- }
1351
- return { userAnswers, acquiredEvidenceIds };
1303
+ let userAnswers = {};
1304
+ let acquiredEvidenceIds = [];
1305
+ if (questions.length > 0 && provider?.answerQuestions) {
1306
+ await emit(onEvent, {
1307
+ type: "questions_start",
1308
+ task,
1309
+ questions
1310
+ });
1311
+ userAnswers = await provider.answerQuestions(questions, task);
1312
+ await emit(onEvent, {
1313
+ type: "questions_end",
1314
+ task,
1315
+ questions,
1316
+ userAnswers
1317
+ });
1318
+ }
1319
+ if (acquisitionPlans.length > 0 && provider?.executeAcquisitionPlans) {
1320
+ await emit(onEvent, {
1321
+ type: "acquisition_start",
1322
+ task,
1323
+ acquisitionPlans
1324
+ });
1325
+ acquiredEvidenceIds = await provider.executeAcquisitionPlans(acquisitionPlans, task);
1326
+ await emit(onEvent, {
1327
+ type: "acquisition_end",
1328
+ task,
1329
+ acquisitionPlans,
1330
+ acquiredEvidenceIds
1331
+ });
1332
+ }
1333
+ return {
1334
+ userAnswers,
1335
+ acquiredEvidenceIds
1336
+ };
1352
1337
  }
1353
1338
  async function runKnowledgePreflightStream(task, questions, acquisitionPlans, provider) {
1354
- const events = [];
1355
- let userAnswers = {};
1356
- let acquiredEvidenceIds = [];
1357
- if (questions.length > 0 && provider?.answerQuestions) {
1358
- events.push(streamEvent({ type: "questions_start", task, questions }));
1359
- userAnswers = await provider.answerQuestions(questions, task);
1360
- events.push(streamEvent({ type: "questions_end", task, questions, userAnswers }));
1361
- }
1362
- if (acquisitionPlans.length > 0 && provider?.executeAcquisitionPlans) {
1363
- events.push(streamEvent({ type: "acquisition_start", task, acquisitionPlans }));
1364
- acquiredEvidenceIds = await provider.executeAcquisitionPlans(acquisitionPlans, task);
1365
- events.push(
1366
- streamEvent({ type: "acquisition_end", task, acquisitionPlans, acquiredEvidenceIds })
1367
- );
1368
- }
1369
- return { userAnswers, acquiredEvidenceIds, events };
1339
+ const events = [];
1340
+ let userAnswers = {};
1341
+ let acquiredEvidenceIds = [];
1342
+ if (questions.length > 0 && provider?.answerQuestions) {
1343
+ events.push(streamEvent({
1344
+ type: "questions_start",
1345
+ task,
1346
+ questions
1347
+ }));
1348
+ userAnswers = await provider.answerQuestions(questions, task);
1349
+ events.push(streamEvent({
1350
+ type: "questions_end",
1351
+ task,
1352
+ questions,
1353
+ userAnswers
1354
+ }));
1355
+ }
1356
+ if (acquisitionPlans.length > 0 && provider?.executeAcquisitionPlans) {
1357
+ events.push(streamEvent({
1358
+ type: "acquisition_start",
1359
+ task,
1360
+ acquisitionPlans
1361
+ }));
1362
+ acquiredEvidenceIds = await provider.executeAcquisitionPlans(acquisitionPlans, task);
1363
+ events.push(streamEvent({
1364
+ type: "acquisition_end",
1365
+ task,
1366
+ acquisitionPlans,
1367
+ acquiredEvidenceIds
1368
+ }));
1369
+ }
1370
+ return {
1371
+ userAnswers,
1372
+ acquiredEvidenceIds,
1373
+ events
1374
+ };
1370
1375
  }
1371
1376
  function streamEvent(event) {
1372
- return { ...event, timestamp: nowIso() };
1377
+ return {
1378
+ ...event,
1379
+ timestamp: nowIso()
1380
+ };
1373
1381
  }
1374
1382
  function buildReadiness(task, provider) {
1375
- if (provider?.buildReadiness) return provider.buildReadiness(task);
1376
- return scoreKnowledgeReadiness({
1377
- taskId: task.id,
1378
- requirements: task.requiredKnowledge ?? [],
1379
- metadata: { domain: task.domain, ...task.metadata }
1380
- });
1383
+ if (provider?.buildReadiness) return provider.buildReadiness(task);
1384
+ return scoreKnowledgeReadiness({
1385
+ taskId: task.id,
1386
+ requirements: task.requiredKnowledge ?? [],
1387
+ metadata: {
1388
+ domain: task.domain,
1389
+ ...task.metadata
1390
+ }
1391
+ });
1381
1392
  }
1382
1393
  function isKnowledgeBlocked(evals) {
1383
- return evals.some((evalResult) => evalResult.id === "knowledge-ready" && !evalResult.passed);
1394
+ return evals.some((evalResult) => evalResult.id === "knowledge-ready" && !evalResult.passed);
1384
1395
  }
1385
1396
  function statusFromControl(control) {
1386
- if (control.stoppedBy === "abort") return "aborted";
1387
- if (control.reason.includes("knowledge readiness blocked")) return "blocked";
1388
- if (control.pass) return "completed";
1389
- return "failed";
1397
+ if (control.stoppedBy === "abort") return "aborted";
1398
+ if (control.reason.includes("knowledge readiness blocked")) return "blocked";
1399
+ if (control.pass) return "completed";
1400
+ return "failed";
1390
1401
  }
1391
1402
  async function emit(sink, event) {
1392
- await sink?.(event);
1403
+ await sink?.(event);
1393
1404
  }
1394
1405
  function toAgentContext(task, knowledge, ctx) {
1395
- return {
1396
- task,
1397
- knowledge,
1398
- state: ctx.state,
1399
- evals: ctx.evals,
1400
- history: ctx.history,
1401
- budget: ctx.budget,
1402
- stepIndex: ctx.stepIndex,
1403
- wallMs: ctx.wallMs,
1404
- spentCostUsd: ctx.spentCostUsd,
1405
- remainingCostUsd: ctx.remainingCostUsd,
1406
- abortSignal: ctx.abortSignal
1407
- };
1408
- }
1409
-
1410
- // src/runtime-run.ts
1406
+ return {
1407
+ task,
1408
+ knowledge,
1409
+ state: ctx.state,
1410
+ evals: ctx.evals,
1411
+ history: ctx.history,
1412
+ budget: ctx.budget,
1413
+ stepIndex: ctx.stepIndex,
1414
+ wallMs: ctx.wallMs,
1415
+ spentCostUsd: ctx.spentCostUsd,
1416
+ remainingCostUsd: ctx.remainingCostUsd,
1417
+ abortSignal: ctx.abortSignal
1418
+ };
1419
+ }
1420
+ //#endregion
1421
+ //#region src/runtime-run.ts
1422
+ /**
1423
+ *
1424
+ * Production-run lifecycle: record what the agent did on behalf of a customer,
1425
+ * what it cost, and how it ended.
1426
+ *
1427
+ * Three concerns live in this module:
1428
+ *
1429
+ * 1. **Lifecycle state machine** — `running` -> `completed | failed | cancelled`,
1430
+ * enforced by `RuntimeRunStateError`. Completion is idempotent for the same
1431
+ * status (a second `complete()` call is a no-op so retries / cleanup paths
1432
+ * don't double-fire side effects). A different terminal status is a state
1433
+ * error.
1434
+ *
1435
+ * 2. **Cost ledger** — every `llm_call` event the handle observes contributes
1436
+ * `tokensIn`, `tokensOut`, `costUsd`, and bumps `llmCalls`. Wall time is
1437
+ * measured from `startRuntimeRun()` to `complete()`. Surface via
1438
+ * `handle.cost()` for cost-per-task dashboards.
1439
+ *
1440
+ * 3. **Persistence adapter** — `RuntimeRunPersistenceAdapter` is the seam
1441
+ * consumers plug in to write a `RuntimeRunRow` to their D1 / postgres /
1442
+ * KV store. The adapter receives a sanitized row shape; no telemetry
1443
+ * payload bytes flow through it unless the consumer opts in via
1444
+ * `RuntimeRunOptions.telemetryEvents`.
1445
+ *
1446
+ * @stable
1447
+ */
1448
+ /**
1449
+ *
1450
+ * Construct a runtime-run handle. The returned handle is mutable across its
1451
+ * lifetime; consumers should not share it across requests.
1452
+ *
1453
+ * @stable
1454
+ */
1411
1455
  function startRuntimeRun(options) {
1412
- if (!options.workspaceId) {
1413
- throw new ValidationError("startRuntimeRun: workspaceId is required");
1414
- }
1415
- if (!options.taskSpec?.id) {
1416
- throw new ValidationError("startRuntimeRun: taskSpec.id is required");
1417
- }
1418
- const now = options.now ?? Date.now;
1419
- const startedAtMs = now();
1420
- const startedAt = new Date(startedAtMs).toISOString();
1421
- const id = options.id ?? `${options.taskSpec.id}:${randomSuffix()}`;
1422
- let status = "running";
1423
- let completedAtMs;
1424
- let resultSummary;
1425
- let error;
1426
- let completionMetadata;
1427
- const ledger = {
1428
- tokensIn: 0,
1429
- tokensOut: 0,
1430
- costUsd: 0,
1431
- wallMs: 0,
1432
- llmCalls: 0
1433
- };
1434
- const snapshotCost = () => ({
1435
- tokensIn: ledger.tokensIn,
1436
- tokensOut: ledger.tokensOut,
1437
- costUsd: ledger.costUsd,
1438
- wallMs: (completedAtMs ?? now()) - startedAtMs,
1439
- llmCalls: ledger.llmCalls
1440
- });
1441
- const buildRow = (extraMetadata) => ({
1442
- id,
1443
- workspaceId: options.workspaceId,
1444
- sessionId: options.sessionId,
1445
- agentId: options.agentId,
1446
- domain: options.taskSpec.domain,
1447
- taskId: options.taskSpec.id,
1448
- scenarioId: options.scenarioId,
1449
- status,
1450
- resultSummary,
1451
- error,
1452
- cost: snapshotCost(),
1453
- startedAt,
1454
- completedAt: completedAtMs !== void 0 ? new Date(completedAtMs).toISOString() : void 0,
1455
- metadata: mergeMetadata(completionMetadata, extraMetadata)
1456
- });
1457
- return {
1458
- id,
1459
- workspaceId: options.workspaceId,
1460
- sessionId: options.sessionId,
1461
- taskSpec: options.taskSpec,
1462
- get status() {
1463
- return status;
1464
- },
1465
- observe(event) {
1466
- if (event.type !== "llm_call") return;
1467
- ledger.llmCalls += 1;
1468
- if (typeof event.tokensIn === "number" && Number.isFinite(event.tokensIn)) {
1469
- ledger.tokensIn += event.tokensIn;
1470
- }
1471
- if (typeof event.tokensOut === "number" && Number.isFinite(event.tokensOut)) {
1472
- ledger.tokensOut += event.tokensOut;
1473
- }
1474
- if (typeof event.costUsd === "number" && Number.isFinite(event.costUsd)) {
1475
- ledger.costUsd += event.costUsd;
1476
- }
1477
- },
1478
- cost: snapshotCost,
1479
- complete(input) {
1480
- if (input.status === "running") {
1481
- throw new ValidationError('complete() requires a terminal status, got "running"');
1482
- }
1483
- if (status !== "running") {
1484
- if (status === input.status) return;
1485
- throw new RuntimeRunStateError(
1486
- `Cannot transition runtime run from "${status}" to "${input.status}"`
1487
- );
1488
- }
1489
- status = input.status;
1490
- completedAtMs = now();
1491
- resultSummary = input.resultSummary;
1492
- error = input.error;
1493
- completionMetadata = input.metadata;
1494
- if (input.cost) {
1495
- if (typeof input.cost.tokensIn === "number" && Number.isFinite(input.cost.tokensIn)) {
1496
- ledger.tokensIn = input.cost.tokensIn;
1497
- }
1498
- if (typeof input.cost.tokensOut === "number" && Number.isFinite(input.cost.tokensOut)) {
1499
- ledger.tokensOut = input.cost.tokensOut;
1500
- }
1501
- if (typeof input.cost.costUsd === "number" && Number.isFinite(input.cost.costUsd)) {
1502
- ledger.costUsd = input.cost.costUsd;
1503
- }
1504
- if (typeof input.cost.llmCalls === "number" && Number.isFinite(input.cost.llmCalls)) {
1505
- ledger.llmCalls = input.cost.llmCalls;
1506
- }
1507
- }
1508
- },
1509
- toRow(metadata) {
1510
- return buildRow(metadata);
1511
- },
1512
- async persist(metadata) {
1513
- if (status === "running") {
1514
- throw new RuntimeRunStateError("Cannot persist a runtime run before complete() is called");
1515
- }
1516
- if (!options.adapter) return;
1517
- await options.adapter.upsert(buildRow(metadata));
1518
- }
1519
- };
1456
+ if (!options.workspaceId) throw new ValidationError("startRuntimeRun: workspaceId is required");
1457
+ if (!options.taskSpec?.id) throw new ValidationError("startRuntimeRun: taskSpec.id is required");
1458
+ const now = options.now ?? Date.now;
1459
+ const startedAtMs = now();
1460
+ const startedAt = new Date(startedAtMs).toISOString();
1461
+ const id = options.id ?? `${options.taskSpec.id}:${randomSuffix$1()}`;
1462
+ let status = "running";
1463
+ let completedAtMs;
1464
+ let resultSummary;
1465
+ let error;
1466
+ let completionMetadata;
1467
+ const ledger = {
1468
+ tokensIn: 0,
1469
+ tokensOut: 0,
1470
+ costUsd: 0,
1471
+ wallMs: 0,
1472
+ llmCalls: 0
1473
+ };
1474
+ const snapshotCost = () => ({
1475
+ tokensIn: ledger.tokensIn,
1476
+ tokensOut: ledger.tokensOut,
1477
+ costUsd: ledger.costUsd,
1478
+ wallMs: (completedAtMs ?? now()) - startedAtMs,
1479
+ llmCalls: ledger.llmCalls
1480
+ });
1481
+ const buildRow = (extraMetadata) => ({
1482
+ id,
1483
+ workspaceId: options.workspaceId,
1484
+ sessionId: options.sessionId,
1485
+ agentId: options.agentId,
1486
+ domain: options.taskSpec.domain,
1487
+ taskId: options.taskSpec.id,
1488
+ scenarioId: options.scenarioId,
1489
+ status,
1490
+ resultSummary,
1491
+ error,
1492
+ cost: snapshotCost(),
1493
+ startedAt,
1494
+ completedAt: completedAtMs !== void 0 ? new Date(completedAtMs).toISOString() : void 0,
1495
+ metadata: mergeMetadata(completionMetadata, extraMetadata)
1496
+ });
1497
+ return {
1498
+ id,
1499
+ workspaceId: options.workspaceId,
1500
+ sessionId: options.sessionId,
1501
+ taskSpec: options.taskSpec,
1502
+ get status() {
1503
+ return status;
1504
+ },
1505
+ observe(event) {
1506
+ if (event.type !== "llm_call") return;
1507
+ ledger.llmCalls += 1;
1508
+ if (typeof event.tokensIn === "number" && Number.isFinite(event.tokensIn)) ledger.tokensIn += event.tokensIn;
1509
+ if (typeof event.tokensOut === "number" && Number.isFinite(event.tokensOut)) ledger.tokensOut += event.tokensOut;
1510
+ if (typeof event.costUsd === "number" && Number.isFinite(event.costUsd)) ledger.costUsd += event.costUsd;
1511
+ },
1512
+ cost: snapshotCost,
1513
+ complete(input) {
1514
+ if (input.status === "running") throw new ValidationError("complete() requires a terminal status, got \"running\"");
1515
+ if (status !== "running") {
1516
+ if (status === input.status) return;
1517
+ throw new RuntimeRunStateError(`Cannot transition runtime run from "${status}" to "${input.status}"`);
1518
+ }
1519
+ status = input.status;
1520
+ completedAtMs = now();
1521
+ resultSummary = input.resultSummary;
1522
+ error = input.error;
1523
+ completionMetadata = input.metadata;
1524
+ if (input.cost) {
1525
+ if (typeof input.cost.tokensIn === "number" && Number.isFinite(input.cost.tokensIn)) ledger.tokensIn = input.cost.tokensIn;
1526
+ if (typeof input.cost.tokensOut === "number" && Number.isFinite(input.cost.tokensOut)) ledger.tokensOut = input.cost.tokensOut;
1527
+ if (typeof input.cost.costUsd === "number" && Number.isFinite(input.cost.costUsd)) ledger.costUsd = input.cost.costUsd;
1528
+ if (typeof input.cost.llmCalls === "number" && Number.isFinite(input.cost.llmCalls)) ledger.llmCalls = input.cost.llmCalls;
1529
+ }
1530
+ },
1531
+ toRow(metadata) {
1532
+ return buildRow(metadata);
1533
+ },
1534
+ async persist(metadata) {
1535
+ if (status === "running") throw new RuntimeRunStateError("Cannot persist a runtime run before complete() is called");
1536
+ if (!options.adapter) return;
1537
+ await options.adapter.upsert(buildRow(metadata));
1538
+ }
1539
+ };
1520
1540
  }
1521
1541
  function mergeMetadata(base, extra) {
1522
- if (!base && !extra) return void 0;
1523
- return { ...base ?? {}, ...extra ?? {} };
1524
- }
1525
- function randomSuffix() {
1526
- return Math.random().toString(36).slice(2, 10);
1527
- }
1528
-
1529
- // src/sse.ts
1542
+ if (!base && !extra) return void 0;
1543
+ return {
1544
+ ...base ?? {},
1545
+ ...extra ?? {}
1546
+ };
1547
+ }
1548
+ function randomSuffix$1() {
1549
+ return Math.random().toString(36).slice(2, 10);
1550
+ }
1551
+ //#endregion
1552
+ //#region src/sse.ts
1553
+ /** @stable */
1530
1554
  function encodeServerSentEvent(data, options = {}) {
1531
- const lines = [];
1532
- if (options.id) lines.push(`id: ${stripNewlines(options.id)}`);
1533
- if (options.event) lines.push(`event: ${stripNewlines(options.event)}`);
1534
- if (typeof options.retry === "number" && Number.isFinite(options.retry) && options.retry >= 0) {
1535
- lines.push(`retry: ${Math.floor(options.retry)}`);
1536
- }
1537
- const payload = typeof data === "string" ? data : JSON.stringify(data);
1538
- for (const line of payload.split(/\r?\n/)) {
1539
- lines.push(`data: ${line}`);
1540
- }
1541
- return `${lines.join("\n")}
1542
-
1543
- `;
1544
- }
1555
+ const lines = [];
1556
+ if (options.id) lines.push(`id: ${stripNewlines(options.id)}`);
1557
+ if (options.event) lines.push(`event: ${stripNewlines(options.event)}`);
1558
+ if (typeof options.retry === "number" && Number.isFinite(options.retry) && options.retry >= 0) lines.push(`retry: ${Math.floor(options.retry)}`);
1559
+ const payload = typeof data === "string" ? data : JSON.stringify(data);
1560
+ for (const line of payload.split(/\r?\n/)) lines.push(`data: ${line}`);
1561
+ return `${lines.join("\n")}\n\n`;
1562
+ }
1563
+ /** Serialize a `KnowledgeReadinessReport` as a Server-Sent Event string. @stable */
1545
1564
  function readinessServerSentEvent(report, options = {}) {
1546
- const { event, id, retry, ...telemetryOptions } = options;
1547
- return encodeServerSentEvent(
1548
- {
1549
- type: "readiness",
1550
- readiness: sanitizeKnowledgeReadinessReport(report, telemetryOptions)
1551
- },
1552
- { event, id, retry }
1553
- );
1554
- }
1565
+ const { event, id, retry, ...telemetryOptions } = options;
1566
+ return encodeServerSentEvent({
1567
+ type: "readiness",
1568
+ readiness: sanitizeKnowledgeReadinessReport(report, telemetryOptions)
1569
+ }, {
1570
+ event,
1571
+ id,
1572
+ retry
1573
+ });
1574
+ }
1575
+ /** Serialize a `RuntimeStreamEvent` as a Server-Sent Event string. @stable */
1555
1576
  function runtimeStreamServerSentEvent(event, options = {}) {
1556
- const { event: sseEvent, id, retry, ...telemetryOptions } = options;
1557
- return encodeServerSentEvent(sanitizeRuntimeStreamEvent(event, telemetryOptions), {
1558
- event: sseEvent,
1559
- id,
1560
- retry
1561
- });
1577
+ const { event: sseEvent, id, retry, ...telemetryOptions } = options;
1578
+ return encodeServerSentEvent(sanitizeRuntimeStreamEvent(event, telemetryOptions), {
1579
+ event: sseEvent,
1580
+ id,
1581
+ retry
1582
+ });
1562
1583
  }
1563
1584
  function stripNewlines(value) {
1564
- return value.replace(/[\r\n]/g, " ");
1565
- }
1566
-
1567
- // src/tool-loop.ts
1568
- var RUNAWAY_BACKSTOP_TURNS = 200;
1569
- var DEFAULT_DECISION_CONTEXT_CHARS = 12e3;
1570
- var FAILURE_RECOVERY_ACTIONS = ["retry", "verify", "continue", "stop"];
1571
- var STUCK_LOOP_THRESHOLD = 3;
1585
+ return value.replace(/[\r\n]/g, " ");
1586
+ }
1587
+ //#endregion
1588
+ //#region src/tool-loop.ts
1589
+ /** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set
1590
+ * far above any legitimate workflow — this is a watchdog, not a policy cap.
1591
+ * Legitimate per-call budgets come from `maxCostUsd` + `costOf`. */
1592
+ const RUNAWAY_BACKSTOP_TURNS = 200;
1593
+ const DEFAULT_DECISION_CONTEXT_CHARS = 12e3;
1594
+ const FAILURE_RECOVERY_ACTIONS = [
1595
+ "retry",
1596
+ "verify",
1597
+ "continue",
1598
+ "stop"
1599
+ ];
1600
+ /** Consecutive identical calls (same tool + canonical-JSON args) that trigger
1601
+ * stuck-loop detection. The window resets on any different call. */
1602
+ const STUCK_LOOP_THRESHOLD = 3;
1603
+ /** A tool-call id is required to key a `role: 'tool'` result back to its call.
1604
+ * When the model omitted one, derive a stable id from the tool name so the
1605
+ * assistant `tool_calls` entry and its `tool` result still match. */
1572
1606
  function toolCallId(call) {
1573
- return call.toolCallId ?? `call_${call.toolName}`;
1607
+ return call.toolCallId ?? `call_${call.toolName}`;
1574
1608
  }
1609
+ /** The assistant turn that emitted `pending`, in OpenAI shape: text content
1610
+ * (null when the turn was tool-only) plus its `tool_calls` array. */
1575
1611
  function assistantToolCallMessage(turnText, pending) {
1576
- return {
1577
- role: "assistant",
1578
- content: turnText.trim() || null,
1579
- tool_calls: pending.map((call) => ({
1580
- id: toolCallId(call),
1581
- type: "function",
1582
- function: { name: call.toolName, arguments: JSON.stringify(call.args) }
1583
- }))
1584
- };
1585
- }
1612
+ return {
1613
+ role: "assistant",
1614
+ content: turnText.trim() || null,
1615
+ tool_calls: pending.map((call) => ({
1616
+ id: toolCallId(call),
1617
+ type: "function",
1618
+ function: {
1619
+ name: call.toolName,
1620
+ arguments: JSON.stringify(call.args)
1621
+ }
1622
+ }))
1623
+ };
1624
+ }
1625
+ /** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */
1586
1626
  function toolResultMessage(call, content) {
1587
- return { role: "tool", tool_call_id: toolCallId(call), content };
1627
+ return {
1628
+ role: "tool",
1629
+ tool_call_id: toolCallId(call),
1630
+ content
1631
+ };
1588
1632
  }
1589
1633
  function defaultRender(label, outcome) {
1590
- if (outcome.ok) return `- ${label} \u2192 ok: ${JSON.stringify(outcome.result)}`;
1591
- return `- ${label} \u2192 failed (${outcome.code}): ${outcome.message}`;
1634
+ if (outcome.ok) return `- ${label} ok: ${JSON.stringify(outcome.result)}`;
1635
+ return `- ${label} failed (${outcome.code}): ${outcome.message}`;
1592
1636
  }
1637
+ /** Run the bounded tool loop and return the final text + every executed tool
1638
+ * outcome. Awaitable — callers needing to stream events to a UI use
1639
+ * {@link streamToolLoop}. */
1593
1640
  async function runToolLoop(opts) {
1594
- const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
1595
- const render = opts.renderResult ?? defaultRender;
1596
- const labelFor = opts.labelFor ?? ((c) => c.toolName);
1597
- const runId = opts.runId ?? `agent-run-${randomSuffix2()}`;
1598
- const messages = [
1599
- { role: "system", content: opts.systemPrompt },
1600
- ...opts.priorMessages ?? [],
1601
- { role: "user", content: opts.userMessage }
1602
- ];
1603
- const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId);
1604
- const toolResults = [];
1605
- let finalText = "";
1606
- let turns = 0;
1607
- let accumulatedCostUsd = 0;
1608
- let lastCallHash = null;
1609
- let consecutiveCount = 0;
1610
- observer.loopBefore(backstop, messages.length);
1611
- for (let toolTurn = 0; ; toolTurn++) {
1612
- turns++;
1613
- if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
1614
- observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: "deadline" });
1615
- return { finalText, toolResults, turns, stopReason: "deadline", cappedOut: true };
1616
- }
1617
- let turnText = "";
1618
- const pending = [];
1619
- const turnEventId = observer.turnBefore(toolTurn, messages.length);
1620
- for await (const ev of opts.streamTurn([...messages])) {
1621
- if (ev.type === "text") {
1622
- turnText += ev.text;
1623
- finalText += ev.text;
1624
- } else if (ev.type === "tool_call" && opts.isExecutableTool(ev.call.toolName)) {
1625
- pending.push(ev.call);
1626
- }
1627
- }
1628
- if (pending.length === 0) {
1629
- observer.turnAfter(toolTurn, turnEventId, {
1630
- pendingToolCalls: 0,
1631
- finalTextChars: finalText.length
1632
- });
1633
- break;
1634
- }
1635
- if (toolTurn >= backstop) {
1636
- observer.turnAfter(toolTurn, turnEventId, {
1637
- pendingToolCalls: pending.length,
1638
- stopReason: "backstop"
1639
- });
1640
- observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: "backstop" });
1641
- return { finalText, toolResults, turns, stopReason: "backstop", cappedOut: true };
1642
- }
1643
- messages.push(assistantToolCallMessage(turnText, pending));
1644
- const outcomes = [];
1645
- for (const [callIndex, call] of pending.entries()) {
1646
- const callHash = canonicalCallHash(call);
1647
- if (callHash === lastCallHash) {
1648
- consecutiveCount++;
1649
- } else {
1650
- lastCallHash = callHash;
1651
- consecutiveCount = 1;
1652
- }
1653
- if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
1654
- observer.turnAfter(toolTurn, turnEventId, {
1655
- pendingToolCalls: pending.length,
1656
- stopReason: "stuck-loop"
1657
- });
1658
- observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: "stuck-loop" });
1659
- return { finalText, toolResults, turns, stopReason: "stuck-loop", cappedOut: true };
1660
- }
1661
- const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call);
1662
- let outcome;
1663
- try {
1664
- outcome = await opts.executeToolCall(call);
1665
- } catch (err) {
1666
- outcome = {
1667
- ok: false,
1668
- code: "executor_error",
1669
- message: err instanceof Error ? err.message : String(err)
1670
- };
1671
- }
1672
- if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
1673
- accumulatedCostUsd += opts.costOf(call, outcome);
1674
- if (accumulatedCostUsd >= opts.maxCostUsd) {
1675
- const label2 = labelFor(call);
1676
- toolResults.push({ call, label: label2, outcome });
1677
- messages.push(toolResultMessage(call, render(label2, outcome)));
1678
- observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1679
- observer.turnAfter(toolTurn, turnEventId, {
1680
- pendingToolCalls: pending.length,
1681
- stopReason: "budget"
1682
- });
1683
- observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: "budget" });
1684
- return { finalText, toolResults, turns, stopReason: "budget", cappedOut: true };
1685
- }
1686
- }
1687
- const label = labelFor(call);
1688
- const rendered = render(label, outcome);
1689
- toolResults.push({ call, label, outcome });
1690
- outcomes.push({ call, label, outcome, rendered });
1691
- messages.push(toolResultMessage(call, rendered));
1692
- observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1693
- }
1694
- observer.failureRecovery({
1695
- toolTurn,
1696
- messages,
1697
- turnText,
1698
- outcomes
1699
- });
1700
- observer.turnAfter(toolTurn, turnEventId, {
1701
- pendingToolCalls: pending.length,
1702
- toolResults: outcomes.map((item) => ({
1703
- toolName: item.call.toolName,
1704
- toolCallId: item.call.toolCallId,
1705
- ok: item.outcome.ok
1706
- })),
1707
- failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length
1708
- });
1709
- }
1710
- observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: "completed" });
1711
- return { finalText, toolResults, turns, stopReason: "completed", cappedOut: false };
1712
- }
1641
+ const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
1642
+ const render = opts.renderResult ?? defaultRender;
1643
+ const labelFor = opts.labelFor ?? ((c) => c.toolName);
1644
+ const runId = opts.runId ?? `agent-run-${randomSuffix()}`;
1645
+ const messages = [
1646
+ {
1647
+ role: "system",
1648
+ content: opts.systemPrompt
1649
+ },
1650
+ ...opts.priorMessages ?? [],
1651
+ {
1652
+ role: "user",
1653
+ content: opts.userMessage
1654
+ }
1655
+ ];
1656
+ const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId);
1657
+ const toolResults = [];
1658
+ let finalText = "";
1659
+ let turns = 0;
1660
+ let accumulatedCostUsd = 0;
1661
+ let lastCallHash = null;
1662
+ let consecutiveCount = 0;
1663
+ observer.loopBefore(backstop, messages.length);
1664
+ for (let toolTurn = 0;; toolTurn++) {
1665
+ turns++;
1666
+ if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
1667
+ observer.loopAfter({
1668
+ turns,
1669
+ toolResults: toolResults.length,
1670
+ stopReason: "deadline"
1671
+ });
1672
+ return {
1673
+ finalText,
1674
+ toolResults,
1675
+ turns,
1676
+ stopReason: "deadline",
1677
+ cappedOut: true
1678
+ };
1679
+ }
1680
+ let turnText = "";
1681
+ const pending = [];
1682
+ const turnEventId = observer.turnBefore(toolTurn, messages.length);
1683
+ for await (const ev of opts.streamTurn([...messages])) if (ev.type === "text") {
1684
+ turnText += ev.text;
1685
+ finalText += ev.text;
1686
+ } else if (ev.type === "tool_call" && opts.isExecutableTool(ev.call.toolName)) pending.push(ev.call);
1687
+ if (pending.length === 0) {
1688
+ observer.turnAfter(toolTurn, turnEventId, {
1689
+ pendingToolCalls: 0,
1690
+ finalTextChars: finalText.length
1691
+ });
1692
+ break;
1693
+ }
1694
+ if (toolTurn >= backstop) {
1695
+ observer.turnAfter(toolTurn, turnEventId, {
1696
+ pendingToolCalls: pending.length,
1697
+ stopReason: "backstop"
1698
+ });
1699
+ observer.loopAfter({
1700
+ turns,
1701
+ toolResults: toolResults.length,
1702
+ stopReason: "backstop"
1703
+ });
1704
+ return {
1705
+ finalText,
1706
+ toolResults,
1707
+ turns,
1708
+ stopReason: "backstop",
1709
+ cappedOut: true
1710
+ };
1711
+ }
1712
+ messages.push(assistantToolCallMessage(turnText, pending));
1713
+ const outcomes = [];
1714
+ for (const [callIndex, call] of pending.entries()) {
1715
+ const callHash = canonicalCallHash(call);
1716
+ if (callHash === lastCallHash) consecutiveCount++;
1717
+ else {
1718
+ lastCallHash = callHash;
1719
+ consecutiveCount = 1;
1720
+ }
1721
+ if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
1722
+ observer.turnAfter(toolTurn, turnEventId, {
1723
+ pendingToolCalls: pending.length,
1724
+ stopReason: "stuck-loop"
1725
+ });
1726
+ observer.loopAfter({
1727
+ turns,
1728
+ toolResults: toolResults.length,
1729
+ stopReason: "stuck-loop"
1730
+ });
1731
+ return {
1732
+ finalText,
1733
+ toolResults,
1734
+ turns,
1735
+ stopReason: "stuck-loop",
1736
+ cappedOut: true
1737
+ };
1738
+ }
1739
+ const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call);
1740
+ let outcome;
1741
+ try {
1742
+ outcome = await opts.executeToolCall(call);
1743
+ } catch (err) {
1744
+ outcome = {
1745
+ ok: false,
1746
+ code: "executor_error",
1747
+ message: err instanceof Error ? err.message : String(err)
1748
+ };
1749
+ }
1750
+ if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
1751
+ accumulatedCostUsd += opts.costOf(call, outcome);
1752
+ if (accumulatedCostUsd >= opts.maxCostUsd) {
1753
+ const label = labelFor(call);
1754
+ toolResults.push({
1755
+ call,
1756
+ label,
1757
+ outcome
1758
+ });
1759
+ messages.push(toolResultMessage(call, render(label, outcome)));
1760
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1761
+ observer.turnAfter(toolTurn, turnEventId, {
1762
+ pendingToolCalls: pending.length,
1763
+ stopReason: "budget"
1764
+ });
1765
+ observer.loopAfter({
1766
+ turns,
1767
+ toolResults: toolResults.length,
1768
+ stopReason: "budget"
1769
+ });
1770
+ return {
1771
+ finalText,
1772
+ toolResults,
1773
+ turns,
1774
+ stopReason: "budget",
1775
+ cappedOut: true
1776
+ };
1777
+ }
1778
+ }
1779
+ const label = labelFor(call);
1780
+ const rendered = render(label, outcome);
1781
+ toolResults.push({
1782
+ call,
1783
+ label,
1784
+ outcome
1785
+ });
1786
+ outcomes.push({
1787
+ call,
1788
+ label,
1789
+ outcome,
1790
+ rendered
1791
+ });
1792
+ messages.push(toolResultMessage(call, rendered));
1793
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1794
+ }
1795
+ observer.failureRecovery({
1796
+ toolTurn,
1797
+ messages,
1798
+ turnText,
1799
+ outcomes
1800
+ });
1801
+ observer.turnAfter(toolTurn, turnEventId, {
1802
+ pendingToolCalls: pending.length,
1803
+ toolResults: outcomes.map((item) => ({
1804
+ toolName: item.call.toolName,
1805
+ toolCallId: item.call.toolCallId,
1806
+ ok: item.outcome.ok
1807
+ })),
1808
+ failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length
1809
+ });
1810
+ }
1811
+ observer.loopAfter({
1812
+ turns,
1813
+ toolResults: toolResults.length,
1814
+ stopReason: "completed"
1815
+ });
1816
+ return {
1817
+ finalText,
1818
+ toolResults,
1819
+ turns,
1820
+ stopReason: "completed",
1821
+ cappedOut: false
1822
+ };
1823
+ }
1824
+ /** Streaming bounded tool loop: yields each raw turn event (the caller maps +
1825
+ * telemetries + re-emits it) and each executed `tool_result`; emits one
1826
+ * `capped` if it stops for any non-completed reason with calls still pending. */
1713
1827
  async function* streamToolLoop(opts) {
1714
- const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
1715
- const render = opts.renderResult ?? defaultRender;
1716
- const labelFor = opts.labelFor ?? ((c) => c.toolName);
1717
- const runId = opts.runId ?? `agent-run-${randomSuffix2()}`;
1718
- const messages = [
1719
- { role: "system", content: opts.systemPrompt },
1720
- ...opts.priorMessages ?? [],
1721
- { role: "user", content: opts.userMessage }
1722
- ];
1723
- const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId);
1724
- let accumulatedCostUsd = 0;
1725
- let lastCallHash = null;
1726
- let consecutiveCount = 0;
1727
- observer.loopBefore(backstop, messages.length);
1728
- for (let toolTurn = 0; ; toolTurn++) {
1729
- if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
1730
- observer.loopAfter({ turns: toolTurn + 1, stopReason: "deadline" });
1731
- yield { kind: "capped", pending: 0, stopReason: "deadline" };
1732
- return;
1733
- }
1734
- let turnText = "";
1735
- const pending = [];
1736
- const turnEventId = observer.turnBefore(toolTurn, messages.length);
1737
- for await (const event of opts.streamTurn([...messages])) {
1738
- yield { kind: "event", event };
1739
- turnText += opts.extractText(event);
1740
- const call = opts.extractToolCall(event);
1741
- if (call && opts.isExecutableTool(call.toolName)) pending.push(call);
1742
- }
1743
- if (pending.length === 0) {
1744
- observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 });
1745
- observer.loopAfter({ turns: toolTurn + 1, stopReason: "completed" });
1746
- return;
1747
- }
1748
- if (toolTurn >= backstop) {
1749
- observer.turnAfter(toolTurn, turnEventId, {
1750
- pendingToolCalls: pending.length,
1751
- stopReason: "backstop"
1752
- });
1753
- observer.loopAfter({ turns: toolTurn + 1, stopReason: "backstop" });
1754
- yield { kind: "capped", pending: pending.length, stopReason: "backstop" };
1755
- return;
1756
- }
1757
- messages.push(assistantToolCallMessage(turnText, pending));
1758
- const outcomes = [];
1759
- for (const [callIndex, call] of pending.entries()) {
1760
- const callHash = canonicalCallHash(call);
1761
- if (callHash === lastCallHash) {
1762
- consecutiveCount++;
1763
- } else {
1764
- lastCallHash = callHash;
1765
- consecutiveCount = 1;
1766
- }
1767
- if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
1768
- observer.turnAfter(toolTurn, turnEventId, {
1769
- pendingToolCalls: pending.length,
1770
- stopReason: "stuck-loop"
1771
- });
1772
- observer.loopAfter({ turns: toolTurn + 1, stopReason: "stuck-loop" });
1773
- yield { kind: "capped", pending: pending.length, stopReason: "stuck-loop" };
1774
- return;
1775
- }
1776
- const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call);
1777
- let outcome;
1778
- try {
1779
- outcome = await opts.executeToolCall(call);
1780
- } catch (err) {
1781
- outcome = {
1782
- ok: false,
1783
- code: "executor_error",
1784
- message: err instanceof Error ? err.message : String(err)
1785
- };
1786
- }
1787
- if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
1788
- accumulatedCostUsd += opts.costOf(call, outcome);
1789
- if (accumulatedCostUsd >= opts.maxCostUsd) {
1790
- const label2 = labelFor(call);
1791
- yield {
1792
- kind: "tool_result",
1793
- toolName: call.toolName,
1794
- toolCallId: call.toolCallId,
1795
- label: label2,
1796
- outcome
1797
- };
1798
- messages.push(toolResultMessage(call, render(label2, outcome)));
1799
- observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1800
- observer.turnAfter(toolTurn, turnEventId, {
1801
- pendingToolCalls: pending.length,
1802
- stopReason: "budget"
1803
- });
1804
- observer.loopAfter({ turns: toolTurn + 1, stopReason: "budget" });
1805
- yield { kind: "capped", pending: pending.length, stopReason: "budget" };
1806
- return;
1807
- }
1808
- }
1809
- const label = labelFor(call);
1810
- yield {
1811
- kind: "tool_result",
1812
- toolName: call.toolName,
1813
- toolCallId: call.toolCallId,
1814
- label,
1815
- outcome
1816
- };
1817
- const rendered = render(label, outcome);
1818
- outcomes.push({ call, label, outcome, rendered });
1819
- messages.push(toolResultMessage(call, rendered));
1820
- observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1821
- }
1822
- observer.failureRecovery({
1823
- toolTurn,
1824
- messages,
1825
- turnText,
1826
- outcomes
1827
- });
1828
- observer.turnAfter(toolTurn, turnEventId, {
1829
- pendingToolCalls: pending.length,
1830
- toolResults: outcomes.map((item) => ({
1831
- toolName: item.call.toolName,
1832
- toolCallId: item.call.toolCallId,
1833
- ok: item.outcome.ok
1834
- })),
1835
- failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length
1836
- });
1837
- }
1828
+ const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
1829
+ const render = opts.renderResult ?? defaultRender;
1830
+ const labelFor = opts.labelFor ?? ((c) => c.toolName);
1831
+ const runId = opts.runId ?? `agent-run-${randomSuffix()}`;
1832
+ const messages = [
1833
+ {
1834
+ role: "system",
1835
+ content: opts.systemPrompt
1836
+ },
1837
+ ...opts.priorMessages ?? [],
1838
+ {
1839
+ role: "user",
1840
+ content: opts.userMessage
1841
+ }
1842
+ ];
1843
+ const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId);
1844
+ let accumulatedCostUsd = 0;
1845
+ let lastCallHash = null;
1846
+ let consecutiveCount = 0;
1847
+ observer.loopBefore(backstop, messages.length);
1848
+ for (let toolTurn = 0;; toolTurn++) {
1849
+ if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
1850
+ observer.loopAfter({
1851
+ turns: toolTurn + 1,
1852
+ stopReason: "deadline"
1853
+ });
1854
+ yield {
1855
+ kind: "capped",
1856
+ pending: 0,
1857
+ stopReason: "deadline"
1858
+ };
1859
+ return;
1860
+ }
1861
+ let turnText = "";
1862
+ const pending = [];
1863
+ const turnEventId = observer.turnBefore(toolTurn, messages.length);
1864
+ for await (const event of opts.streamTurn([...messages])) {
1865
+ yield {
1866
+ kind: "event",
1867
+ event
1868
+ };
1869
+ turnText += opts.extractText(event);
1870
+ const call = opts.extractToolCall(event);
1871
+ if (call && opts.isExecutableTool(call.toolName)) pending.push(call);
1872
+ }
1873
+ if (pending.length === 0) {
1874
+ observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 });
1875
+ observer.loopAfter({
1876
+ turns: toolTurn + 1,
1877
+ stopReason: "completed"
1878
+ });
1879
+ return;
1880
+ }
1881
+ if (toolTurn >= backstop) {
1882
+ observer.turnAfter(toolTurn, turnEventId, {
1883
+ pendingToolCalls: pending.length,
1884
+ stopReason: "backstop"
1885
+ });
1886
+ observer.loopAfter({
1887
+ turns: toolTurn + 1,
1888
+ stopReason: "backstop"
1889
+ });
1890
+ yield {
1891
+ kind: "capped",
1892
+ pending: pending.length,
1893
+ stopReason: "backstop"
1894
+ };
1895
+ return;
1896
+ }
1897
+ messages.push(assistantToolCallMessage(turnText, pending));
1898
+ const outcomes = [];
1899
+ for (const [callIndex, call] of pending.entries()) {
1900
+ const callHash = canonicalCallHash(call);
1901
+ if (callHash === lastCallHash) consecutiveCount++;
1902
+ else {
1903
+ lastCallHash = callHash;
1904
+ consecutiveCount = 1;
1905
+ }
1906
+ if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
1907
+ observer.turnAfter(toolTurn, turnEventId, {
1908
+ pendingToolCalls: pending.length,
1909
+ stopReason: "stuck-loop"
1910
+ });
1911
+ observer.loopAfter({
1912
+ turns: toolTurn + 1,
1913
+ stopReason: "stuck-loop"
1914
+ });
1915
+ yield {
1916
+ kind: "capped",
1917
+ pending: pending.length,
1918
+ stopReason: "stuck-loop"
1919
+ };
1920
+ return;
1921
+ }
1922
+ const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call);
1923
+ let outcome;
1924
+ try {
1925
+ outcome = await opts.executeToolCall(call);
1926
+ } catch (err) {
1927
+ outcome = {
1928
+ ok: false,
1929
+ code: "executor_error",
1930
+ message: err instanceof Error ? err.message : String(err)
1931
+ };
1932
+ }
1933
+ if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
1934
+ accumulatedCostUsd += opts.costOf(call, outcome);
1935
+ if (accumulatedCostUsd >= opts.maxCostUsd) {
1936
+ const label = labelFor(call);
1937
+ yield {
1938
+ kind: "tool_result",
1939
+ toolName: call.toolName,
1940
+ toolCallId: call.toolCallId,
1941
+ label,
1942
+ outcome
1943
+ };
1944
+ messages.push(toolResultMessage(call, render(label, outcome)));
1945
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1946
+ observer.turnAfter(toolTurn, turnEventId, {
1947
+ pendingToolCalls: pending.length,
1948
+ stopReason: "budget"
1949
+ });
1950
+ observer.loopAfter({
1951
+ turns: toolTurn + 1,
1952
+ stopReason: "budget"
1953
+ });
1954
+ yield {
1955
+ kind: "capped",
1956
+ pending: pending.length,
1957
+ stopReason: "budget"
1958
+ };
1959
+ return;
1960
+ }
1961
+ }
1962
+ const label = labelFor(call);
1963
+ yield {
1964
+ kind: "tool_result",
1965
+ toolName: call.toolName,
1966
+ toolCallId: call.toolCallId,
1967
+ label,
1968
+ outcome
1969
+ };
1970
+ const rendered = render(label, outcome);
1971
+ outcomes.push({
1972
+ call,
1973
+ label,
1974
+ outcome,
1975
+ rendered
1976
+ });
1977
+ messages.push(toolResultMessage(call, rendered));
1978
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
1979
+ }
1980
+ observer.failureRecovery({
1981
+ toolTurn,
1982
+ messages,
1983
+ turnText,
1984
+ outcomes
1985
+ });
1986
+ observer.turnAfter(toolTurn, turnEventId, {
1987
+ pendingToolCalls: pending.length,
1988
+ toolResults: outcomes.map((item) => ({
1989
+ toolName: item.call.toolName,
1990
+ toolCallId: item.call.toolCallId,
1991
+ ok: item.outcome.ok
1992
+ })),
1993
+ failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length
1994
+ });
1995
+ }
1838
1996
  }
1839
1997
  function createToolLoopObserver(hooks, runId, scenarioId) {
1840
- const loopEventId = `${runId}:agent.run`;
1841
- return {
1842
- loopBefore: (maxToolTurns, messageCount) => {
1843
- notifyToolLoopEvent({
1844
- hooks,
1845
- runId,
1846
- scenarioId,
1847
- target: "agent.run",
1848
- phase: "before",
1849
- id: `${loopEventId}:before`,
1850
- payload: { maxToolTurns, messageCount }
1851
- });
1852
- },
1853
- loopAfter: (payload) => {
1854
- notifyToolLoopEvent({
1855
- hooks,
1856
- runId,
1857
- scenarioId,
1858
- target: "agent.run",
1859
- phase: "after",
1860
- id: `${loopEventId}:after`,
1861
- payload
1862
- });
1863
- },
1864
- turnBefore: (toolTurn, messageCount) => {
1865
- const turnEventId = `${loopEventId}:${toolTurn}`;
1866
- notifyToolLoopEvent({
1867
- hooks,
1868
- runId,
1869
- scenarioId,
1870
- target: "agent.turn",
1871
- phase: "before",
1872
- id: turnEventId,
1873
- stepIndex: toolTurn,
1874
- parentId: loopEventId,
1875
- payload: { messageCount }
1876
- });
1877
- return turnEventId;
1878
- },
1879
- turnAfter: (toolTurn, turnEventId, payload) => {
1880
- notifyToolLoopEvent({
1881
- hooks,
1882
- runId,
1883
- scenarioId,
1884
- target: "agent.turn",
1885
- phase: "after",
1886
- id: `${turnEventId}:after`,
1887
- stepIndex: toolTurn,
1888
- parentId: turnEventId,
1889
- payload
1890
- });
1891
- },
1892
- toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {
1893
- const callEventId = `${turnEventId}:tool-call:${callIndex}`;
1894
- notifyToolLoopEvent({
1895
- hooks,
1896
- runId,
1897
- scenarioId,
1898
- target: "agent.tool_call",
1899
- phase: "before",
1900
- id: callEventId,
1901
- stepIndex: toolTurn,
1902
- parentId: turnEventId,
1903
- payload: toolCallPayload(call)
1904
- });
1905
- return callEventId;
1906
- },
1907
- toolCallAfter: (toolTurn, callEventId, call, outcome) => {
1908
- notifyToolLoopEvent({
1909
- hooks,
1910
- runId,
1911
- scenarioId,
1912
- target: "agent.tool_call",
1913
- phase: "after",
1914
- id: `${callEventId}:after`,
1915
- stepIndex: toolTurn,
1916
- parentId: callEventId,
1917
- payload: { ...toolCallPayload(call), outcome: outcomePayload(outcome) }
1918
- });
1919
- },
1920
- failureRecovery: (options) => {
1921
- notifyToolFailureRecovery({
1922
- hooks,
1923
- runId,
1924
- scenarioId,
1925
- stepIndex: options.toolTurn,
1926
- messages: options.messages,
1927
- turnText: options.turnText,
1928
- outcomes: options.outcomes
1929
- });
1930
- }
1931
- };
1998
+ const loopEventId = `${runId}:agent.run`;
1999
+ return {
2000
+ loopBefore: (maxToolTurns, messageCount) => {
2001
+ notifyToolLoopEvent({
2002
+ hooks,
2003
+ runId,
2004
+ scenarioId,
2005
+ target: "agent.run",
2006
+ phase: "before",
2007
+ id: `${loopEventId}:before`,
2008
+ payload: {
2009
+ maxToolTurns,
2010
+ messageCount
2011
+ }
2012
+ });
2013
+ },
2014
+ loopAfter: (payload) => {
2015
+ notifyToolLoopEvent({
2016
+ hooks,
2017
+ runId,
2018
+ scenarioId,
2019
+ target: "agent.run",
2020
+ phase: "after",
2021
+ id: `${loopEventId}:after`,
2022
+ payload
2023
+ });
2024
+ },
2025
+ turnBefore: (toolTurn, messageCount) => {
2026
+ const turnEventId = `${loopEventId}:${toolTurn}`;
2027
+ notifyToolLoopEvent({
2028
+ hooks,
2029
+ runId,
2030
+ scenarioId,
2031
+ target: "agent.turn",
2032
+ phase: "before",
2033
+ id: turnEventId,
2034
+ stepIndex: toolTurn,
2035
+ parentId: loopEventId,
2036
+ payload: { messageCount }
2037
+ });
2038
+ return turnEventId;
2039
+ },
2040
+ turnAfter: (toolTurn, turnEventId, payload) => {
2041
+ notifyToolLoopEvent({
2042
+ hooks,
2043
+ runId,
2044
+ scenarioId,
2045
+ target: "agent.turn",
2046
+ phase: "after",
2047
+ id: `${turnEventId}:after`,
2048
+ stepIndex: toolTurn,
2049
+ parentId: turnEventId,
2050
+ payload
2051
+ });
2052
+ },
2053
+ toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {
2054
+ const callEventId = `${turnEventId}:tool-call:${callIndex}`;
2055
+ notifyToolLoopEvent({
2056
+ hooks,
2057
+ runId,
2058
+ scenarioId,
2059
+ target: "agent.tool_call",
2060
+ phase: "before",
2061
+ id: callEventId,
2062
+ stepIndex: toolTurn,
2063
+ parentId: turnEventId,
2064
+ payload: toolCallPayload(call)
2065
+ });
2066
+ return callEventId;
2067
+ },
2068
+ toolCallAfter: (toolTurn, callEventId, call, outcome) => {
2069
+ notifyToolLoopEvent({
2070
+ hooks,
2071
+ runId,
2072
+ scenarioId,
2073
+ target: "agent.tool_call",
2074
+ phase: "after",
2075
+ id: `${callEventId}:after`,
2076
+ stepIndex: toolTurn,
2077
+ parentId: callEventId,
2078
+ payload: {
2079
+ ...toolCallPayload(call),
2080
+ outcome: outcomePayload(outcome)
2081
+ }
2082
+ });
2083
+ },
2084
+ failureRecovery: (options) => {
2085
+ notifyToolFailureRecovery({
2086
+ hooks,
2087
+ runId,
2088
+ scenarioId,
2089
+ stepIndex: options.toolTurn,
2090
+ messages: options.messages,
2091
+ turnText: options.turnText,
2092
+ outcomes: options.outcomes
2093
+ });
2094
+ }
2095
+ };
1932
2096
  }
1933
2097
  function notifyToolLoopEvent(options) {
1934
- notifyRuntimeHookEvent(options.hooks, {
1935
- id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,
1936
- runId: options.runId,
1937
- scenarioId: options.scenarioId,
1938
- target: options.target,
1939
- phase: options.phase,
1940
- timestamp: Date.now(),
1941
- stepIndex: options.stepIndex,
1942
- parentId: options.parentId,
1943
- payload: options.payload,
1944
- metadata: { producer: "tool-loop", ...options.metadata }
1945
- });
2098
+ notifyRuntimeHookEvent(options.hooks, {
2099
+ id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,
2100
+ runId: options.runId,
2101
+ scenarioId: options.scenarioId,
2102
+ target: options.target,
2103
+ phase: options.phase,
2104
+ timestamp: Date.now(),
2105
+ stepIndex: options.stepIndex,
2106
+ parentId: options.parentId,
2107
+ payload: options.payload,
2108
+ metadata: {
2109
+ producer: "tool-loop",
2110
+ ...options.metadata
2111
+ }
2112
+ });
1946
2113
  }
1947
2114
  function notifyToolFailureRecovery(options) {
1948
- const failed = options.outcomes.filter((item) => !item.outcome.ok);
1949
- if (failed.length === 0) return;
1950
- const evidence = [];
1951
- for (const item of failed) {
1952
- const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`;
1953
- evidence.push({
1954
- source: "tool_call",
1955
- id,
1956
- detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2e3)}`,
1957
- metadata: { toolName: item.call.toolName, label: item.label }
1958
- });
1959
- evidence.push({
1960
- source: "tool_result",
1961
- id: `${id}:result`,
1962
- detail: item.rendered,
1963
- metadata: failureMetadata(item.outcome)
1964
- });
1965
- }
1966
- notifyRuntimeDecisionPoint(options.hooks, {
1967
- id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,
1968
- runId: options.runId,
1969
- scenarioId: options.scenarioId,
1970
- stepIndex: options.stepIndex,
1971
- kind: "retry",
1972
- candidateActions: [...FAILURE_RECOVERY_ACTIONS],
1973
- context: renderDecisionContext(options.messages, options.turnText, options.outcomes),
1974
- evidence,
1975
- metadata: {
1976
- target: "failure-recovery",
1977
- source: "agent.turn",
1978
- failedToolCount: failed.length,
1979
- toolNames: failed.map((item) => item.call.toolName)
1980
- }
1981
- });
2115
+ const failed = options.outcomes.filter((item) => !item.outcome.ok);
2116
+ if (failed.length === 0) return;
2117
+ const evidence = [];
2118
+ for (const item of failed) {
2119
+ const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`;
2120
+ evidence.push({
2121
+ source: "tool_call",
2122
+ id,
2123
+ detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2e3)}`,
2124
+ metadata: {
2125
+ toolName: item.call.toolName,
2126
+ label: item.label
2127
+ }
2128
+ });
2129
+ evidence.push({
2130
+ source: "tool_result",
2131
+ id: `${id}:result`,
2132
+ detail: item.rendered,
2133
+ metadata: failureMetadata(item.outcome)
2134
+ });
2135
+ }
2136
+ notifyRuntimeDecisionPoint(options.hooks, {
2137
+ id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,
2138
+ runId: options.runId,
2139
+ scenarioId: options.scenarioId,
2140
+ stepIndex: options.stepIndex,
2141
+ kind: "retry",
2142
+ candidateActions: [...FAILURE_RECOVERY_ACTIONS],
2143
+ context: renderDecisionContext(options.messages, options.turnText, options.outcomes),
2144
+ evidence,
2145
+ metadata: {
2146
+ target: "failure-recovery",
2147
+ source: "agent.turn",
2148
+ failedToolCount: failed.length,
2149
+ toolNames: failed.map((item) => item.call.toolName)
2150
+ }
2151
+ });
1982
2152
  }
1983
2153
  function toolCallPayload(call) {
1984
- return {
1985
- toolName: call.toolName,
1986
- toolCallId: call.toolCallId,
1987
- argsPreview: stringifySafe(call.args, 2e3)
1988
- };
2154
+ return {
2155
+ toolName: call.toolName,
2156
+ toolCallId: call.toolCallId,
2157
+ argsPreview: stringifySafe(call.args, 2e3)
2158
+ };
1989
2159
  }
1990
2160
  function outcomePayload(outcome) {
1991
- if (!outcome.ok) {
1992
- return {
1993
- ok: false,
1994
- code: outcome.code,
1995
- message: trimText(outcome.message, 2e3),
1996
- status: outcome.status
1997
- };
1998
- }
1999
- return {
2000
- ok: true,
2001
- resultPreview: stringifySafe(outcome.result, 2e3)
2002
- };
2161
+ if (!outcome.ok) return {
2162
+ ok: false,
2163
+ code: outcome.code,
2164
+ message: trimText(outcome.message, 2e3),
2165
+ status: outcome.status
2166
+ };
2167
+ return {
2168
+ ok: true,
2169
+ resultPreview: stringifySafe(outcome.result, 2e3)
2170
+ };
2003
2171
  }
2004
2172
  function failureMetadata(outcome) {
2005
- if (outcome.ok) return void 0;
2006
- return {
2007
- code: outcome.code,
2008
- message: outcome.message,
2009
- status: outcome.status
2010
- };
2173
+ if (outcome.ok) return void 0;
2174
+ return {
2175
+ code: outcome.code,
2176
+ message: outcome.message,
2177
+ status: outcome.status
2178
+ };
2011
2179
  }
2012
2180
  function renderDecisionContext(messages, turnText, outcomes) {
2013
- const recent = messages.slice(-6).map((message) => `[${message.role}]
2014
- ${message.content ?? ""}`);
2015
- const assistant = turnText.trim() ? [`[assistant]
2016
- ${turnText}`] : [];
2017
- const toolResults = [`[tool results]
2018
- ${outcomes.map((item) => item.rendered).join("\n")}`];
2019
- return trimText(
2020
- [...recent, ...assistant, ...toolResults].join("\n\n"),
2021
- DEFAULT_DECISION_CONTEXT_CHARS
2022
- );
2023
- }
2181
+ const recent = messages.slice(-6).map((message) => `[${message.role}]\n${message.content ?? ""}`);
2182
+ const assistant = turnText.trim() ? [`[assistant]\n${turnText}`] : [];
2183
+ const toolResults = [`[tool results]\n${outcomes.map((item) => item.rendered).join("\n")}`];
2184
+ return trimText([
2185
+ ...recent,
2186
+ ...assistant,
2187
+ ...toolResults
2188
+ ].join("\n\n"), DEFAULT_DECISION_CONTEXT_CHARS);
2189
+ }
2190
+ /** Canonical identifier for a tool call used by stuck-loop detection.
2191
+ * Keys are sorted so `{b:1,a:2}` and `{a:2,b:1}` produce the same hash. */
2024
2192
  function canonicalCallHash(call) {
2025
- const sortedArgs = Object.fromEntries(
2026
- Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b))
2027
- );
2028
- return `${call.toolName}:${JSON.stringify(sortedArgs)}`;
2193
+ const sortedArgs = Object.fromEntries(Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)));
2194
+ return `${call.toolName}:${JSON.stringify(sortedArgs)}`;
2029
2195
  }
2030
2196
  function stringifySafe(value, max) {
2031
- let text;
2032
- try {
2033
- text = JSON.stringify(value) ?? String(value);
2034
- } catch {
2035
- text = String(value);
2036
- }
2037
- return trimText(text, max);
2197
+ let text;
2198
+ try {
2199
+ text = JSON.stringify(value) ?? String(value);
2200
+ } catch {
2201
+ text = String(value);
2202
+ }
2203
+ return trimText(text, max);
2038
2204
  }
2039
2205
  function trimText(text, max) {
2040
- if (text.length <= max) return text;
2041
- return `${text.slice(0, max)}\u2026`;
2042
- }
2043
- function randomSuffix2(len = 8) {
2044
- return Math.random().toString(36).slice(2, 2 + len);
2045
- }
2046
- export {
2047
- AGENTIC_PROFILE_RESOURCE_ROOT,
2048
- AGENT_CANDIDATE_EXECUTION_SUPPORT,
2049
- AgentEvalError,
2050
- BackendTransportError,
2051
- CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV,
2052
- CANDIDATE_KNOWLEDGE_ROOT_ENV,
2053
- CANDIDATE_TRACE_ENV,
2054
- CANDIDATE_TRACE_TAGS,
2055
- CircuitBreakerState,
2056
- CircuitOpenError,
2057
- ConfigError,
2058
- DEFAULT_MAX_DEPTH,
2059
- DEFAULT_ROUTER_BASE_URL,
2060
- DELEGATED_LOOP_MODES,
2061
- DeadlineExceededError,
2062
- FORWARD_HEADERS,
2063
- FileAgentCandidateExecutionClaimStore,
2064
- FileConversationJournal,
2065
- INTELLIGENCE_WIRE_VERSION,
2066
- InMemoryAgentCandidateExecutionClaimStore,
2067
- InMemoryConversationJournal,
2068
- InMemoryRuntimeSessionStore,
2069
- JudgeError,
2070
- LIFTED_FINDING_ANALYST_ID,
2071
- NotFoundError,
2072
- OfficialOptimizerUnavailableError,
2073
- PlannerError,
2074
- RESEARCH_SUPERVISOR_SYSTEM_PROMPT,
2075
- ROLLOUT_POLICY_EXTENSION,
2076
- RuntimeRunStateError,
2077
- SqlConversationJournal,
2078
- ValidationError,
2079
- agenticGenerator,
2080
- applyExactAgentProfileDiff,
2081
- applyRolloutPolicyToProfile,
2082
- applyRunRecordDefaults,
2083
- assertCandidateProfileBinding,
2084
- auditLoopRunner,
2085
- buildAgentCandidateBundle,
2086
- buildDriverSystem,
2087
- buildForwardHeaders,
2088
- buildKnowledgeImprovementExperimentBundles,
2089
- buildLoopOtelSpans,
2090
- buildLoopSpanNodes,
2091
- buildRuntimeEventOtelSpans,
2092
- candidateExecutionClaim,
2093
- candidateKnowledgeExecutionPaths,
2094
- captureAgentCandidateWorkspace,
2095
- captureAgentCandidateWorkspaceFiles,
2096
- cleanModelId,
2097
- commandVerifier,
2098
- composeRuntimeHooks,
2099
- computeBackoff,
2100
- createAgentCandidateWorkspacePort,
2101
- createAgentKnowledgeReadinessCheck,
2102
- createConversationBackend,
2103
- createIterableBackend,
2104
- createKnowledgeImprovementActivationExecutor,
2105
- createOpenAICompatibleBackend,
2106
- createOtelExporter,
2107
- createProtectedAgentCandidateModelPort,
2108
- createRuntimeEventCollector,
2109
- createRuntimeStreamEventCollector,
2110
- createSandboxPromptBackend,
2111
- createSupervisedKnowledgeUpdater,
2112
- d1ToSqlAdapter,
2113
- decideKnowledgeReadiness,
2114
- defaultBuildPrompt,
2115
- defaultIsRetryable,
2116
- defineConversation,
2117
- defineRuntimeHooks,
2118
- deriveExecutionId,
2119
- disposePreparedAgentCandidateExecution,
2120
- driverLoopGenerator,
2121
- exactProcessProviderAsCandidateExecutor,
2122
- executePreparedAgentCandidate,
2123
- exportEvalRuns,
2124
- findingLines,
2125
- formatSupervisedKnowledgeTask,
2126
- getModels,
2127
- handleChatTurn,
2128
- improve,
2129
- isAnalystFinding,
2130
- isDelegatedLoopMode,
2131
- isDepthExceeded,
2132
- knowledgeReadinessDeliverable,
2133
- loopEventToOtelSpan,
2134
- makePerAttemptSignal,
2135
- mcpBuildPrompt,
2136
- mcpServeVerifier,
2137
- mcpToolsForRuntimeMcp,
2138
- mcpToolsForRuntimeMcpSubset,
2139
- normalizeRolloutPolicy,
2140
- notifyRuntimeDecisionPoint,
2141
- notifyRuntimeHookEvent,
2142
- officialGepa,
2143
- officialSkillOpt,
2144
- optimizerMethod,
2145
- parseExactAgentProfile,
2146
- parseExactAgentProfileDiff,
2147
- parseLoopRunnerArgv,
2148
- parseRolloutPolicy,
2149
- persistCandidateOutputArtifact,
2150
- prepareAgentCandidateExecution,
2151
- rawTraceDistiller,
2152
- readDepth,
2153
- readinessServerSentEvent,
2154
- recoverExpiredAgentCandidateExecution,
2155
- reflectiveGenerator,
2156
- researchDriverNote,
2157
- researchLoopRunner,
2158
- resolveAgentBackend,
2159
- resolveChatModel,
2160
- resolveRouterBaseUrl,
2161
- runAgentTask,
2162
- runAgentTaskStream,
2163
- runConversation,
2164
- runConversationStream,
2165
- runDelegatedLoop,
2166
- runKnowledgeImprovementJob,
2167
- runLoopRunnerCli,
2168
- runPersonaConversation,
2169
- runPersonaDispatch,
2170
- runSupervisedKnowledgeUpdate,
2171
- runToolLoop,
2172
- runtimeStreamServerSentEvent,
2173
- sanitizeAgentRuntimeEvent,
2174
- sanitizeKnowledgeReadinessReport,
2175
- sanitizeRuntimeStreamEvent,
2176
- sealAgentCandidateBundle,
2177
- serializeRolloutPolicy,
2178
- sleep,
2179
- slugifySpeaker,
2180
- startRuntimeRun,
2181
- strategyAuthorMethod,
2182
- streamToolLoop,
2183
- structuralRolloutPolicyFromProfile,
2184
- toAnalystFindings,
2185
- toolBuildPrompt,
2186
- turnId,
2187
- validateChatModelId,
2188
- verifyAgentCandidateBundle,
2189
- worktreeLoopRunner
2190
- };
2206
+ if (text.length <= max) return text;
2207
+ return `${text.slice(0, max)}…`;
2208
+ }
2209
+ function randomSuffix(len = 8) {
2210
+ return Math.random().toString(36).slice(2, 2 + len);
2211
+ }
2212
+ //#endregion
2213
+ export { AGENTIC_PROFILE_RESOURCE_ROOT, AGENT_CANDIDATE_EXECUTION_SUPPORT, AgentEvalError, BackendTransportError, CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV, CANDIDATE_KNOWLEDGE_ROOT_ENV, CANDIDATE_TRACE_ENV, CANDIDATE_TRACE_TAGS, CircuitBreakerState, CircuitOpenError, ConfigError, DEFAULT_MAX_DEPTH, DEFAULT_ROUTER_BASE_URL, DELEGATED_LOOP_MODES, DeadlineExceededError, FORWARD_HEADERS, FileAgentCandidateExecutionClaimStore, FileConversationJournal, INTELLIGENCE_WIRE_VERSION, InMemoryAgentCandidateExecutionClaimStore, InMemoryConversationJournal, InMemoryRuntimeSessionStore, JudgeError, LIFTED_FINDING_ANALYST_ID, NotFoundError, OfficialOptimizerUnavailableError, PlannerError, RESEARCH_SUPERVISOR_SYSTEM_PROMPT, ROLLOUT_POLICY_EXTENSION, RuntimeRunStateError, SqlConversationJournal, ValidationError, agenticGenerator, applyExactAgentProfileDiff, applyRolloutPolicyToProfile, applyRunRecordDefaults, assertCandidateProfileBinding, auditLoopRunner, buildAgentCandidateBundle, buildDriverSystem, buildForwardHeaders, buildKnowledgeImprovementExperimentBundles, buildLoopOtelSpans, buildLoopSpanNodes, buildRuntimeEventOtelSpans, candidateExecutionClaim, candidateKnowledgeExecutionPaths, captureAgentCandidateWorkspace, captureAgentCandidateWorkspaceFiles, cleanModelId, commandVerifier, composeRuntimeHooks, computeBackoff, createAgentCandidateWorkspacePort, createAgentKnowledgeReadinessCheck, createConversationBackend, createIterableBackend, createKnowledgeImprovementActivationExecutor, createOpenAICompatibleBackend, createOtelExporter, createProtectedAgentCandidateModelPort, createRuntimeEventCollector, createRuntimeStreamEventCollector, createSandboxPromptBackend, createSupervisedKnowledgeUpdater, d1ToSqlAdapter, decideKnowledgeReadiness, defaultBuildPrompt, defaultIsRetryable, defineConversation, defineRuntimeHooks, deriveExecutionId, disposePreparedAgentCandidateExecution, driverLoopGenerator, exactProcessProviderAsCandidateExecutor, executePreparedAgentCandidate, exportEvalRuns, findingLines, formatSupervisedKnowledgeTask, getModels, handleChatTurn, improve, isAnalystFinding, isDelegatedLoopMode, isDepthExceeded, knowledgeReadinessDeliverable, loopEventToOtelSpan, makePerAttemptSignal, mcpBuildPrompt, mcpServeVerifier, mcpToolsForRuntimeMcp, mcpToolsForRuntimeMcpSubset, normalizeRolloutPolicy, notifyRuntimeDecisionPoint, notifyRuntimeHookEvent, officialGepa, officialSkillOpt, optimizerMethod, parseExactAgentProfile, parseExactAgentProfileDiff, parseLoopRunnerArgv, parseRolloutPolicy, persistCandidateOutputArtifact, prepareAgentCandidateExecution, rawTraceDistiller, readDepth, readinessServerSentEvent, recoverExpiredAgentCandidateExecution, reflectiveGenerator, researchDriverNote, researchLoopRunner, resolveAgentBackend, resolveChatModel, resolveRouterBaseUrl, runAgentTask, runAgentTaskStream, runConversation, runConversationStream, runDelegatedLoop, runKnowledgeImprovementJob, runLoopRunnerCli, runPersonaConversation, runPersonaDispatch, runSupervisedKnowledgeUpdate, runToolLoop, runtimeStreamServerSentEvent, sanitizeAgentRuntimeEvent, sanitizeKnowledgeReadinessReport, sanitizeRuntimeStreamEvent, sealAgentCandidateBundle, serializeRolloutPolicy, sleep, slugifySpeaker, startRuntimeRun, strategyAuthorMethod, streamToolLoop, structuralRolloutPolicyFromProfile, toAnalystFindings, toolBuildPrompt, turnId, validateChatModelId, verifyAgentCandidateBundle, worktreeLoopRunner };
2214
+
2191
2215
  //# sourceMappingURL=index.js.map