@tangle-network/agent-runtime 0.104.0 → 0.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -42
- package/dist/activation-CRjVfOiy.js +203 -0
- package/dist/activation-CRjVfOiy.js.map +1 -0
- package/dist/activation-DyWB0K6E.d.ts +98 -0
- package/dist/agent.d.ts +216 -234
- package/dist/agent.js +615 -547
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop-DGdtVENg.js +517 -0
- package/dist/analyst-loop-DGdtVENg.js.map +1 -0
- package/dist/analyst-loop.d.ts +10 -43
- package/dist/analyst-loop.js +2 -11
- package/dist/backends-Dy8NujB4.js +713 -0
- package/dist/backends-Dy8NujB4.js.map +1 -0
- package/dist/candidate-execution/index.d.ts +4 -192
- package/dist/candidate-execution/index.js +5 -66
- package/dist/candidate-execution-DqMbv1rU.js +488 -0
- package/dist/candidate-execution-DqMbv1rU.js.map +1 -0
- package/dist/coder-yhVWbdWc.js +40 -0
- package/dist/coder-yhVWbdWc.js.map +1 -0
- package/dist/conversation-XucG_K3l.js +1265 -0
- package/dist/conversation-XucG_K3l.js.map +1 -0
- package/dist/conversation.d.ts +2 -804
- package/dist/conversation.js +2 -55
- package/dist/environment-provider-CfLUmKmf.js +1168 -0
- package/dist/environment-provider-CfLUmKmf.js.map +1 -0
- package/dist/environment-provider-IUGU3epE.d.ts +964 -0
- package/dist/environment-provider.d.ts +2 -78
- package/dist/environment-provider.js +2 -18
- package/dist/errors-DEAvWQPy.js +111 -0
- package/dist/errors-DEAvWQPy.js.map +1 -0
- package/dist/improvement-adapter-Cr5TBSji.d.ts +166 -0
- package/dist/improvement-cycle-C095kS_g.js +2624 -0
- package/dist/improvement-cycle-C095kS_g.js.map +1 -0
- package/dist/index-BGdxAwfO.d.ts +838 -0
- package/dist/index-C-FYUuFG.d.ts +193 -0
- package/dist/index-CbNM40xG.d.ts +140 -0
- package/dist/index-DruSKs3Z.d.ts +7224 -0
- package/dist/index-Efjb3nrQ.d.ts +671 -0
- package/dist/index.d.ts +14 -1134
- package/dist/index.js +2134 -1972
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +776 -888
- package/dist/intelligence.js +1466 -1410
- package/dist/intelligence.js.map +1 -1
- package/dist/{kb-gate-CwHO0vz6.d.ts → kb-gate-C8z2juK8.d.ts} +33 -31
- package/dist/kb-gate-DpaSwXVx.js +80 -0
- package/dist/kb-gate-DpaSwXVx.js.map +1 -0
- package/dist/knowledge-Ku-mPP4f.js +433 -0
- package/dist/knowledge-Ku-mPP4f.js.map +1 -0
- package/dist/knowledge.d.ts +2 -150
- package/dist/knowledge.js +2 -40
- package/dist/local-harness-Dh8PJ0ot.d.ts +455 -0
- package/dist/loop-runner-bin-BXsFEUcf.d.ts +162 -0
- package/dist/loop-runner-bin-BwjQ1NfH.js +268 -0
- package/dist/loop-runner-bin-BwjQ1NfH.js.map +1 -0
- package/dist/loop-runner-bin.d.ts +2 -17
- package/dist/loop-runner-bin.js +2 -24
- package/dist/loops.d.ts +5 -4164
- package/dist/loops.js +8 -419
- package/dist/mcp/bin.d.ts +1 -1
- package/dist/mcp/bin.js +116 -146
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +295 -437
- package/dist/mcp/index.js +1242 -1177
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.d.ts +1 -1
- package/dist/mcp/memory-bin.js +37 -16
- package/dist/mcp/memory-bin.js.map +1 -1
- package/dist/memory-server-DL6cE2Ag.js +234 -0
- package/dist/memory-server-DL6cE2Ag.js.map +1 -0
- package/dist/model-resolution-Btd9iIKV.js +98 -0
- package/dist/model-resolution-Btd9iIKV.js.map +1 -0
- package/dist/openai-tools-B68JaOCx.d.ts +24 -0
- package/dist/openai-tools-CKLy1C7M.js +44 -0
- package/dist/openai-tools-CKLy1C7M.js.map +1 -0
- package/dist/otel-export-D34IQW1C.js +843 -0
- package/dist/otel-export-D34IQW1C.js.map +1 -0
- package/dist/platform.d.ts +188 -185
- package/dist/platform.js +185 -218
- package/dist/platform.js.map +1 -1
- package/dist/prepare-B53sPV7M.js +4292 -0
- package/dist/prepare-B53sPV7M.js.map +1 -0
- package/dist/primeintellect/index.d.ts +150 -147
- package/dist/primeintellect/index.js +626 -1004
- package/dist/primeintellect/index.js.map +1 -1
- package/dist/profiles.d.ts +296 -490
- package/dist/profiles.js +722 -749
- package/dist/profiles.js.map +1 -1
- package/dist/protected-model-port-BLPH5VVv.js +722 -0
- package/dist/protected-model-port-BLPH5VVv.js.map +1 -0
- package/dist/protected-model-port-BP6Z4eau.d.ts +283 -0
- package/dist/protected-redaction--F3v1oo8.js +200 -0
- package/dist/protected-redaction--F3v1oo8.js.map +1 -0
- package/dist/redact-D-u-rrcn.js +82 -0
- package/dist/redact-D-u-rrcn.js.map +1 -0
- package/dist/redact-kbIOjfjM.d.ts +452 -0
- package/dist/researcher-CoVqNhfI.js +433 -0
- package/dist/researcher-CoVqNhfI.js.map +1 -0
- package/dist/runtime-BG0BziaP.js +5408 -0
- package/dist/runtime-BG0BziaP.js.map +1 -0
- package/dist/sandbox-events-DeI5xX8P.js +316 -0
- package/dist/sandbox-events-DeI5xX8P.js.map +1 -0
- package/dist/spawn-journal-CwPvKUTa.js +456 -0
- package/dist/spawn-journal-CwPvKUTa.js.map +1 -0
- package/dist/structural-rollout-IXUEplky.js +1445 -0
- package/dist/structural-rollout-IXUEplky.js.map +1 -0
- package/dist/substrate-B0TYNrXn.js +27 -0
- package/dist/substrate-B0TYNrXn.js.map +1 -0
- package/dist/{substrate-Dyiqmd3o.d.ts → substrate-BcnuSHXm.d.ts} +57 -54
- package/dist/supervise-BiRutHS9.js +7321 -0
- package/dist/supervise-BiRutHS9.js.map +1 -0
- package/dist/supervisor-DTKhF-RV.js +3826 -0
- package/dist/supervisor-DTKhF-RV.js.map +1 -0
- package/dist/testing.d.ts +4 -3
- package/dist/testing.js +2478 -2637
- package/dist/testing.js.map +1 -1
- package/dist/tool-server-RcWgLIsL.js +141 -0
- package/dist/tool-server-RcWgLIsL.js.map +1 -0
- package/dist/trace-propagation-CJJC7SVB.js +57 -0
- package/dist/trace-propagation-CJJC7SVB.js.map +1 -0
- package/dist/types-BaIpb0XT.d.ts +492 -0
- package/dist/types-BevOjfTY.d.ts +765 -0
- package/dist/types-C9j4qg6l.d.ts +500 -0
- package/dist/types-Cja5OTGh.d.ts +190 -0
- package/dist/util-MVgdwuIS.js +145 -0
- package/dist/util-MVgdwuIS.js.map +1 -0
- package/dist/workspace-archive-BosQiotA.js +575 -0
- package/dist/workspace-archive-BosQiotA.js.map +1 -0
- package/package.json +43 -55
- package/skills/build-with-agent-runtime/SKILL.md +16 -9
- package/skills/loop-writer/SKILL.md +1 -1
- package/dist/activation-Cm-9MMX1.d.ts +0 -69
- package/dist/analyst-loop.js.map +0 -1
- package/dist/backends-BO4Jqoe2.d.ts +0 -134
- package/dist/candidate-execution/index.js.map +0 -1
- package/dist/chunk-2KGAN2HM.js +0 -1256
- package/dist/chunk-2KGAN2HM.js.map +0 -1
- package/dist/chunk-2QJSNOGK.js +0 -48
- package/dist/chunk-2QJSNOGK.js.map +0 -1
- package/dist/chunk-2UABGGUB.js +0 -474
- package/dist/chunk-2UABGGUB.js.map +0 -1
- package/dist/chunk-2VZYRB55.js +0 -834
- package/dist/chunk-2VZYRB55.js.map +0 -1
- package/dist/chunk-2X65HYMI.js +0 -3526
- package/dist/chunk-2X65HYMI.js.map +0 -1
- package/dist/chunk-3MDZX7YU.js +0 -125
- package/dist/chunk-3MDZX7YU.js.map +0 -1
- package/dist/chunk-3PPVCS75.js +0 -199
- package/dist/chunk-3PPVCS75.js.map +0 -1
- package/dist/chunk-5HNDYWUT.js +0 -6424
- package/dist/chunk-5HNDYWUT.js.map +0 -1
- package/dist/chunk-5IBQAPVB.js +0 -29
- package/dist/chunk-5IBQAPVB.js.map +0 -1
- package/dist/chunk-5QOB7H74.js +0 -387
- package/dist/chunk-5QOB7H74.js.map +0 -1
- package/dist/chunk-ATDFUJYI.js +0 -4728
- package/dist/chunk-ATDFUJYI.js.map +0 -1
- package/dist/chunk-B7K7V22Y.js +0 -699
- package/dist/chunk-B7K7V22Y.js.map +0 -1
- package/dist/chunk-DPEUKJRO.js +0 -27
- package/dist/chunk-DPEUKJRO.js.map +0 -1
- package/dist/chunk-FD2MBMOH.js +0 -494
- package/dist/chunk-FD2MBMOH.js.map +0 -1
- package/dist/chunk-FJI3EXU3.js +0 -2402
- package/dist/chunk-FJI3EXU3.js.map +0 -1
- package/dist/chunk-HAO4CXHP.js +0 -200
- package/dist/chunk-HAO4CXHP.js.map +0 -1
- package/dist/chunk-ISPWRSEC.js +0 -408
- package/dist/chunk-ISPWRSEC.js.map +0 -1
- package/dist/chunk-KRBFHMV6.js +0 -1228
- package/dist/chunk-KRBFHMV6.js.map +0 -1
- package/dist/chunk-L5DST3QC.js +0 -351
- package/dist/chunk-L5DST3QC.js.map +0 -1
- package/dist/chunk-M22PL4GH.js +0 -171
- package/dist/chunk-M22PL4GH.js.map +0 -1
- package/dist/chunk-NURIQBJQ.js +0 -72
- package/dist/chunk-NURIQBJQ.js.map +0 -1
- package/dist/chunk-PZZKQVQV.js +0 -52
- package/dist/chunk-PZZKQVQV.js.map +0 -1
- package/dist/chunk-Q2JSAVQ3.js +0 -671
- package/dist/chunk-Q2JSAVQ3.js.map +0 -1
- package/dist/chunk-SBTWKPVR.js +0 -5576
- package/dist/chunk-SBTWKPVR.js.map +0 -1
- package/dist/chunk-SGKPNBXE.js +0 -60
- package/dist/chunk-SGKPNBXE.js.map +0 -1
- package/dist/chunk-SNSMRT6H.js +0 -680
- package/dist/chunk-SNSMRT6H.js.map +0 -1
- package/dist/chunk-STJEHM5F.js +0 -1338
- package/dist/chunk-STJEHM5F.js.map +0 -1
- package/dist/chunk-UPWGXKXB.js +0 -254
- package/dist/chunk-UPWGXKXB.js.map +0 -1
- package/dist/chunk-YEJR7IXO.js +0 -68
- package/dist/chunk-YEJR7IXO.js.map +0 -1
- package/dist/chunk-ZXICDSAK.js +0 -907
- package/dist/chunk-ZXICDSAK.js.map +0 -1
- package/dist/completion-gate-Bv47cOnn.d.ts +0 -256
- package/dist/conversation.js.map +0 -1
- package/dist/coordination-CxtzMxSM.d.ts +0 -2570
- package/dist/environment-provider.js.map +0 -1
- package/dist/improve--2A0gK1-.d.ts +0 -440
- package/dist/improvement-adapter-HAZz-7vK.d.ts +0 -203
- package/dist/knowledge.js.map +0 -1
- package/dist/local-harness-CtK9dSny.d.ts +0 -175
- package/dist/loop-runner-bin-DUOGtvOl.d.ts +0 -207
- package/dist/loop-runner-bin.js.map +0 -1
- package/dist/loops.js.map +0 -1
- package/dist/openai-tools-fnj6SRVg.d.ts +0 -46
- package/dist/protected-model-port-DzkUY3HQ.d.ts +0 -271
- package/dist/sanitize-DEbPNtyI.d.ts +0 -328
- package/dist/structural-rollout-C4mabf87.d.ts +0 -446
- package/dist/supervise-7q9F5brx.d.ts +0 -379
- package/dist/types-BCoemcXU.d.ts +0 -802
- package/dist/types-BwoZWq-i.d.ts +0 -510
- package/dist/types-Cbp72qyu.d.ts +0 -491
- package/dist/types-CmYCMbFT.d.ts +0 -203
- package/dist/types-CvNwMzQt.d.ts +0 -957
- package/dist/worktree-fanout-WsOzt8qu.d.ts +0 -999
package/dist/index.js
CHANGED
|
@@ -1,2053 +1,2215 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "./
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "
|
|
22
|
-
import "
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
getModels,
|
|
27
|
-
resolveChatModel,
|
|
28
|
-
resolveRouterBaseUrl,
|
|
29
|
-
validateChatModelId
|
|
30
|
-
} from "./chunk-NURIQBJQ.js";
|
|
31
|
-
import {
|
|
32
|
-
CircuitBreakerState,
|
|
33
|
-
CircuitOpenError,
|
|
34
|
-
DEFAULT_MAX_DEPTH,
|
|
35
|
-
DeadlineExceededError,
|
|
36
|
-
FORWARD_HEADERS,
|
|
37
|
-
FileConversationJournal,
|
|
38
|
-
InMemoryConversationJournal,
|
|
39
|
-
SqlConversationJournal,
|
|
40
|
-
buildForwardHeaders,
|
|
41
|
-
computeBackoff,
|
|
42
|
-
createConversationBackend,
|
|
43
|
-
d1ToSqlAdapter,
|
|
44
|
-
defaultIsRetryable,
|
|
45
|
-
defineConversation,
|
|
46
|
-
isDepthExceeded,
|
|
47
|
-
makePerAttemptSignal,
|
|
48
|
-
readDepth,
|
|
49
|
-
runConversation,
|
|
50
|
-
runConversationStream,
|
|
51
|
-
runPersonaConversation,
|
|
52
|
-
runPersonaDispatch,
|
|
53
|
-
sleep,
|
|
54
|
-
slugifySpeaker,
|
|
55
|
-
turnId
|
|
56
|
-
} from "./chunk-2KGAN2HM.js";
|
|
57
|
-
import {
|
|
58
|
-
createProtectedAgentCandidateModelPort,
|
|
59
|
-
exactProcessProviderAsCandidateExecutor
|
|
60
|
-
} from "./chunk-ZXICDSAK.js";
|
|
61
|
-
import {
|
|
62
|
-
McpSpawnFault,
|
|
63
|
-
connectStdioMcp
|
|
64
|
-
} from "./chunk-ATDFUJYI.js";
|
|
65
|
-
import {
|
|
66
|
-
InMemoryRuntimeSessionStore,
|
|
67
|
-
createIterableBackend,
|
|
68
|
-
createOpenAICompatibleBackend,
|
|
69
|
-
createSandboxPromptBackend,
|
|
70
|
-
normalizeBackendStreamEvent,
|
|
71
|
-
nowIso,
|
|
72
|
-
startOrResumeRuntimeSession,
|
|
73
|
-
touchSession
|
|
74
|
-
} from "./chunk-Q2JSAVQ3.js";
|
|
75
|
-
import {
|
|
76
|
-
RESEARCH_SUPERVISOR_SYSTEM_PROMPT,
|
|
77
|
-
buildKnowledgeImprovementExperimentBundles,
|
|
78
|
-
createAgentKnowledgeReadinessCheck,
|
|
79
|
-
createKnowledgeImprovementActivationExecutor,
|
|
80
|
-
createSupervisedKnowledgeUpdater,
|
|
81
|
-
formatSupervisedKnowledgeTask,
|
|
82
|
-
knowledgeReadinessDeliverable,
|
|
83
|
-
runKnowledgeImprovementJob,
|
|
84
|
-
runSupervisedKnowledgeUpdate
|
|
85
|
-
} from "./chunk-2UABGGUB.js";
|
|
86
|
-
import "./chunk-5QOB7H74.js";
|
|
87
|
-
import {
|
|
88
|
-
captureAgentCandidateWorkspace,
|
|
89
|
-
captureAgentCandidateWorkspaceFiles,
|
|
90
|
-
createAgentCandidateWorkspacePort,
|
|
91
|
-
sealAgentCandidateBundle
|
|
92
|
-
} from "./chunk-B7K7V22Y.js";
|
|
93
|
-
import "./chunk-HAO4CXHP.js";
|
|
94
|
-
import {
|
|
95
|
-
AGENTIC_PROFILE_RESOURCE_ROOT,
|
|
96
|
-
LIFTED_FINDING_ANALYST_ID,
|
|
97
|
-
ROLLOUT_POLICY_BOUNDS,
|
|
98
|
-
ROLLOUT_POLICY_EXTENSION,
|
|
99
|
-
agenticGenerator,
|
|
100
|
-
applyRolloutPolicyToProfile,
|
|
101
|
-
commandVerifier,
|
|
102
|
-
defaultBuildPrompt,
|
|
103
|
-
enumerateNeighborPolicies,
|
|
104
|
-
improve,
|
|
105
|
-
improvementDriver,
|
|
106
|
-
isAnalystFinding,
|
|
107
|
-
normalizeRolloutPolicy,
|
|
108
|
-
parseRolloutPolicy,
|
|
109
|
-
rawTraceDistiller,
|
|
110
|
-
rawTraceEvidenceProblem,
|
|
111
|
-
requiresRawTraceEvidence,
|
|
112
|
-
rolloutPolicyProposer,
|
|
113
|
-
serializeRolloutPolicy,
|
|
114
|
-
structuralRolloutPolicyFromProfile,
|
|
115
|
-
summarizeFindings,
|
|
116
|
-
toAnalystFindings,
|
|
117
|
-
worktreeChangedPaths
|
|
118
|
-
} from "./chunk-FJI3EXU3.js";
|
|
119
|
-
import {
|
|
120
|
-
AGENT_CANDIDATE_EXECUTION_SUPPORT,
|
|
121
|
-
CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV,
|
|
122
|
-
CANDIDATE_KNOWLEDGE_ROOT_ENV,
|
|
123
|
-
CANDIDATE_TRACE_ENV,
|
|
124
|
-
CANDIDATE_TRACE_TAGS,
|
|
125
|
-
InMemoryAgentCandidateExecutionClaimStore,
|
|
126
|
-
applyExactAgentProfileDiff,
|
|
127
|
-
assertCandidateProfileBinding,
|
|
128
|
-
candidateExecutionClaim,
|
|
129
|
-
candidateKnowledgeExecutionPaths,
|
|
130
|
-
canonicalCandidateDigest,
|
|
131
|
-
executePreparedAgentCandidate,
|
|
132
|
-
parseExactAgentProfile,
|
|
133
|
-
parseExactAgentProfileDiff,
|
|
134
|
-
persistCandidateOutputArtifact,
|
|
135
|
-
prepareAgentCandidateExecution,
|
|
136
|
-
verifyAgentCandidateBundle
|
|
137
|
-
} from "./chunk-SBTWKPVR.js";
|
|
138
|
-
import "./chunk-5HNDYWUT.js";
|
|
139
|
-
import "./chunk-DPEUKJRO.js";
|
|
140
|
-
import {
|
|
141
|
-
buildDriverSystem,
|
|
142
|
-
optimizerMethod,
|
|
143
|
-
researchDriverNote,
|
|
144
|
-
strategyAuthorMethod
|
|
145
|
-
} from "./chunk-STJEHM5F.js";
|
|
146
|
-
import "./chunk-UPWGXKXB.js";
|
|
147
|
-
import {
|
|
148
|
-
INTELLIGENCE_WIRE_VERSION,
|
|
149
|
-
buildLoopOtelSpans,
|
|
150
|
-
buildLoopSpanNodes,
|
|
151
|
-
buildRuntimeEventOtelSpans,
|
|
152
|
-
createOtelExporter,
|
|
153
|
-
createRuntimeEventCollector,
|
|
154
|
-
createRuntimeStreamEventCollector,
|
|
155
|
-
exportEvalRuns,
|
|
156
|
-
loopEventToOtelSpan,
|
|
157
|
-
sanitizeAgentRuntimeEvent,
|
|
158
|
-
sanitizeKnowledgeReadinessReport,
|
|
159
|
-
sanitizeRuntimeStreamEvent
|
|
160
|
-
} from "./chunk-2VZYRB55.js";
|
|
161
|
-
import {
|
|
162
|
-
composeRuntimeHooks,
|
|
163
|
-
defineRuntimeHooks,
|
|
164
|
-
notifyRuntimeDecisionPoint,
|
|
165
|
-
notifyRuntimeHookEvent,
|
|
166
|
-
runBrainLoop,
|
|
167
|
-
runLocalHarness
|
|
168
|
-
} from "./chunk-2X65HYMI.js";
|
|
169
|
-
import "./chunk-ISPWRSEC.js";
|
|
170
|
-
import "./chunk-KRBFHMV6.js";
|
|
171
|
-
import "./chunk-3MDZX7YU.js";
|
|
172
|
-
import "./chunk-FD2MBMOH.js";
|
|
173
|
-
import "./chunk-M22PL4GH.js";
|
|
174
|
-
import {
|
|
175
|
-
AgentEvalError,
|
|
176
|
-
BackendTransportError,
|
|
177
|
-
ConfigError,
|
|
178
|
-
JudgeError,
|
|
179
|
-
NotFoundError,
|
|
180
|
-
PlannerError,
|
|
181
|
-
RuntimeRunStateError,
|
|
182
|
-
ValidationError
|
|
183
|
-
} from "./chunk-YEJR7IXO.js";
|
|
184
|
-
|
|
185
|
-
// src/durable/chat-engine.ts
|
|
186
|
-
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-Dy8NujB4.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-XucG_K3l.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-BG0BziaP.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-BwjQ1NfH.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();
|
|
187
26
|
function encodeLine(event) {
|
|
188
|
-
|
|
189
|
-
`);
|
|
27
|
+
return encoder.encode(`${JSON.stringify(event)}\n`);
|
|
190
28
|
}
|
|
191
29
|
function defaultLog(message, meta) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
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
|
+
*/
|
|
195
38
|
function handleChatTurn(input) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
*/
|
|
267
124
|
function deriveExecutionId(input) {
|
|
268
|
-
|
|
125
|
+
return `${input.projectId}:${input.sessionId}:${input.turnIndex}`;
|
|
269
126
|
}
|
|
270
|
-
|
|
271
|
-
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/improvement/build-prompts.ts
|
|
129
|
+
/** Render findings as the ranked-evidence block every build prompt ends with. */
|
|
272
130
|
function findingLines(findings) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
+
});
|
|
278
136
|
}
|
|
137
|
+
/** Build the starting instruction for a coder agent tasked with implementing a new tool. */
|
|
279
138
|
function toolBuildPrompt(args) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
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. */
|
|
303
163
|
function mcpBuildPrompt(args) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
attributes: {
|
|
369
|
-
[OPENINFERENCE_SPAN_KIND]: "AGENT",
|
|
370
|
-
"agent.name": cell.cellId
|
|
371
|
-
}
|
|
372
|
-
})
|
|
373
|
-
];
|
|
374
|
-
for (let i = 0; i < records.length; i++) {
|
|
375
|
-
const r = records[i];
|
|
376
|
-
lines.push(
|
|
377
|
-
JSON.stringify({
|
|
378
|
-
trace_id: traceId,
|
|
379
|
-
span_id: foldTo16Hex(`${key}::${i}`),
|
|
380
|
-
parent_span_id: rootSpanId,
|
|
381
|
-
name: r.name,
|
|
382
|
-
start_time: msToIso(r.startMs),
|
|
383
|
-
end_time: msToIso(r.startMs + (r.durationMs ?? 0)),
|
|
384
|
-
status: r.error === void 0 ? { code: "STATUS_CODE_OK", message: "" } : { code: "STATUS_CODE_ERROR", message: r.error },
|
|
385
|
-
resource,
|
|
386
|
-
attributes: r.attributes
|
|
387
|
-
})
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
return lines;
|
|
391
|
-
}
|
|
392
|
-
function convertCampaignDirToOtlp(dir, opts = {}) {
|
|
393
|
-
const root = resolve(dir);
|
|
394
|
-
const files = findSpansFiles(root, MAX_WALK_DEPTH);
|
|
395
|
-
const lines = [];
|
|
396
|
-
for (const file of files) {
|
|
397
|
-
let content;
|
|
398
|
-
try {
|
|
399
|
-
content = readFileSync(file, "utf8");
|
|
400
|
-
} catch {
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
const cellDir = dirname(file);
|
|
404
|
-
lines.push(
|
|
405
|
-
...campaignCellSpansToOtlp(content, { cellId: basename(cellDir), cellKey: cellDir }, opts)
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
return lines.length > 0 ? `${lines.join("\n")}
|
|
409
|
-
` : "";
|
|
410
|
-
}
|
|
411
|
-
function campaignTraceResolver(opts) {
|
|
412
|
-
return (ctx) => {
|
|
413
|
-
const priorDir = ctx.generation <= 0 ? join(opts.runDir, "baseline") : join(opts.runDir, `gen-${ctx.generation - 1}`);
|
|
414
|
-
const prior = convertCampaignDirToOtlp(priorDir, opts);
|
|
415
|
-
if (prior) return prior;
|
|
416
|
-
return convertCampaignDirToOtlp(opts.runDir, opts);
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
function parseCampaignSpans(content, cellId) {
|
|
420
|
-
const out = [];
|
|
421
|
-
for (const line of content.split("\n")) {
|
|
422
|
-
const trimmed = line.trim();
|
|
423
|
-
if (!trimmed) continue;
|
|
424
|
-
let raw;
|
|
425
|
-
try {
|
|
426
|
-
raw = JSON.parse(trimmed);
|
|
427
|
-
} catch {
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
431
|
-
const o = raw;
|
|
432
|
-
if (typeof o.name !== "string" || typeof o.startMs !== "number") continue;
|
|
433
|
-
const attributes = { "campaign.cell_id": cellId };
|
|
434
|
-
for (const [k, v] of Object.entries(o)) {
|
|
435
|
-
if (k === "name" || k === "cellId" || k === "startMs" || k === "durationMs") continue;
|
|
436
|
-
attributes[k] = v;
|
|
437
|
-
}
|
|
438
|
-
out.push({
|
|
439
|
-
name: o.name,
|
|
440
|
-
cellId,
|
|
441
|
-
startMs: o.startMs,
|
|
442
|
-
...typeof o.durationMs === "number" ? { durationMs: o.durationMs } : {},
|
|
443
|
-
attributes,
|
|
444
|
-
...typeof o.error === "string" ? { error: o.error } : {}
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
return out;
|
|
448
|
-
}
|
|
449
|
-
function findSpansFiles(dir, depth) {
|
|
450
|
-
const out = [];
|
|
451
|
-
let entries;
|
|
452
|
-
try {
|
|
453
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
454
|
-
} catch {
|
|
455
|
-
return out;
|
|
456
|
-
}
|
|
457
|
-
for (const entry of entries) {
|
|
458
|
-
const full = join(dir, entry.name);
|
|
459
|
-
if (entry.isFile() && entry.name === "spans.jsonl") {
|
|
460
|
-
out.push(full);
|
|
461
|
-
} else if (depth > 0 && entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
462
|
-
out.push(...findSpansFiles(full, depth - 1));
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return out.sort();
|
|
466
|
-
}
|
|
467
|
-
function msToIso(ms) {
|
|
468
|
-
return Number.isFinite(ms) && ms > 0 ? new Date(ms).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
469
|
-
}
|
|
470
|
-
function foldTo16Hex(s) {
|
|
471
|
-
const a = fnv1a(s);
|
|
472
|
-
const b = fnv1a(`${s}::salt`);
|
|
473
|
-
return a + b;
|
|
474
|
-
}
|
|
475
|
-
function foldTo32Hex(s) {
|
|
476
|
-
return foldTo16Hex(s) + foldTo16Hex(`${s}::trace`);
|
|
477
|
-
}
|
|
478
|
-
function fnv1a(s) {
|
|
479
|
-
let h = 2166136261;
|
|
480
|
-
for (let i = 0; i < s.length; i++) {
|
|
481
|
-
h ^= s.charCodeAt(i);
|
|
482
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
483
|
-
}
|
|
484
|
-
return h.toString(16).padStart(8, "0");
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// src/improvement/driver-loop-generator.ts
|
|
488
|
-
import { spawnSync } from "child_process";
|
|
489
|
-
import { readFileSync as readFileSync2, statSync } from "fs";
|
|
490
|
-
import { resolve as resolve2, sep } from "path";
|
|
491
|
-
var workerOutputTailChars = 2e3;
|
|
492
|
-
var diffMaxChars = 6e3;
|
|
493
|
-
var readFileDefaultBytes = 8192;
|
|
494
|
-
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. */
|
|
495
228
|
function driverLoopGenerator(opts) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
+
}
|
|
672
416
|
];
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
+
}
|
|
686
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. */
|
|
687
436
|
function worktreeDiff(worktreePath) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
return result.stdout;
|
|
700
|
-
}
|
|
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). */
|
|
701
447
|
function readWorktreeFile(worktreePath, args) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
} catch (e) {
|
|
716
|
-
return `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
717
|
-
}
|
|
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
|
+
}
|
|
718
461
|
}
|
|
719
462
|
function tail(s, n) {
|
|
720
|
-
|
|
721
|
-
|
|
463
|
+
const trimmed = s.trim();
|
|
464
|
+
return trimmed.length <= n ? trimmed : `…${trimmed.slice(-n)}`;
|
|
722
465
|
}
|
|
723
466
|
function truncate(s, n) {
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
|
|
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. */
|
|
728
490
|
function mcpServeVerifier(spec) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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. */
|
|
529
|
+
var OfficialOptimizerUnavailableError = class extends ConfigError {
|
|
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
|
+
}
|
|
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
|
+
*/
|
|
554
|
+
function officialGepa(options) {
|
|
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. */
|
|
589
|
+
function officialSkillOpt(options) {
|
|
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
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function assertMaxFindingsChars(label, value) {
|
|
624
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new ConfigError(`${label}: maxFindingsChars must be a positive safe integer`);
|
|
625
|
+
}
|
|
626
|
+
function methodBackground(options) {
|
|
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");
|
|
642
|
+
}
|
|
643
|
+
function assertSafeOptimizerCandidate(label, input, authorizeSensitiveCandidate) {
|
|
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.`);
|
|
659
|
+
}
|
|
660
|
+
function sensitiveProfileSurfacePaths(input) {
|
|
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];
|
|
691
|
+
}
|
|
692
|
+
function redactOptimizerEvidence(label, field, value, redactor) {
|
|
693
|
+
try {
|
|
694
|
+
return redactor(value);
|
|
695
|
+
} catch (cause) {
|
|
696
|
+
throw new ConfigError(`${label}: ${field} redaction failed`, { cause });
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function redactOptimizerText(label, field, value, redactor) {
|
|
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;
|
|
703
|
+
}
|
|
704
|
+
function redactJudgeScore(score, redactor) {
|
|
705
|
+
const notes = redactOptimizerEvidence("official optimizer", "judge notes", score.notes, redactor);
|
|
706
|
+
return {
|
|
707
|
+
...score,
|
|
708
|
+
notes: typeof notes === "string" ? notes : "[redacted]"
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function optimizerRedactionPolicyRef(redact, builtInIdentity = defaultRedactorIdentityMaterial()) {
|
|
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
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function optimizerEvidencePolicyRef(input) {
|
|
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
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function callbackSource(callback) {
|
|
732
|
+
return typeof callback === "function" ? Function.prototype.toString.call(callback) : null;
|
|
733
|
+
}
|
|
734
|
+
function withDependencyHelp(optimizer, evaluationRef, redactor, redactionPolicyRef, method) {
|
|
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
|
+
};
|
|
762
|
+
}
|
|
763
|
+
function isMissingDependency(optimizer, cause) {
|
|
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. */
|
|
826
800
|
function reflectiveGenerator(opts) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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). */
|
|
842
824
|
function applyPatch(patch, cwd) {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
+
*/
|
|
853
844
|
function decideKnowledgeReadiness(report, options = {}) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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
|
+
*/
|
|
899
918
|
function resolveAgentBackend(opts) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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. */
|
|
939
964
|
function asFailureClass(value) {
|
|
940
|
-
|
|
941
|
-
}
|
|
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. */
|
|
942
974
|
function applyRunRecordDefaults(records, scenarioId, controlFailureClass) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
+
*/
|
|
951
994
|
async function runAgentTask(options) {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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
|
+
*/
|
|
1043
1118
|
async function* runAgentTaskStream(options) {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
+
}
|
|
1188
1301
|
}
|
|
1189
1302
|
async function runKnowledgePreflight(task, questions, acquisitionPlans, provider, onEvent) {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
+
};
|
|
1208
1337
|
}
|
|
1209
1338
|
async function runKnowledgePreflightStream(task, questions, acquisitionPlans, provider) {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
+
};
|
|
1226
1375
|
}
|
|
1227
1376
|
function streamEvent(event) {
|
|
1228
|
-
|
|
1377
|
+
return {
|
|
1378
|
+
...event,
|
|
1379
|
+
timestamp: nowIso()
|
|
1380
|
+
};
|
|
1229
1381
|
}
|
|
1230
1382
|
function buildReadiness(task, provider) {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
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
|
+
});
|
|
1237
1392
|
}
|
|
1238
1393
|
function isKnowledgeBlocked(evals) {
|
|
1239
|
-
|
|
1394
|
+
return evals.some((evalResult) => evalResult.id === "knowledge-ready" && !evalResult.passed);
|
|
1240
1395
|
}
|
|
1241
1396
|
function statusFromControl(control) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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";
|
|
1246
1401
|
}
|
|
1247
1402
|
async function emit(sink, event) {
|
|
1248
|
-
|
|
1403
|
+
await sink?.(event);
|
|
1249
1404
|
}
|
|
1250
1405
|
function toAgentContext(task, knowledge, ctx) {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
+
*/
|
|
1267
1455
|
function startRuntimeRun(options) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
ledger.tokensIn = input.cost.tokensIn;
|
|
1353
|
-
}
|
|
1354
|
-
if (typeof input.cost.tokensOut === "number" && Number.isFinite(input.cost.tokensOut)) {
|
|
1355
|
-
ledger.tokensOut = input.cost.tokensOut;
|
|
1356
|
-
}
|
|
1357
|
-
if (typeof input.cost.costUsd === "number" && Number.isFinite(input.cost.costUsd)) {
|
|
1358
|
-
ledger.costUsd = input.cost.costUsd;
|
|
1359
|
-
}
|
|
1360
|
-
if (typeof input.cost.llmCalls === "number" && Number.isFinite(input.cost.llmCalls)) {
|
|
1361
|
-
ledger.llmCalls = input.cost.llmCalls;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
},
|
|
1365
|
-
toRow(metadata) {
|
|
1366
|
-
return buildRow(metadata);
|
|
1367
|
-
},
|
|
1368
|
-
async persist(metadata) {
|
|
1369
|
-
if (status === "running") {
|
|
1370
|
-
throw new RuntimeRunStateError("Cannot persist a runtime run before complete() is called");
|
|
1371
|
-
}
|
|
1372
|
-
if (!options.adapter) return;
|
|
1373
|
-
await options.adapter.upsert(buildRow(metadata));
|
|
1374
|
-
}
|
|
1375
|
-
};
|
|
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
|
+
};
|
|
1376
1540
|
}
|
|
1377
1541
|
function mergeMetadata(base, extra) {
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
|
|
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 */
|
|
1386
1554
|
function encodeServerSentEvent(data, options = {}) {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1397
|
-
return `${lines.join("\n")}
|
|
1398
|
-
|
|
1399
|
-
`;
|
|
1400
|
-
}
|
|
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 */
|
|
1401
1564
|
function readinessServerSentEvent(report, options = {}) {
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
}
|
|
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 */
|
|
1411
1576
|
function runtimeStreamServerSentEvent(event, options = {}) {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1577
|
+
const { event: sseEvent, id, retry, ...telemetryOptions } = options;
|
|
1578
|
+
return encodeServerSentEvent(sanitizeRuntimeStreamEvent(event, telemetryOptions), {
|
|
1579
|
+
event: sseEvent,
|
|
1580
|
+
id,
|
|
1581
|
+
retry
|
|
1582
|
+
});
|
|
1418
1583
|
}
|
|
1419
1584
|
function stripNewlines(value) {
|
|
1420
|
-
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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. */
|
|
1428
1606
|
function toolCallId(call) {
|
|
1429
|
-
|
|
1607
|
+
return call.toolCallId ?? `call_${call.toolName}`;
|
|
1430
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. */
|
|
1431
1611
|
function assistantToolCallMessage(turnText, pending) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
}
|
|
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`. */
|
|
1442
1626
|
function toolResultMessage(call, content) {
|
|
1443
|
-
|
|
1627
|
+
return {
|
|
1628
|
+
role: "tool",
|
|
1629
|
+
tool_call_id: toolCallId(call),
|
|
1630
|
+
content
|
|
1631
|
+
};
|
|
1444
1632
|
}
|
|
1445
1633
|
function defaultRender(label, outcome) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1634
|
+
if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`;
|
|
1635
|
+
return `- ${label} → failed (${outcome.code}): ${outcome.message}`;
|
|
1448
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}. */
|
|
1449
1640
|
async function runToolLoop(opts) {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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. */
|
|
1569
1827
|
async function* streamToolLoop(opts) {
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
+
}
|
|
1694
1996
|
}
|
|
1695
1997
|
function createToolLoopObserver(hooks, runId, scenarioId) {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
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
|
+
};
|
|
1788
2096
|
}
|
|
1789
2097
|
function notifyToolLoopEvent(options) {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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
|
+
});
|
|
1802
2113
|
}
|
|
1803
2114
|
function notifyToolFailureRecovery(options) {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
+
});
|
|
1838
2152
|
}
|
|
1839
2153
|
function toolCallPayload(call) {
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2154
|
+
return {
|
|
2155
|
+
toolName: call.toolName,
|
|
2156
|
+
toolCallId: call.toolCallId,
|
|
2157
|
+
argsPreview: stringifySafe(call.args, 2e3)
|
|
2158
|
+
};
|
|
1845
2159
|
}
|
|
1846
2160
|
function outcomePayload(outcome) {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
resultPreview: stringifySafe(outcome.result, 2e3)
|
|
1858
|
-
};
|
|
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
|
+
};
|
|
1859
2171
|
}
|
|
1860
2172
|
function failureMetadata(outcome) {
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
2173
|
+
if (outcome.ok) return void 0;
|
|
2174
|
+
return {
|
|
2175
|
+
code: outcome.code,
|
|
2176
|
+
message: outcome.message,
|
|
2177
|
+
status: outcome.status
|
|
2178
|
+
};
|
|
1867
2179
|
}
|
|
1868
2180
|
function renderDecisionContext(messages, turnText, outcomes) {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
}
|
|
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. */
|
|
1880
2192
|
function canonicalCallHash(call) {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
);
|
|
1884
|
-
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)}`;
|
|
1885
2195
|
}
|
|
1886
2196
|
function stringifySafe(value, max) {
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2197
|
+
let text;
|
|
2198
|
+
try {
|
|
2199
|
+
text = JSON.stringify(value) ?? String(value);
|
|
2200
|
+
} catch {
|
|
2201
|
+
text = String(value);
|
|
2202
|
+
}
|
|
2203
|
+
return trimText(text, max);
|
|
1894
2204
|
}
|
|
1895
2205
|
function trimText(text, max) {
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
}
|
|
1899
|
-
function
|
|
1900
|
-
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
AgentEvalError,
|
|
1906
|
-
BackendTransportError,
|
|
1907
|
-
CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV,
|
|
1908
|
-
CANDIDATE_KNOWLEDGE_ROOT_ENV,
|
|
1909
|
-
CANDIDATE_TRACE_ENV,
|
|
1910
|
-
CANDIDATE_TRACE_TAGS,
|
|
1911
|
-
CircuitBreakerState,
|
|
1912
|
-
CircuitOpenError,
|
|
1913
|
-
ConfigError,
|
|
1914
|
-
DEFAULT_MAX_DEPTH,
|
|
1915
|
-
DEFAULT_ROUTER_BASE_URL,
|
|
1916
|
-
DELEGATED_LOOP_MODES,
|
|
1917
|
-
DeadlineExceededError,
|
|
1918
|
-
FORWARD_HEADERS,
|
|
1919
|
-
FileAgentCandidateExecutionClaimStore,
|
|
1920
|
-
FileConversationJournal,
|
|
1921
|
-
INTELLIGENCE_WIRE_VERSION,
|
|
1922
|
-
InMemoryAgentCandidateExecutionClaimStore,
|
|
1923
|
-
InMemoryConversationJournal,
|
|
1924
|
-
InMemoryRuntimeSessionStore,
|
|
1925
|
-
JudgeError,
|
|
1926
|
-
LIFTED_FINDING_ANALYST_ID,
|
|
1927
|
-
NotFoundError,
|
|
1928
|
-
PlannerError,
|
|
1929
|
-
RESEARCH_SUPERVISOR_SYSTEM_PROMPT,
|
|
1930
|
-
ROLLOUT_POLICY_BOUNDS,
|
|
1931
|
-
ROLLOUT_POLICY_EXTENSION,
|
|
1932
|
-
RuntimeRunStateError,
|
|
1933
|
-
SqlConversationJournal,
|
|
1934
|
-
ValidationError,
|
|
1935
|
-
agenticGenerator,
|
|
1936
|
-
applyExactAgentProfileDiff,
|
|
1937
|
-
applyRolloutPolicyToProfile,
|
|
1938
|
-
applyRunRecordDefaults,
|
|
1939
|
-
assertCandidateProfileBinding,
|
|
1940
|
-
auditLoopRunner,
|
|
1941
|
-
buildAgentCandidateBundle,
|
|
1942
|
-
buildDriverSystem,
|
|
1943
|
-
buildForwardHeaders,
|
|
1944
|
-
buildKnowledgeImprovementExperimentBundles,
|
|
1945
|
-
buildLoopOtelSpans,
|
|
1946
|
-
buildLoopSpanNodes,
|
|
1947
|
-
buildRuntimeEventOtelSpans,
|
|
1948
|
-
campaignCellSpansToOtlp,
|
|
1949
|
-
campaignTraceResolver,
|
|
1950
|
-
candidateExecutionClaim,
|
|
1951
|
-
candidateKnowledgeExecutionPaths,
|
|
1952
|
-
captureAgentCandidateWorkspace,
|
|
1953
|
-
captureAgentCandidateWorkspaceFiles,
|
|
1954
|
-
cleanModelId,
|
|
1955
|
-
commandVerifier,
|
|
1956
|
-
composeRuntimeHooks,
|
|
1957
|
-
computeBackoff,
|
|
1958
|
-
convertCampaignDirToOtlp,
|
|
1959
|
-
createAgentCandidateWorkspacePort,
|
|
1960
|
-
createAgentKnowledgeReadinessCheck,
|
|
1961
|
-
createConversationBackend,
|
|
1962
|
-
createIterableBackend,
|
|
1963
|
-
createKnowledgeImprovementActivationExecutor,
|
|
1964
|
-
createOpenAICompatibleBackend,
|
|
1965
|
-
createOtelExporter,
|
|
1966
|
-
createProtectedAgentCandidateModelPort,
|
|
1967
|
-
createRuntimeEventCollector,
|
|
1968
|
-
createRuntimeStreamEventCollector,
|
|
1969
|
-
createSandboxPromptBackend,
|
|
1970
|
-
createSupervisedKnowledgeUpdater,
|
|
1971
|
-
d1ToSqlAdapter,
|
|
1972
|
-
decideKnowledgeReadiness,
|
|
1973
|
-
defaultBuildPrompt,
|
|
1974
|
-
defaultIsRetryable,
|
|
1975
|
-
defineConversation,
|
|
1976
|
-
defineRuntimeHooks,
|
|
1977
|
-
deriveExecutionId,
|
|
1978
|
-
disposePreparedAgentCandidateExecution,
|
|
1979
|
-
driverLoopGenerator,
|
|
1980
|
-
enumerateNeighborPolicies,
|
|
1981
|
-
exactProcessProviderAsCandidateExecutor,
|
|
1982
|
-
executePreparedAgentCandidate,
|
|
1983
|
-
exportEvalRuns,
|
|
1984
|
-
findingLines,
|
|
1985
|
-
formatSupervisedKnowledgeTask,
|
|
1986
|
-
getModels,
|
|
1987
|
-
handleChatTurn,
|
|
1988
|
-
improve,
|
|
1989
|
-
improvementDriver,
|
|
1990
|
-
isAnalystFinding,
|
|
1991
|
-
isDelegatedLoopMode,
|
|
1992
|
-
isDepthExceeded,
|
|
1993
|
-
knowledgeReadinessDeliverable,
|
|
1994
|
-
loopEventToOtelSpan,
|
|
1995
|
-
makePerAttemptSignal,
|
|
1996
|
-
mcpBuildPrompt,
|
|
1997
|
-
mcpServeVerifier,
|
|
1998
|
-
mcpToolsForRuntimeMcp,
|
|
1999
|
-
mcpToolsForRuntimeMcpSubset,
|
|
2000
|
-
normalizeRolloutPolicy,
|
|
2001
|
-
notifyRuntimeDecisionPoint,
|
|
2002
|
-
notifyRuntimeHookEvent,
|
|
2003
|
-
optimizerMethod,
|
|
2004
|
-
parseExactAgentProfile,
|
|
2005
|
-
parseExactAgentProfileDiff,
|
|
2006
|
-
parseLoopRunnerArgv,
|
|
2007
|
-
parseRolloutPolicy,
|
|
2008
|
-
persistCandidateOutputArtifact,
|
|
2009
|
-
prepareAgentCandidateExecution,
|
|
2010
|
-
profileDiffProposer,
|
|
2011
|
-
rawTraceDistiller,
|
|
2012
|
-
readDepth,
|
|
2013
|
-
readinessServerSentEvent,
|
|
2014
|
-
recoverExpiredAgentCandidateExecution,
|
|
2015
|
-
reflectiveGenerator,
|
|
2016
|
-
researchDriverNote,
|
|
2017
|
-
researchLoopRunner,
|
|
2018
|
-
resolveAgentBackend,
|
|
2019
|
-
resolveChatModel,
|
|
2020
|
-
resolveRouterBaseUrl,
|
|
2021
|
-
rolloutPolicyProposer,
|
|
2022
|
-
runAgentTask,
|
|
2023
|
-
runAgentTaskStream,
|
|
2024
|
-
runConversation,
|
|
2025
|
-
runConversationStream,
|
|
2026
|
-
runDelegatedLoop,
|
|
2027
|
-
runKnowledgeImprovementJob,
|
|
2028
|
-
runLoopRunnerCli,
|
|
2029
|
-
runPersonaConversation,
|
|
2030
|
-
runPersonaDispatch,
|
|
2031
|
-
runSupervisedKnowledgeUpdate,
|
|
2032
|
-
runToolLoop,
|
|
2033
|
-
runtimeStreamServerSentEvent,
|
|
2034
|
-
sanitizeAgentRuntimeEvent,
|
|
2035
|
-
sanitizeKnowledgeReadinessReport,
|
|
2036
|
-
sanitizeRuntimeStreamEvent,
|
|
2037
|
-
sealAgentCandidateBundle,
|
|
2038
|
-
selfImproveLoopRunner,
|
|
2039
|
-
serializeRolloutPolicy,
|
|
2040
|
-
sleep,
|
|
2041
|
-
slugifySpeaker,
|
|
2042
|
-
startRuntimeRun,
|
|
2043
|
-
strategyAuthorMethod,
|
|
2044
|
-
streamToolLoop,
|
|
2045
|
-
structuralRolloutPolicyFromProfile,
|
|
2046
|
-
toAnalystFindings,
|
|
2047
|
-
toolBuildPrompt,
|
|
2048
|
-
turnId,
|
|
2049
|
-
validateChatModelId,
|
|
2050
|
-
verifyAgentCandidateBundle,
|
|
2051
|
-
worktreeLoopRunner
|
|
2052
|
-
};
|
|
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
|
+
|
|
2053
2215
|
//# sourceMappingURL=index.js.map
|