chainlesschain 0.162.198 → 0.163.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/bin/chainlesschain.js +14 -6
- package/package.json +5 -1
- package/scripts/command-lifecycle-report.mjs +94 -0
- package/scripts/postinstall.mjs +19 -3
- package/scripts/repair-node-pty.mjs +74 -0
- package/src/assets/web-panel/assets/{AIOps-CSN-D2xb.js → AIOps-CSy9BB6Z.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CBSMlGzv.js → ActionButton-CFyrEVfq.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-3yr3Nt2t.js → Analytics-8419vb47.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-1b-o3lMH.js → AppLayout-CHu3Hkdx.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dr8rJi17.js → Artifacts-DJnt57Fs.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-2V689HC2.js → Audit-CHMq9ajE.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DIxkNrD_.js → BackgroundAgents-Cyfa4ub2.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Pg6BcwrE.js → Backup-ACXUrUv7.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CLUpqyfH.js → BaseInput-DjC-3qRE.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-qVS8rD4G.js → Chat-7_VDQ851.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-ti49X12Q.js → ChatBubbleRenderer-B4zct0dY.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Bs9AaEay.js → Checkbox-DxbDl_rk.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dvc1mq8q.js → Codegen-BvZa5854.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C4_biO7A.js → Col-CX5Ow2Zl.js} +1 -1
- package/src/assets/web-panel/assets/{Community-al25XQUV.js → Community-wFun-jSg.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-D-ZDB8M1.js → Compact-C6vJDuSn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-6lIfDz1W.js → Compliance-Cm4sBytH.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DdI48XMJ.js → Cowork-DiEcMwE8.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DF5HZd2g.js → Cron-RbeexsSQ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DuxeSvt3.js → Crosschain-BBsyCKOp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B9yHkUTd.js → DID-DE36g7Hz.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Z5EuI3IM.js → Dashboard-CdfLSTz1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-vuRfO3dY.js → Dropdown-CyaT6SyW.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-QFLCc1uL.js → EmailListRenderer-Nv_66bkM.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZ7EXkFJ.js → FamilyGuardDashboard-Di-Sb-Ad.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DycIEg2s.js → Federation-B3mdht-U.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Ba0H_7yG.js → FormItemContext-BeP93u4f.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer--JgSTK-C.js → GenericCardRenderer-CETLayUr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DEvTbldE.js → Git-Bsat6hgI.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJIljy18.js → Governance-YVmA7SwN.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CVyW3G3q.js → Inference-DCa94nGl.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-v8-borge.js → KnowledgeGraph-CIKEU7T0.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BCdS7gGo.js → Logs-DUxP0MnS.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-CU-JB2Ns.js → MarkdownRenderer-Mw5o3e7M.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-AN6tGA2B.js → Marketplace-BU0X2lqu.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DGViXASN.js → McpTools-BSsg58nZ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Bpvmx9X4.js → Memory-CjqNIXlO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CTP-DOTC.js → MobileBridge-DSsa4doG.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-DLl1qzid.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-ndYr0M1s.js → Mtc-Du080G7t.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Ci0-YK7W.js → MtcAudit-JzQk-arw.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BehSTFW7.js → Multisig-BMVT5Uln.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-B91_YBLv.js → NLProgramming-D_38QYjs.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BQlJ0-gq.js → Notes-DvbdjNm5.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CDgYMjYK.js → NotificationSettings-DVHCW6ii.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BJkSlKeH.js → OrderTableRenderer-DmMBFD3g.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B-b3cWCp.js → Organization-DC-9pAmP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CILUL4nL.js → Overflow-BRxWq0Qq.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTCFBkF0.js → P2P-CUdzhyLH.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BAR6AUx5.js → PdhVaultBrowser-CwG21s9P.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-RRf6HbWJ.js → Permissions-6bHd4zgK.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BGTMiICa.js → PersonalDataHub-BXgTDCba.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-945qY9YF.js → Pipeline-BFhKCBNj.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DPZwI6lJ.js → Privacy-Byz9IqUF.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BPH2NRKc.js → ProjectInit-B9LNoyoN.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BZCRNCvb.js → ProjectSettings-DEj4eVdl.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BTI__tBt.js → Projects-BAAvus06.js} +1 -1
- package/src/assets/web-panel/assets/Providers-Bu_pQsk6.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-N6rbqt1r.js → QrScannerModal-CeDey0SJ.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DrwjPS7M.js → QuickAsk-exUvRIgX.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cog9Wd99.js → Recommend-4hJWmtSI.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DC9pynjM.js → RemoteSession-BmYUpl0m.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D4th4gKS.js → Reputation-B_txYAyY.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ogiWR6v9.js → Row-BPtqOqGh.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-uYlk-2Oj.js → RssFeed-CyWTxmq0.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DYzzwvtM.js → Search-cItU1tmC.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Da7YOyX6.js → Security-B0Z9_BUt.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CLI1VM1X.js → Services-Dzpz2uFp.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DK77dzDC.js → Skeleton-CBtoylP8.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C_z7Lmhl.js → Skills-Cw9RJiVn.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D6413Q8L.js → Sla-D2vLfvgl.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C07ydbj2.js → SpeechSettings-I9dKbcOU.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DGOm2W22.js → SyncSettings-BOju53RT.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cr_PVyLM.js → Tasks-C3LBy_7x.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BhL2mZ0t.js → Templates-C2ttpPql.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CPk5oyUr.js → Tenant-XQ6Qg8-Q.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ClhqUsqV.js → Terminal-DYB9dUnS.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BCC7MV5a.js → TimelineRenderer-gShtO4I8.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DD1Jt4oO.js → Tokens-BvoJTCJg.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BCydFvN2.js → Trigger-BwYRTMY_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DDHT1noa.js → Trust-C5rt6fCF.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CWPZC7oI.js → UkeySign-DPBrHUgL.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9Ea0nJR0.js → VideoEditing-BcxywO_K.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BjxhLxTf.js → Wallet-CZvTpsPz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C6uCye67.js → WebAuthn-LI_jAuOx.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-d2g4reYo.js → WorkflowEditor-B2a6bANB.js} +1 -1
- package/src/assets/web-panel/assets/{chat-frTc6ScX.js → chat-B6DPMfRB.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B5K6lG3v.js → colors-IpLhw9T8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cdn46r8D.js → compact-item-BzodimsI.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DU1jqVQi.js → createContext-DL2jaWH5.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CZB0EDsa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DdNH-aW_.js → hasIn-C4Geudre.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHmkkdfT.js → index-B5Sn7CtW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj6P9Nst.js → index-B6w4fpSI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQorP1Ng.js → index-BRj6YKuI.js} +1 -1
- package/src/assets/web-panel/assets/{index-cYaWV4Mq.js → index-BX4pKudW.js} +1 -1
- package/src/assets/web-panel/assets/{index-kVO_MDm-.js → index-BaTvPxY0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23xGYjM.js → index-BbWRxWM2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Be4OtL-N.js → index-BeZhISVl.js} +1 -1
- package/src/assets/web-panel/assets/{index-32ajXmTb.js → index-Bi6Zkv5M.js} +1 -1
- package/src/assets/web-panel/assets/{index-NmEXVjzt.js → index-BvKoSvko.js} +1 -1
- package/src/assets/web-panel/assets/{index-7ehytj1K.js → index-C3LzRvkw.js} +1 -1
- package/src/assets/web-panel/assets/{index-gg3QPC3i.js → index-CAjyUjZl.js} +3 -3
- package/src/assets/web-panel/assets/index-CI7k8Sk9.js +1 -0
- package/src/assets/web-panel/assets/{index-CGiwfwB9.js → index-COxGCo8v.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIVyTmZ3.js → index-CQBPhJlk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUoNAmE8.js → index-CQPOCB4h.js} +1 -1
- package/src/assets/web-panel/assets/{index-t_OBzIP4.js → index-CTlmw_cr.js} +1 -1
- package/src/assets/web-panel/assets/{index-VDM2KKo9.js → index-CVFXYmUe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DftZuyA7.js → index-C_SxaIPI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGwGIaR.js → index-CaKKxqCI.js} +1 -1
- package/src/assets/web-panel/assets/{index-HjCvXHf5.js → index-CcgoKNXN.js} +1 -1
- package/src/assets/web-panel/assets/{index-mgU3Q-lN.js → index-CczBR6Du.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM5FmPZU.js → index-CeUnSSEP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4l82Me1.js → index-CnwHp5Oi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwlIxAmX.js → index-DGBRBg3D.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVLL-mq_.js → index-DHdD0Y7h.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXKhhx9V.js → index-DMY1waPC.js} +1 -1
- package/src/assets/web-panel/assets/index-DPTHBSR8.js +1 -0
- package/src/assets/web-panel/assets/{index-LaivuXyF.js → index-DREx4gaS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtRPi4YC.js → index-DXQ2OtXt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzBBdQ0.js → index-DZU7Y67X.js} +1 -1
- package/src/assets/web-panel/assets/{index-PjAS0sIZ.js → index-DZtKnnTW.js} +1 -1
- package/src/assets/web-panel/assets/{index-MM4Zz2OE.js → index-DlABpgCv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoFak7le.js → index-Dl_QAtzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1qIC4GR.js → index-DnBybYhx.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4miNkWv.js → index-DoyIjk6r.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtBCCkC4.js → index-DvEnrn8M.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC4eGkar.js → index-DvOzjf2F.js} +1 -1
- package/src/assets/web-panel/assets/{index-yZRuZpwK.js → index-EjW1R_a7.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dca_i7H8.js → index-F-SCUTee.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DwLTRNdo.js → initDefaultProps-CW5mTl3w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BchZDXnh.js → motion-BtjIT9np.js} +1 -1
- package/src/assets/web-panel/assets/{move-e8M-zZFl.js → move-DZv4fZAV.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Ci40fQb3.js → mtc-parser-BwCix1O-.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C7110_f2.js → omit-BW72HDUD.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-skteMgnM.js → pickAttrs-DBW2jcMc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-MGZ-lh2B.js → placementArrow-D3YmWlIS.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-N4Sd7uy-.js → responsiveObserve-CL4-a-9Q.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C7HDGRaa.js → slide-Cw9zIbXQ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BhRexlgy.js → statusUtils-mMLVioo-.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-ChEoQkN2.js → styleChecker-BK8857AH.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CDbG1s2Y.js → useFlexGapSupport-C1iSaJij.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUIx0tcJ.js → useFs-D3riGOaP.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-bkO7L7ec.js → usePersonalDataHub-Bwqvmz4X.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B_ZvABIG.js → vnode-Byk4zzkf.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D5shjpS8.js → zoom-D7INFTp2.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/mcp.js +119 -0
- package/src/commands/session-show.js +8 -10
- package/src/commands/session.js +60 -52
- package/src/data/changelog.json +40 -1
- package/src/gateways/ws/session-protocol.js +14 -4
- package/src/gateways/ws/ws-agent-handler.js +31 -0
- package/src/harness/jsonl-session-store.js +1147 -130
- package/src/harness/mcp-client.js +44 -11
- package/src/harness/session-index.js +80 -80
- package/src/harness/session-list-index.js +147 -2
- package/src/lib/background-interaction-journal.js +2 -2
- package/src/lib/command-lifecycle-report.js +516 -0
- package/src/lib/mcp-stdio-environment.js +48 -0
- package/src/lib/mcp-stdio-executable-identity.js +552 -17
- package/src/lib/mcp-stdio-package-materialization.js +1424 -0
- package/src/lib/packer/native-update-state.js +979 -1
- package/src/lib/packer/pack-update-applier.js +1050 -125
- package/src/lib/packer/pkg-config-generator.js +1 -0
- package/src/lib/paths.js +53 -0
- package/src/lib/process-execution-broker/index.js +180 -37
- package/src/lib/process-execution-broker/platform-sandbox.js +291 -24
- package/src/lib/recent-session.js +17 -17
- package/src/lib/secure-file-identity.js +47 -0
- package/src/lib/secure-fs.js +21 -2
- package/src/lib/session-anti-rollback-anchor.js +766 -0
- package/src/lib/session-host-lease.js +492 -0
- package/src/lib/session-persistence-failure.js +109 -0
- package/src/lib/session-picker.js +26 -3
- package/src/lib/session-search.js +25 -11
- package/src/lib/side-effect-ledger-store.js +2 -2
- package/src/lib/skill-execution-authority.js +13 -9
- package/src/lib/turn-binding-store.js +2 -2
- package/src/repl/agent-repl.js +309 -39
- package/src/runtime/agent-core.js +88 -5
- package/src/runtime/cleanup-deadline.js +143 -0
- package/src/runtime/headless-runner.js +357 -139
- package/src/runtime/headless-stream.js +207 -28
- package/src/runtime/output-backpressure.js +53 -0
- package/src/assets/web-panel/assets/MobileProjects-C8qfktxn.js +0 -1
- package/src/assets/web-panel/assets/Providers-DDCf_nIw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +0 -1
- package/src/assets/web-panel/assets/index-CzrCHrFy.js +0 -1
- package/src/assets/web-panel/assets/index-OJsGskZw.js +0 -1
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
rebuildMessages as jsonlRebuildMessages,
|
|
55
55
|
sessionExists as jsonlSessionExists,
|
|
56
56
|
sessionHasPersistedEvidence as jsonlSessionHasPersistedEvidence,
|
|
57
|
+
resolveSessionAuthority as jsonlResolveSessionAuthority,
|
|
57
58
|
getLastSessionId as jsonlGetLastSessionId,
|
|
58
59
|
verifySession as jsonlVerifySession,
|
|
59
60
|
} from "../harness/jsonl-session-store.js";
|
|
@@ -72,6 +73,7 @@ import {
|
|
|
72
73
|
} from "../lib/mcp-call-ledger-store.js";
|
|
73
74
|
import { createMcpHostRecoveryRuntime } from "../lib/mcp-host-recovery-runtime.js";
|
|
74
75
|
import { readSessionHostResumeState } from "../lib/session-host-snapshot.js";
|
|
76
|
+
import { acquireSessionHostLease } from "../lib/session-host-lease.js";
|
|
75
77
|
import { TurnBindingLog, createTurnBindingFeed } from "../lib/turn-binding.js";
|
|
76
78
|
import { TURN_BINDING_EVENT } from "../lib/turn-binding-store.js";
|
|
77
79
|
import { operationIdempotencyKey } from "../lib/idempotency.js";
|
|
@@ -155,6 +157,14 @@ const VALID_OUTPUT_FORMATS = Object.freeze(["text", "json", "stream-json"]);
|
|
|
155
157
|
export { installPipeSafety } from "./pipe-safety.js";
|
|
156
158
|
import { installPipeSafety } from "./pipe-safety.js";
|
|
157
159
|
import { createHeadlessOutputBackpressure } from "./output-backpressure.js";
|
|
160
|
+
import {
|
|
161
|
+
cleanupDeadlineError,
|
|
162
|
+
createCleanupDeadline,
|
|
163
|
+
} from "./cleanup-deadline.js";
|
|
164
|
+
import {
|
|
165
|
+
createSessionPersistenceFailure,
|
|
166
|
+
projectSessionPersistenceFailure,
|
|
167
|
+
} from "../lib/session-persistence-failure.js";
|
|
158
168
|
|
|
159
169
|
/**
|
|
160
170
|
* Normalize a public --permission-mode spelling to the canonical internal mode.
|
|
@@ -290,6 +300,19 @@ export function resolveHeadlessSession(options = {}, store = {}, fallbackId) {
|
|
|
290
300
|
} else if (typeof resume === "string" && resume.trim()) {
|
|
291
301
|
resumeId = resume.trim();
|
|
292
302
|
}
|
|
303
|
+
if (resumeId && typeof store.resolveSessionAuthority === "function") {
|
|
304
|
+
const authority = store.resolveSessionAuthority(resumeId);
|
|
305
|
+
if (authority && !authority.readable) {
|
|
306
|
+
const error = new Error(
|
|
307
|
+
`Canonical session ${authority.id} is not resumable (${authority.presence})`,
|
|
308
|
+
);
|
|
309
|
+
error.code = "SESSION_CANONICAL_UNAVAILABLE";
|
|
310
|
+
error.sessionId = authority.id;
|
|
311
|
+
error.presence = authority.presence;
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
if (authority?.id) resumeId = authority.id;
|
|
315
|
+
}
|
|
293
316
|
const persist =
|
|
294
317
|
ephemeral === true
|
|
295
318
|
? false
|
|
@@ -363,6 +386,67 @@ export function applyForkSession(opts = {}, store = {}) {
|
|
|
363
386
|
* @param {object} [deps] Injection seam for tests.
|
|
364
387
|
* @returns {Promise<{ exitCode:number, result:string, isError:boolean, sessionSnapshot?:object }>}
|
|
365
388
|
*/
|
|
389
|
+
const HEADLESS_SESSION_RESOLUTION = Symbol("headlessSessionResolution");
|
|
390
|
+
const HEADLESS_SESSION_HOST_LEASE = Symbol("headlessSessionHostLease");
|
|
391
|
+
|
|
392
|
+
async function withHeadlessSessionHostLease(options, deps, task) {
|
|
393
|
+
const prompt = (options.prompt || "").trim();
|
|
394
|
+
if (!prompt) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
"runAgentHeadless requires a non-empty prompt (use -p, a positional arg, or pipe stdin).",
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
const outputFormat = options.outputFormat || "text";
|
|
400
|
+
if (!VALID_OUTPUT_FORMATS.includes(outputFormat)) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
`Invalid --output-format "${outputFormat}". Expected one of: ${VALID_OUTPUT_FORMATS.join(", ")}`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const hasInjectedSessionStore =
|
|
406
|
+
typeof deps.sessionHasPersistedEvidence === "function" ||
|
|
407
|
+
typeof deps.sessionExists === "function" ||
|
|
408
|
+
typeof deps.rebuildMessages === "function" ||
|
|
409
|
+
typeof deps.appendEvent === "function" ||
|
|
410
|
+
typeof deps.appendAuthorityEvent === "function" ||
|
|
411
|
+
typeof deps.readVerifiedEvents === "function";
|
|
412
|
+
const resolution = resolveHeadlessSession(
|
|
413
|
+
options,
|
|
414
|
+
{
|
|
415
|
+
getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
|
|
416
|
+
resolveSessionAuthority:
|
|
417
|
+
deps.resolveSessionAuthority ||
|
|
418
|
+
(!hasInjectedSessionStore ? jsonlResolveSessionAuthority : undefined),
|
|
419
|
+
},
|
|
420
|
+
`headless-${Date.now()}-${process.pid}`,
|
|
421
|
+
);
|
|
422
|
+
const authoritySessionId =
|
|
423
|
+
resolution.resumeId || (resolution.persist ? resolution.sessionId : null);
|
|
424
|
+
const acquireHostLease =
|
|
425
|
+
deps.acquireSessionHostLease ||
|
|
426
|
+
(!hasInjectedSessionStore ? acquireSessionHostLease : null);
|
|
427
|
+
let lease = null;
|
|
428
|
+
try {
|
|
429
|
+
if (authoritySessionId && typeof acquireHostLease === "function") {
|
|
430
|
+
lease = acquireHostLease(authoritySessionId, { hostKind: "headless" });
|
|
431
|
+
}
|
|
432
|
+
const scopedOptions = lease?.signal
|
|
433
|
+
? {
|
|
434
|
+
...options,
|
|
435
|
+
signal: options.signal
|
|
436
|
+
? AbortSignal.any([options.signal, lease.signal])
|
|
437
|
+
: lease.signal,
|
|
438
|
+
}
|
|
439
|
+
: options;
|
|
440
|
+
return await task(scopedOptions, {
|
|
441
|
+
...deps,
|
|
442
|
+
[HEADLESS_SESSION_RESOLUTION]: resolution,
|
|
443
|
+
[HEADLESS_SESSION_HOST_LEASE]: lease,
|
|
444
|
+
});
|
|
445
|
+
} finally {
|
|
446
|
+
lease?.release?.();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
366
450
|
export async function runAgentHeadless(options = {}, deps = {}) {
|
|
367
451
|
const trustedWorkspaceRoot = options.cwd || process.cwd();
|
|
368
452
|
const pipeState = { closed: false };
|
|
@@ -388,6 +472,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
388
472
|
writeOut: deps.writeOut,
|
|
389
473
|
writeErr: deps.writeErr,
|
|
390
474
|
maxQueuedBytes: deps.outputBackpressureMaxBytes,
|
|
475
|
+
drainTimeoutMs: deps.outputBackpressureTimeoutMs,
|
|
391
476
|
onFailure: (error) => {
|
|
392
477
|
outputAbort?.abort(error);
|
|
393
478
|
if (error?.code === "EPIPE") handlePipeClosed();
|
|
@@ -423,7 +508,12 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
423
508
|
let failure = null;
|
|
424
509
|
try {
|
|
425
510
|
outcome = await runWithHostHooksV2Workspace(trustedWorkspaceRoot, () =>
|
|
426
|
-
|
|
511
|
+
withHeadlessSessionHostLease(
|
|
512
|
+
runtimeOptions,
|
|
513
|
+
runtimeDeps,
|
|
514
|
+
(scopedOptions, scopedDeps) =>
|
|
515
|
+
runAgentHeadlessInWorkspace(scopedOptions, scopedDeps, pipeState),
|
|
516
|
+
),
|
|
427
517
|
);
|
|
428
518
|
} catch (error) {
|
|
429
519
|
failure = error;
|
|
@@ -470,6 +560,10 @@ async function runAgentHeadlessInWorkspace(
|
|
|
470
560
|
const isText = outputFormat === "text";
|
|
471
561
|
const writeOut = deps.writeOut;
|
|
472
562
|
const writeErr = deps.writeErr;
|
|
563
|
+
const cleanupDeadline = createCleanupDeadline({
|
|
564
|
+
timeoutMs: deps.cleanupDeadlineMs,
|
|
565
|
+
label: "headless-runner-cleanup",
|
|
566
|
+
});
|
|
473
567
|
|
|
474
568
|
const hasInjectedSessionStore =
|
|
475
569
|
typeof deps.sessionHasPersistedEvidence === "function" ||
|
|
@@ -478,11 +572,19 @@ async function runAgentHeadlessInWorkspace(
|
|
|
478
572
|
typeof deps.appendEvent === "function" ||
|
|
479
573
|
typeof deps.appendAuthorityEvent === "function" ||
|
|
480
574
|
typeof deps.readVerifiedEvents === "function";
|
|
481
|
-
const { sessionId, resumeId, persist } =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
575
|
+
const { sessionId, resumeId, persist } =
|
|
576
|
+
deps[HEADLESS_SESSION_RESOLUTION] ||
|
|
577
|
+
resolveHeadlessSession(
|
|
578
|
+
options,
|
|
579
|
+
{
|
|
580
|
+
getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
|
|
581
|
+
resolveSessionAuthority:
|
|
582
|
+
deps.resolveSessionAuthority ||
|
|
583
|
+
(!hasInjectedSessionStore ? jsonlResolveSessionAuthority : undefined),
|
|
584
|
+
},
|
|
585
|
+
`headless-${Date.now()}-${process.pid}`,
|
|
586
|
+
);
|
|
587
|
+
const sessionHostLease = deps[HEADLESS_SESSION_HOST_LEASE] || null;
|
|
486
588
|
|
|
487
589
|
const emitHeadlessError = (resultMsg) => {
|
|
488
590
|
if (isStream) {
|
|
@@ -544,8 +646,8 @@ async function runAgentHeadlessInWorkspace(
|
|
|
544
646
|
sessionSnapshot: canonicalAdmission.snapshot,
|
|
545
647
|
};
|
|
546
648
|
}
|
|
547
|
-
// A persist-only target must pass the same canonical admission boundary,
|
|
548
|
-
// it does not opt into replaying the target's prior conversation.
|
|
649
|
+
// A persist-only target must pass the same canonical admission boundary,
|
|
650
|
+
// but it does not opt into replaying the target's prior conversation.
|
|
549
651
|
const canonicalResume = resumeId ? canonicalAdmission : null;
|
|
550
652
|
|
|
551
653
|
// `let` (not const): a custom-command macro's `model:` frontmatter may
|
|
@@ -1026,7 +1128,7 @@ async function runAgentHeadlessInWorkspace(
|
|
|
1026
1128
|
if (!persist) return;
|
|
1027
1129
|
if (sideEffectLedgerLoadError) throw sideEffectLedgerLoadError;
|
|
1028
1130
|
try {
|
|
1029
|
-
const persisted = store.
|
|
1131
|
+
const persisted = store.appendAuthorityEvent(
|
|
1030
1132
|
sessionId,
|
|
1031
1133
|
SIDE_EFFECT_LEDGER_EVENT,
|
|
1032
1134
|
sideEffectLedger.toJSON(),
|
|
@@ -1081,13 +1183,16 @@ async function runAgentHeadlessInWorkspace(
|
|
|
1081
1183
|
// (createTurnBindingFeed) so the interactive REPL producer can never drift
|
|
1082
1184
|
// from this runner's mapping. No feed at all for non-persisted runs.
|
|
1083
1185
|
const turnBindingFeed = persist
|
|
1084
|
-
? createTurnBindingFeed({
|
|
1186
|
+
? createTurnBindingFeed({
|
|
1187
|
+
log: turnBindingLog,
|
|
1188
|
+
nonce: sideEffectRunNonce,
|
|
1189
|
+
})
|
|
1085
1190
|
: null;
|
|
1086
1191
|
const persistTurnBindingLog = () => {
|
|
1087
1192
|
if (!turnBindingFeed || !turnBindingFeed.isDirty()) return;
|
|
1088
1193
|
if (turnBindingLoadError) throw turnBindingLoadError;
|
|
1089
1194
|
try {
|
|
1090
|
-
const persisted = store.
|
|
1195
|
+
const persisted = store.appendAuthorityEvent(
|
|
1091
1196
|
sessionId,
|
|
1092
1197
|
TURN_BINDING_EVENT,
|
|
1093
1198
|
turnBindingLog.toJSON(),
|
|
@@ -1271,7 +1376,9 @@ async function runAgentHeadlessInWorkspace(
|
|
|
1271
1376
|
}
|
|
1272
1377
|
|
|
1273
1378
|
const budget = Number.isFinite(options.maxTurns)
|
|
1274
|
-
? new IterationBudget({
|
|
1379
|
+
? new IterationBudget({
|
|
1380
|
+
limit: Math.max(1, Math.floor(options.maxTurns)),
|
|
1381
|
+
})
|
|
1275
1382
|
: new IterationBudget();
|
|
1276
1383
|
|
|
1277
1384
|
// Effective system prompt: built-in base, optionally replaced by
|
|
@@ -1463,9 +1570,10 @@ async function runAgentHeadlessInWorkspace(
|
|
|
1463
1570
|
{ role: "user", content: userMessageContent },
|
|
1464
1571
|
]);
|
|
1465
1572
|
|
|
1466
|
-
// Persist the user turn
|
|
1467
|
-
//
|
|
1468
|
-
// the
|
|
1573
|
+
// Persist the user turn before model/tool side effects. A full or read-only
|
|
1574
|
+
// session disk is a recoverability contract failure, so it fails closed
|
|
1575
|
+
// while the requested turn is still side-effect free. startSession is
|
|
1576
|
+
// append-safe: only seed the header when the file does not yet exist.
|
|
1469
1577
|
if (persist) {
|
|
1470
1578
|
try {
|
|
1471
1579
|
if (!store.sessionExists(sessionId)) {
|
|
@@ -1478,8 +1586,62 @@ async function runAgentHeadlessInWorkspace(
|
|
|
1478
1586
|
// Persist the expanded content so a resumed session faithfully replays
|
|
1479
1587
|
// what the model actually saw (the file snapshot, not just the @token).
|
|
1480
1588
|
store.appendUserMessage(sessionId, userContent);
|
|
1481
|
-
} catch {
|
|
1482
|
-
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
const persistenceError = createSessionPersistenceFailure(error, {
|
|
1591
|
+
sessionId,
|
|
1592
|
+
operation: "user-turn-append",
|
|
1593
|
+
});
|
|
1594
|
+
const persistence = projectSessionPersistenceFailure(persistenceError, {
|
|
1595
|
+
phase: "before-model",
|
|
1596
|
+
});
|
|
1597
|
+
if (persistence) {
|
|
1598
|
+
try {
|
|
1599
|
+
deps.onPersistenceFailure?.(persistence);
|
|
1600
|
+
} catch {
|
|
1601
|
+
// Observability cannot change persistence semantics.
|
|
1602
|
+
}
|
|
1603
|
+
const resultMsg = `${persistence.code}: session turn was not durably persisted`;
|
|
1604
|
+
if (isStream) {
|
|
1605
|
+
writeOut(
|
|
1606
|
+
JSON.stringify({
|
|
1607
|
+
type: "result",
|
|
1608
|
+
subtype: "error_persistence",
|
|
1609
|
+
is_error: true,
|
|
1610
|
+
error: resultMsg,
|
|
1611
|
+
session_id: sessionId,
|
|
1612
|
+
persistence,
|
|
1613
|
+
}) + "\n",
|
|
1614
|
+
);
|
|
1615
|
+
} else if (isJson) {
|
|
1616
|
+
writeOut(
|
|
1617
|
+
JSON.stringify(
|
|
1618
|
+
buildResultEnvelope({
|
|
1619
|
+
subtype: "error_persistence",
|
|
1620
|
+
isError: true,
|
|
1621
|
+
result: resultMsg,
|
|
1622
|
+
sessionId,
|
|
1623
|
+
toolCalls: [],
|
|
1624
|
+
usage: {},
|
|
1625
|
+
numTurns: 0,
|
|
1626
|
+
durationMs: 0,
|
|
1627
|
+
persistence,
|
|
1628
|
+
}),
|
|
1629
|
+
) + "\n",
|
|
1630
|
+
);
|
|
1631
|
+
} else {
|
|
1632
|
+
writeErr(
|
|
1633
|
+
`${resultMsg} (${persistence.fs_code}; ${persistence.commit_state})\n`,
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
return {
|
|
1637
|
+
exitCode: 1,
|
|
1638
|
+
result: resultMsg,
|
|
1639
|
+
isError: true,
|
|
1640
|
+
persistence,
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
// Preserve legacy best-effort handling for errors outside the explicit
|
|
1644
|
+
// ENOSPC/EROFS durability contract.
|
|
1483
1645
|
}
|
|
1484
1646
|
}
|
|
1485
1647
|
|
|
@@ -1630,7 +1792,13 @@ async function runAgentHeadlessInWorkspace(
|
|
|
1630
1792
|
ppt = resolvePermissionPromptTool(mcp, options.permissionPromptTool);
|
|
1631
1793
|
} catch (err) {
|
|
1632
1794
|
writeErr(`Error: ${err.message}\n`);
|
|
1633
|
-
|
|
1795
|
+
await cleanupDeadline.run("mcp", () => mcp?.mcpClient?.disconnectAll?.());
|
|
1796
|
+
const cleanupReport = cleanupDeadline.report();
|
|
1797
|
+
try {
|
|
1798
|
+
deps.onCleanupReport?.(cleanupReport);
|
|
1799
|
+
} catch {
|
|
1800
|
+
// Metrics/reporting cannot change cleanup semantics.
|
|
1801
|
+
}
|
|
1634
1802
|
emitHeadlessError(err.message);
|
|
1635
1803
|
// A bad --permission-prompt-tool reference is a CONFIG error.
|
|
1636
1804
|
return {
|
|
@@ -2261,8 +2429,11 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2261
2429
|
process.once("SIGINT", _onHardSignal);
|
|
2262
2430
|
process.once("SIGTERM", _onHardSignal);
|
|
2263
2431
|
|
|
2432
|
+
let loopFailureOutcome = null;
|
|
2433
|
+
let cleanupFailure = null;
|
|
2264
2434
|
try {
|
|
2265
2435
|
while (true) {
|
|
2436
|
+
sessionHostLease?.assert?.();
|
|
2266
2437
|
// P1 turn→checkpoint binding: one drain of the loop = one turn. Anchor
|
|
2267
2438
|
// the conversation offset before the model sees the new user turn. A
|
|
2268
2439
|
// `--worktree` run stamps its isolation worktree (branch name, threaded
|
|
@@ -2286,7 +2457,11 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2286
2457
|
case "checkpoint": {
|
|
2287
2458
|
if (isText)
|
|
2288
2459
|
writeErr(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`);
|
|
2289
|
-
emitStream({
|
|
2460
|
+
emitStream({
|
|
2461
|
+
type: "checkpoint",
|
|
2462
|
+
id: event.id,
|
|
2463
|
+
tool: event.tool,
|
|
2464
|
+
});
|
|
2290
2465
|
break;
|
|
2291
2466
|
}
|
|
2292
2467
|
case "tool-executing": {
|
|
@@ -2330,6 +2505,11 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2330
2505
|
})
|
|
2331
2506
|
.start(opId);
|
|
2332
2507
|
persistSideEffectLedger();
|
|
2508
|
+
// The generator executes the tool only after this yielded
|
|
2509
|
+
// prewrite is handled. Recheck the same durable lease at that
|
|
2510
|
+
// final host boundary so a fenced host never resumes into the
|
|
2511
|
+
// external operation.
|
|
2512
|
+
sessionHostLease?.assert?.();
|
|
2333
2513
|
}
|
|
2334
2514
|
}
|
|
2335
2515
|
break;
|
|
@@ -2724,16 +2904,18 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2724
2904
|
}
|
|
2725
2905
|
// Provider/transport failures get their own exit code (5) so CI can tell
|
|
2726
2906
|
// "the model call failed" from "the run itself errored" (1).
|
|
2727
|
-
|
|
2907
|
+
loopFailureOutcome = {
|
|
2908
|
+
exitCode: classifyLoopError(err),
|
|
2909
|
+
result: message,
|
|
2910
|
+
isError: true,
|
|
2911
|
+
};
|
|
2728
2912
|
} finally {
|
|
2729
2913
|
// Capture body-cache hits/misses caused by list_skills/run_skill during the
|
|
2730
2914
|
// turn. `cc context --sources` reads the newest snapshot.
|
|
2731
2915
|
persistContextSources();
|
|
2732
|
-
|
|
2733
|
-
_backgroundInteraction?.close?.()
|
|
2734
|
-
|
|
2735
|
-
// Best-effort listener cleanup; never mask the run result.
|
|
2736
|
-
}
|
|
2916
|
+
await cleanupDeadline.run("background-interaction", () =>
|
|
2917
|
+
_backgroundInteraction?.close?.(),
|
|
2918
|
+
);
|
|
2737
2919
|
// Drop the signal handlers first — a normal return must not leave a
|
|
2738
2920
|
// process-wide SIGINT/SIGTERM listener behind (a later Ctrl-C would wrongly
|
|
2739
2921
|
// exit with 130, and repeated in-process runs would leak listeners).
|
|
@@ -2742,136 +2924,142 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2742
2924
|
// Tear down ad-hoc MCP servers (--mcp-config) before returning, whether the
|
|
2743
2925
|
// loop completed or threw. Best-effort: a failed disconnect never masks the
|
|
2744
2926
|
// run's own outcome.
|
|
2745
|
-
|
|
2746
|
-
try {
|
|
2747
|
-
await mcp.mcpClient.disconnectAll();
|
|
2748
|
-
} catch {
|
|
2749
|
-
// ignore — disconnect is best-effort
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2927
|
+
await cleanupDeadline.run("mcp", () => mcp?.mcpClient?.disconnectAll?.());
|
|
2752
2928
|
// Kill any background run_shell tasks this run spawned so a backgrounded
|
|
2753
2929
|
// command (e.g. a dev server) doesn't outlive the headless invocation.
|
|
2754
|
-
|
|
2755
|
-
killAllBackgroundShellTasks()
|
|
2756
|
-
|
|
2757
|
-
// best-effort — never mask the run's own outcome
|
|
2758
|
-
}
|
|
2930
|
+
await cleanupDeadline.run("background-shells", () =>
|
|
2931
|
+
killAllBackgroundShellTasks(),
|
|
2932
|
+
);
|
|
2759
2933
|
// Tear down the --remote-control approval bridge + its self-hosted WS
|
|
2760
2934
|
// server so the run's port/socket never outlives the invocation.
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
} catch {
|
|
2765
|
-
// best-effort — never mask the run's own outcome
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2935
|
+
await cleanupDeadline.run("remote-approval", () =>
|
|
2936
|
+
_remoteApproval?.close?.(),
|
|
2937
|
+
);
|
|
2768
2938
|
// settings.json Stop + SessionEnd hooks when the run finishes. Stop is the
|
|
2769
2939
|
// canonical async-hook trigger ("run the test suite at turn end"); fire its
|
|
2770
2940
|
// async hooks fire-and-forget, then settle so a fast background check can
|
|
2771
2941
|
// report back within this one-shot run.
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
session_id: sessionId,
|
|
2780
|
-
};
|
|
2781
|
-
const stopOutcome = runObserveHooks(
|
|
2782
|
-
settingsHooks,
|
|
2783
|
-
"Stop",
|
|
2784
|
-
stopPayload,
|
|
2785
|
-
{ cwd },
|
|
2786
|
-
);
|
|
2787
|
-
const stopFailures = (stopOutcome?.results || []).filter(
|
|
2788
|
-
(result) =>
|
|
2789
|
-
result?.nonBlockingError === true ||
|
|
2790
|
-
result?.malformedDecision === true ||
|
|
2791
|
-
result?.breakerOpen === true,
|
|
2792
|
-
);
|
|
2793
|
-
if (stopFailures.length > 0) {
|
|
2794
|
-
emitHooksV2Event("StopFailure", {
|
|
2795
|
-
schema_version: 1,
|
|
2796
|
-
session_id: sessionId,
|
|
2797
|
-
phase: "stop-hook",
|
|
2798
|
-
failures: stopFailures.map((failure) => ({
|
|
2799
|
-
command: failure.command || null,
|
|
2800
|
-
exit_code: failure.exitCode ?? null,
|
|
2801
|
-
reason: failure.reason || failure.error || null,
|
|
2802
|
-
})),
|
|
2803
|
-
});
|
|
2804
|
-
}
|
|
2805
|
-
// Skip the async Stop dispatch/settle here if the auto-rewake re-drive
|
|
2806
|
-
// loop already fired + drained it this run (avoids double-execution).
|
|
2807
|
-
if (_hookSupervisor && !_asyncStopHandled) {
|
|
2808
|
-
dispatchAsyncHooks(settingsHooks, "Stop", stopPayload, {
|
|
2942
|
+
await cleanupDeadline.run("settings-hooks", async () => {
|
|
2943
|
+
if (settingsHooks) {
|
|
2944
|
+
try {
|
|
2945
|
+
const { runObserveHooks, dispatchAsyncHooks } =
|
|
2946
|
+
await import("../lib/settings-hook-events.js");
|
|
2947
|
+
const stopPayload = {
|
|
2948
|
+
reason: pipeState.closed ? "pipe_closed" : endReason,
|
|
2809
2949
|
cwd,
|
|
2810
|
-
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
const
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
// Surface any results/rewakes out-of-band on stderr (never touches the
|
|
2826
|
-
// stdout envelope). A rewake means a background check FAILED and would
|
|
2827
|
-
// re-engage the agent in an interactive session — flag it clearly so a
|
|
2828
|
-
// headless caller/CI can react.
|
|
2829
|
-
for (const rw of _hookSupervisor.drainRewakes()) {
|
|
2950
|
+
session_id: sessionId,
|
|
2951
|
+
};
|
|
2952
|
+
const stopOutcome = runObserveHooks(
|
|
2953
|
+
settingsHooks,
|
|
2954
|
+
"Stop",
|
|
2955
|
+
stopPayload,
|
|
2956
|
+
{ cwd },
|
|
2957
|
+
);
|
|
2958
|
+
const stopFailures = (stopOutcome?.results || []).filter(
|
|
2959
|
+
(result) =>
|
|
2960
|
+
result?.nonBlockingError === true ||
|
|
2961
|
+
result?.malformedDecision === true ||
|
|
2962
|
+
result?.breakerOpen === true,
|
|
2963
|
+
);
|
|
2964
|
+
if (stopFailures.length > 0) {
|
|
2830
2965
|
emitHooksV2Event("StopFailure", {
|
|
2831
2966
|
schema_version: 1,
|
|
2832
2967
|
session_id: sessionId,
|
|
2833
|
-
phase: "
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2968
|
+
phase: "stop-hook",
|
|
2969
|
+
failures: stopFailures.map((failure) => ({
|
|
2970
|
+
command: failure.command || null,
|
|
2971
|
+
exit_code: failure.exitCode ?? null,
|
|
2972
|
+
reason: failure.reason || failure.error || null,
|
|
2973
|
+
})),
|
|
2837
2974
|
});
|
|
2838
|
-
writeErr(
|
|
2839
|
-
`[async-hook REWAKE] ${rw.command} failed` +
|
|
2840
|
-
`${rw.exitCode != null ? ` (exit ${rw.exitCode})` : ""}` +
|
|
2841
|
-
`${rw.error ? `: ${rw.error}` : ""}\n`,
|
|
2842
|
-
);
|
|
2843
2975
|
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2976
|
+
// Skip the async Stop dispatch/settle here if the auto-rewake re-drive
|
|
2977
|
+
// loop already fired + drained it this run (avoids double-execution).
|
|
2978
|
+
if (_hookSupervisor && !_asyncStopHandled) {
|
|
2979
|
+
dispatchAsyncHooks(settingsHooks, "Stop", stopPayload, {
|
|
2980
|
+
cwd,
|
|
2981
|
+
supervisor: _hookSupervisor,
|
|
2982
|
+
});
|
|
2983
|
+
// Bounded settle: wait for in-flight async hooks (Stop + any
|
|
2984
|
+
// PostToolUse dispatched during the loop) to finish, capped so a
|
|
2985
|
+
// slow/hung check can never stall the run. Default 5s; tunable.
|
|
2986
|
+
const waitMs = Number.isFinite(options.asyncHookWaitMs)
|
|
2987
|
+
? options.asyncHookWaitMs
|
|
2988
|
+
: 5000;
|
|
2989
|
+
const started = Date.now();
|
|
2990
|
+
while (
|
|
2991
|
+
_hookSupervisor.runningCount() > 0 &&
|
|
2992
|
+
Date.now() - started < waitMs
|
|
2993
|
+
) {
|
|
2994
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2995
|
+
}
|
|
2996
|
+
// Surface any results/rewakes out-of-band on stderr (never touches the
|
|
2997
|
+
// stdout envelope). A rewake means a background check FAILED and would
|
|
2998
|
+
// re-engage the agent in an interactive session — flag it clearly so a
|
|
2999
|
+
// headless caller/CI can react.
|
|
3000
|
+
for (const rw of _hookSupervisor.drainRewakes()) {
|
|
3001
|
+
emitHooksV2Event("StopFailure", {
|
|
3002
|
+
schema_version: 1,
|
|
3003
|
+
session_id: sessionId,
|
|
3004
|
+
phase: "async-stop-hook",
|
|
3005
|
+
command: rw.command || null,
|
|
3006
|
+
exit_code: rw.exitCode ?? null,
|
|
3007
|
+
reason: rw.error || "async stop hook requested rewake",
|
|
3008
|
+
});
|
|
3009
|
+
writeErr(
|
|
3010
|
+
`[async-hook REWAKE] ${rw.command} failed` +
|
|
3011
|
+
`${rw.exitCode != null ? ` (exit ${rw.exitCode})` : ""}` +
|
|
3012
|
+
`${rw.error ? `: ${rw.error}` : ""}\n`,
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
for (const rs of _hookSupervisor.drainResults()) {
|
|
3016
|
+
if (rs.additionalContext) {
|
|
3017
|
+
writeErr(
|
|
3018
|
+
`[async-hook] ${rs.command}: ${rs.additionalContext}\n`,
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
2847
3021
|
}
|
|
2848
3022
|
}
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
3023
|
+
// Always reap the supervisor (kills any straggler child + detaches the
|
|
3024
|
+
// exit reaper), whether the async Stop was handled here or in re-drive.
|
|
3025
|
+
if (_hookSupervisor) _hookSupervisor.stopAll();
|
|
3026
|
+
runObserveHooks(
|
|
3027
|
+
settingsHooks,
|
|
3028
|
+
"SessionEnd",
|
|
3029
|
+
{
|
|
3030
|
+
reason: pipeState.closed ? "pipe_closed" : "completed",
|
|
3031
|
+
cwd,
|
|
3032
|
+
session_id: sessionId,
|
|
3033
|
+
},
|
|
3034
|
+
{ cwd },
|
|
3035
|
+
);
|
|
3036
|
+
} catch (error) {
|
|
3037
|
+
emitHooksV2Event("StopFailure", {
|
|
3038
|
+
schema_version: 1,
|
|
2859
3039
|
session_id: sessionId,
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
schema_version: 1,
|
|
2866
|
-
session_id: sessionId,
|
|
2867
|
-
phase: "stop-hook-dispatch",
|
|
2868
|
-
reason: error?.message || String(error),
|
|
2869
|
-
});
|
|
2870
|
-
// observe-only + best-effort async surfacing
|
|
3040
|
+
phase: "stop-hook-dispatch",
|
|
3041
|
+
reason: error?.message || String(error),
|
|
3042
|
+
});
|
|
3043
|
+
// observe-only + best-effort async surfacing
|
|
3044
|
+
}
|
|
2871
3045
|
}
|
|
3046
|
+
});
|
|
3047
|
+
const cleanupReport = cleanupDeadline.report();
|
|
3048
|
+
try {
|
|
3049
|
+
deps.onCleanupReport?.(cleanupReport);
|
|
3050
|
+
} catch {
|
|
3051
|
+
// Metrics/reporting cannot change cleanup semantics.
|
|
3052
|
+
}
|
|
3053
|
+
if (cleanupReport.timedOut) {
|
|
3054
|
+
cleanupFailure = cleanupDeadlineError(cleanupReport);
|
|
2872
3055
|
}
|
|
2873
3056
|
}
|
|
2874
3057
|
|
|
3058
|
+
// Preserve the primary model/runtime failure. A simultaneous cleanup timeout
|
|
3059
|
+
// remains observable through onCleanupReport but must not replace it.
|
|
3060
|
+
if (loopFailureOutcome) return loopFailureOutcome;
|
|
3061
|
+
if (cleanupFailure) throw cleanupFailure;
|
|
3062
|
+
|
|
2875
3063
|
// A downstream consumer closed stdout/stderr. The abort above unwound the
|
|
2876
3064
|
// model loop through the same `finally` as every other termination, so MCP,
|
|
2877
3065
|
// background tasks, approval bridges, and hooks are settled. Do not attempt
|
|
@@ -2886,8 +3074,8 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2886
3074
|
const exhausted =
|
|
2887
3075
|
endReason === "budget-exhausted" || endReason === "max_turns";
|
|
2888
3076
|
const costStopped = endReason === "cost-budget-exhausted";
|
|
2889
|
-
|
|
2890
|
-
|
|
3077
|
+
let isError = exhausted || costStopped || endReason === "no-response";
|
|
3078
|
+
let subtype = exhausted
|
|
2891
3079
|
? "error_max_turns"
|
|
2892
3080
|
: costStopped
|
|
2893
3081
|
? "error_max_budget"
|
|
@@ -2895,6 +3083,7 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2895
3083
|
? "error"
|
|
2896
3084
|
: "success";
|
|
2897
3085
|
const durationMs = (deps.now ? deps.now() : Date.now()) - startedAt;
|
|
3086
|
+
let persistenceFailure = null;
|
|
2898
3087
|
|
|
2899
3088
|
// Persist the assistant turn so a later --resume / --continue replays it.
|
|
2900
3089
|
// The user turn was already recorded up front; only append on a clean run.
|
|
@@ -2921,8 +3110,30 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2921
3110
|
store.appendTokenUsage(sessionId, au);
|
|
2922
3111
|
}
|
|
2923
3112
|
store.appendTokenUsage(sessionId, usage);
|
|
2924
|
-
} catch {
|
|
2925
|
-
|
|
3113
|
+
} catch (error) {
|
|
3114
|
+
const persistenceError = createSessionPersistenceFailure(error, {
|
|
3115
|
+
sessionId,
|
|
3116
|
+
operation: "assistant-turn-append",
|
|
3117
|
+
});
|
|
3118
|
+
persistenceFailure = projectSessionPersistenceFailure(persistenceError, {
|
|
3119
|
+
phase: "after-model",
|
|
3120
|
+
});
|
|
3121
|
+
if (persistenceFailure) {
|
|
3122
|
+
isError = true;
|
|
3123
|
+
subtype = "error_persistence";
|
|
3124
|
+
try {
|
|
3125
|
+
deps.onPersistenceFailure?.(persistenceFailure);
|
|
3126
|
+
} catch {
|
|
3127
|
+
// Observability cannot change persistence semantics.
|
|
3128
|
+
}
|
|
3129
|
+
if (isText) {
|
|
3130
|
+
writeErr(
|
|
3131
|
+
`CC_SESSION_PERSISTENCE_FAILED: assistant turn durability is ${persistenceFailure.commit_state} (${persistenceFailure.fs_code})\n`,
|
|
3132
|
+
);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
// Preserve legacy best-effort handling for errors outside the explicit
|
|
3136
|
+
// ENOSPC/EROFS durability contract.
|
|
2926
3137
|
}
|
|
2927
3138
|
}
|
|
2928
3139
|
|
|
@@ -2983,7 +3194,9 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2983
3194
|
if (isText) {
|
|
2984
3195
|
writeErr(
|
|
2985
3196
|
`\n ${denials.length} tool call(s) were denied by policy this run:\n` +
|
|
2986
|
-
formatDenials(denials, {
|
|
3197
|
+
formatDenials(denials, {
|
|
3198
|
+
now: deps.now ? deps.now() : Date.now(),
|
|
3199
|
+
}) +
|
|
2987
3200
|
"\n",
|
|
2988
3201
|
);
|
|
2989
3202
|
} else if (isStream) {
|
|
@@ -3016,6 +3229,7 @@ async function runAgentHeadlessInWorkspace(
|
|
|
3016
3229
|
...(compactionDegradations.length
|
|
3017
3230
|
? { compaction_degradations: compactionDegradations }
|
|
3018
3231
|
: {}),
|
|
3232
|
+
...(persistenceFailure ? { persistence: persistenceFailure } : {}),
|
|
3019
3233
|
});
|
|
3020
3234
|
} else if (isJson) {
|
|
3021
3235
|
writeOut(
|
|
@@ -3031,6 +3245,7 @@ async function runAgentHeadlessInWorkspace(
|
|
|
3031
3245
|
durationMs,
|
|
3032
3246
|
denials,
|
|
3033
3247
|
compactionDegradations,
|
|
3248
|
+
persistence: persistenceFailure,
|
|
3034
3249
|
}),
|
|
3035
3250
|
) + "\n",
|
|
3036
3251
|
);
|
|
@@ -3075,6 +3290,7 @@ async function runAgentHeadlessInWorkspace(
|
|
|
3075
3290
|
exitCode: exitCodeForEndReason(endReason, isError),
|
|
3076
3291
|
result: finalText,
|
|
3077
3292
|
isError,
|
|
3293
|
+
...(persistenceFailure ? { persistence: persistenceFailure } : {}),
|
|
3078
3294
|
};
|
|
3079
3295
|
}
|
|
3080
3296
|
|
|
@@ -3089,6 +3305,7 @@ function buildResultEnvelope({
|
|
|
3089
3305
|
durationMs,
|
|
3090
3306
|
denials,
|
|
3091
3307
|
compactionDegradations,
|
|
3308
|
+
persistence,
|
|
3092
3309
|
}) {
|
|
3093
3310
|
const env = {
|
|
3094
3311
|
type: "result",
|
|
@@ -3107,5 +3324,6 @@ function buildResultEnvelope({
|
|
|
3107
3324
|
if (Array.isArray(compactionDegradations) && compactionDegradations.length) {
|
|
3108
3325
|
env.compaction_degradations = compactionDegradations;
|
|
3109
3326
|
}
|
|
3327
|
+
if (persistence) env.persistence = persistence;
|
|
3110
3328
|
return env;
|
|
3111
3329
|
}
|