chainlesschain 0.162.162 → 0.162.164
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 +3 -1
- package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
- package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +320 -32
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +456 -1
- package/src/commands/review.js +363 -2
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +10 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +55 -2
- package/src/gateways/ws/ws-agent-handler.js +50 -0
- package/src/harness/jsonl-session-store.js +60 -0
- package/src/harness/worktree-isolator.js +140 -5
- package/src/lib/agent-schedule-store.js +318 -10
- package/src/lib/agent-team/team-worktree.js +37 -2
- package/src/lib/agent-tool-admission.js +125 -0
- package/src/lib/app-preview.js +360 -0
- package/src/lib/async-hook-supervisor.cjs +54 -0
- package/src/lib/capability-manifest.js +319 -0
- package/src/lib/capability-negotiation.js +23 -12
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +275 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +46 -12
- package/src/lib/json-schema-validate.js +143 -3
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
- package/src/lib/lsp/lsp-manager.js +56 -0
- package/src/lib/monitor-event.js +290 -0
- package/src/lib/operation-fingerprint.js +217 -0
- package/src/lib/plugin-runtime/bin.js +37 -0
- package/src/lib/plugin-runtime/capabilities.js +19 -0
- package/src/lib/plugin-runtime/capability-consent.js +203 -0
- package/src/lib/plugin-runtime/hooks.js +37 -0
- package/src/lib/plugin-runtime/lsp.js +38 -0
- package/src/lib/plugin-runtime/mcp.js +40 -0
- package/src/lib/plugin-runtime/monitors.js +37 -0
- package/src/lib/plugin-runtime/plugin-options.js +151 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/pr-automation-policy.js +224 -0
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +153 -0
- package/src/lib/review-comment-anchor.js +241 -0
- package/src/lib/review-pipeline.js +46 -7
- package/src/lib/runtime-checkup.js +221 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/settings-hook-events.cjs +13 -0
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +401 -0
- package/src/lib/skill-loader.js +32 -0
- package/src/lib/skill-path-scope.js +63 -0
- package/src/lib/sub-agent-context.js +110 -9
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/unattended-action-policy.js +182 -0
- package/src/lib/worktree-cleanup-safety.js +138 -0
- package/src/lib/worktree-isolator.js +8 -0
- package/src/lib/worktree-sparse.js +131 -0
- package/src/repl/agent-repl.js +280 -30
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +565 -21
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +393 -1
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
- package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
- package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
|
@@ -614,7 +614,7 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
|
|
|
614
614
|
type: "string",
|
|
615
615
|
enum: ["worktree"],
|
|
616
616
|
description:
|
|
617
|
-
|
|
617
|
+
'Optional isolation mode: "worktree" runs the sub-agent in a fresh git worktree so its file edits land on an isolated branch (diff/merge summary comes back with the result).',
|
|
618
618
|
},
|
|
619
619
|
},
|
|
620
620
|
required: ["task"],
|
|
@@ -959,6 +959,65 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
|
|
|
959
959
|
type: "string",
|
|
960
960
|
description: "Optional human label for list/cancel",
|
|
961
961
|
},
|
|
962
|
+
expires: {
|
|
963
|
+
type: "string",
|
|
964
|
+
description:
|
|
965
|
+
"For wakeup/cron/monitor: retire the entry after this long if it hasn't fired (e.g. 2h, 7d); it then never fires",
|
|
966
|
+
},
|
|
967
|
+
jitter: {
|
|
968
|
+
type: "string",
|
|
969
|
+
description:
|
|
970
|
+
"For wakeup/cron/monitor: a random-but-stable spread (e.g. 30s) so entries sharing a schedule fan out instead of firing all at once",
|
|
971
|
+
},
|
|
972
|
+
permission_mode: {
|
|
973
|
+
type: "string",
|
|
974
|
+
enum: [
|
|
975
|
+
"plan",
|
|
976
|
+
"manual",
|
|
977
|
+
"default",
|
|
978
|
+
"acceptEdits",
|
|
979
|
+
"auto",
|
|
980
|
+
"dontAsk",
|
|
981
|
+
"bypassPermissions",
|
|
982
|
+
],
|
|
983
|
+
description:
|
|
984
|
+
"For wakeup/cron: run the fired `cc agent` with this permission mode instead of the ambient one (e.g. `plan` for a read-only report, `manual` to gate every action)",
|
|
985
|
+
},
|
|
986
|
+
worktree: {
|
|
987
|
+
type: "boolean",
|
|
988
|
+
description:
|
|
989
|
+
"For wakeup/cron: run the fired `cc agent` in a fresh git worktree so its changes stay isolated",
|
|
990
|
+
},
|
|
991
|
+
max_turns: {
|
|
992
|
+
type: "number",
|
|
993
|
+
description:
|
|
994
|
+
"For wakeup/cron: cap the fired agent's loop iterations (a per-task budget)",
|
|
995
|
+
},
|
|
996
|
+
goal_condition: {
|
|
997
|
+
type: "string",
|
|
998
|
+
description:
|
|
999
|
+
"For wakeup/cron: run the fired agent to a completion condition, re-driving outer turns until met. Prefix exit-zero:/file-exists:/contains:/regex: (deterministic) or model:<desc> (model-judged). Invalid specs are dropped.",
|
|
1000
|
+
},
|
|
1001
|
+
max_outer_turns: {
|
|
1002
|
+
type: "number",
|
|
1003
|
+
description:
|
|
1004
|
+
"For wakeup/cron with goal_condition: max outer re-drive turns (default 10, hard cap 100)",
|
|
1005
|
+
},
|
|
1006
|
+
goal_max_tokens: {
|
|
1007
|
+
type: "number",
|
|
1008
|
+
description:
|
|
1009
|
+
"For wakeup/cron with goal_condition: token budget across outer turns",
|
|
1010
|
+
},
|
|
1011
|
+
goal_max_cost: {
|
|
1012
|
+
type: "number",
|
|
1013
|
+
description:
|
|
1014
|
+
"For wakeup/cron with goal_condition: cost budget (USD) across outer turns",
|
|
1015
|
+
},
|
|
1016
|
+
goal_max_time: {
|
|
1017
|
+
type: "number",
|
|
1018
|
+
description:
|
|
1019
|
+
"For wakeup/cron with goal_condition: wall-clock budget (ms) across outer turns",
|
|
1020
|
+
},
|
|
962
1021
|
id: {
|
|
963
1022
|
type: "string",
|
|
964
1023
|
description: "For cancel: the schedule entry id to remove",
|
|
@@ -288,6 +288,66 @@ export async function collectDoctorReport() {
|
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Parse `git worktree list --porcelain` output into `{ path, branch }` entries.
|
|
293
|
+
*
|
|
294
|
+
* Pure (no I/O) so it can be unit-tested against fixed fixtures. A detached
|
|
295
|
+
* worktree has no `branch` line and yields `branch: null`.
|
|
296
|
+
*/
|
|
297
|
+
export function parseGitWorktrees(porcelain) {
|
|
298
|
+
if (!porcelain || typeof porcelain !== "string") return [];
|
|
299
|
+
const entries = [];
|
|
300
|
+
let cur = null;
|
|
301
|
+
for (const line of porcelain.split("\n")) {
|
|
302
|
+
const trimmed = line.trimEnd();
|
|
303
|
+
if (trimmed.startsWith("worktree ")) {
|
|
304
|
+
if (cur) entries.push(cur);
|
|
305
|
+
cur = { path: trimmed.slice("worktree ".length).trim(), branch: null };
|
|
306
|
+
} else if (trimmed.startsWith("branch ") && cur) {
|
|
307
|
+
cur.branch = trimmed
|
|
308
|
+
.slice("branch ".length)
|
|
309
|
+
.replace("refs/heads/", "")
|
|
310
|
+
.trim();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (cur) entries.push(cur);
|
|
314
|
+
return entries;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Best-effort live git worktree list for the current repo. */
|
|
318
|
+
export function collectWorktrees() {
|
|
319
|
+
return parseGitWorktrees(safeExec("git worktree list --porcelain"));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Map a doctor report + ambient runtime signals into the input contract of
|
|
324
|
+
* `buildDiagnosticBundle` (src/lib/diagnostic-bundle.js).
|
|
325
|
+
*
|
|
326
|
+
* Pure & deterministic aside from reading this process's own platform tags:
|
|
327
|
+
* the caller gathers live signals (env, worktrees, lockfiles) and passes them
|
|
328
|
+
* in, so the mapping itself is unit-testable. Session-runtime fields
|
|
329
|
+
* (connectionState, reconnectHistory, trace, redactionEvents) are absent from a
|
|
330
|
+
* cold `cc doctor` invocation and left to the bundle builder's safe defaults.
|
|
331
|
+
*/
|
|
332
|
+
export function buildDoctorDiagnosticInput(report, signals = {}) {
|
|
333
|
+
const r = report || {};
|
|
334
|
+
return {
|
|
335
|
+
version: r.version || null,
|
|
336
|
+
platform: {
|
|
337
|
+
os: process.platform,
|
|
338
|
+
arch: process.arch,
|
|
339
|
+
node: process.versions?.node || null,
|
|
340
|
+
},
|
|
341
|
+
ports: (Array.isArray(r.ports) ? r.ports : [])
|
|
342
|
+
.filter((p) => p && p.open)
|
|
343
|
+
.map((p) => ({ port: p.port, proto: p.name })),
|
|
344
|
+
worktrees: Array.isArray(signals.worktrees) ? signals.worktrees : [],
|
|
345
|
+
lockfiles: Array.isArray(signals.lockfiles) ? signals.lockfiles : [],
|
|
346
|
+
env: signals.env && typeof signals.env === "object" ? signals.env : {},
|
|
347
|
+
notes: signals.notes != null ? signals.notes : null,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
291
351
|
/**
|
|
292
352
|
* Collect a machine-readable status report.
|
|
293
353
|
*
|
|
@@ -44,11 +44,20 @@ import {
|
|
|
44
44
|
appendTokenUsage as jsonlAppendTokenUsage,
|
|
45
45
|
appendToolCallCompact as jsonlAppendToolCallCompact,
|
|
46
46
|
appendCompactEvent as jsonlAppendCompactEvent,
|
|
47
|
+
appendEvent as jsonlAppendEvent,
|
|
48
|
+
readEvents as jsonlReadEvents,
|
|
47
49
|
rebuildMessages as jsonlRebuildMessages,
|
|
48
50
|
sessionExists as jsonlSessionExists,
|
|
49
51
|
getLastSessionId as jsonlGetLastSessionId,
|
|
50
52
|
verifySession as jsonlVerifySession,
|
|
51
53
|
} from "../harness/jsonl-session-store.js";
|
|
54
|
+
import {
|
|
55
|
+
SideEffectLedger,
|
|
56
|
+
reconcileSideEffects,
|
|
57
|
+
classifyToolSideEffect,
|
|
58
|
+
} from "../lib/side-effect-ledger.js";
|
|
59
|
+
import { SIDE_EFFECT_LEDGER_EVENT } from "../lib/side-effect-ledger-store.js";
|
|
60
|
+
import { operationIdempotencyKey } from "../lib/idempotency.js";
|
|
52
61
|
import { expandFileRefsAsync } from "./file-ref-expander.js";
|
|
53
62
|
import { composeSystemPrompt } from "./system-prompt.js";
|
|
54
63
|
import { buildUserContent } from "../lib/image-input.js";
|
|
@@ -461,6 +470,8 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
461
470
|
appendToolCallCompact:
|
|
462
471
|
deps.appendToolCallCompact || jsonlAppendToolCallCompact,
|
|
463
472
|
appendCompactEvent: deps.appendCompactEvent || jsonlAppendCompactEvent,
|
|
473
|
+
appendEvent: deps.appendEvent || jsonlAppendEvent,
|
|
474
|
+
readEvents: deps.readEvents || jsonlReadEvents,
|
|
464
475
|
getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
|
|
465
476
|
verifySession: deps.verifySession || jsonlVerifySession,
|
|
466
477
|
};
|
|
@@ -684,6 +695,81 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
684
695
|
}
|
|
685
696
|
}
|
|
686
697
|
|
|
698
|
+
// ── P0-2: crash-safe side-effect ledger ────────────────────────────────
|
|
699
|
+
// Dangerous tools (file writes, opaque shell, git push, publish/schedule/
|
|
700
|
+
// notify/browser actions) are recorded prepare→start→commit|fail and the
|
|
701
|
+
// full snapshot persisted before the effect settles, so a worker killed
|
|
702
|
+
// mid-flight does NOT blindly replay an effect that may already have landed.
|
|
703
|
+
// Only active when persisting (an ephemeral/one-shot run can't be resumed);
|
|
704
|
+
// the byte-for-byte default path is untouched when no dangerous tool runs.
|
|
705
|
+
const sideEffectRunNonce = String(deps.now ? deps.now() : Date.now());
|
|
706
|
+
let sideEffectLedger = new SideEffectLedger({ clock: deps.now || null });
|
|
707
|
+
let sideEffectSeq = 0;
|
|
708
|
+
let currentSideEffectOpId = null;
|
|
709
|
+
let resumeSideEffectContext = null;
|
|
710
|
+
const persistSideEffectLedger = () => {
|
|
711
|
+
if (!persist) return;
|
|
712
|
+
try {
|
|
713
|
+
store.appendEvent(
|
|
714
|
+
sessionId,
|
|
715
|
+
SIDE_EFFECT_LEDGER_EVENT,
|
|
716
|
+
sideEffectLedger.toJSON(),
|
|
717
|
+
);
|
|
718
|
+
} catch {
|
|
719
|
+
// best-effort — never fail the run over ledger persistence
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
if (persist) {
|
|
723
|
+
try {
|
|
724
|
+
const events = store.readEvents(sessionId) || [];
|
|
725
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
726
|
+
const e = events[i];
|
|
727
|
+
if (
|
|
728
|
+
e &&
|
|
729
|
+
e.type === SIDE_EFFECT_LEDGER_EVENT &&
|
|
730
|
+
e.data &&
|
|
731
|
+
Array.isArray(e.data.ops)
|
|
732
|
+
) {
|
|
733
|
+
sideEffectLedger = SideEffectLedger.fromJSON(e.data, {
|
|
734
|
+
clock: deps.now || null,
|
|
735
|
+
});
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} catch {
|
|
740
|
+
// fresh ledger — best effort
|
|
741
|
+
}
|
|
742
|
+
// On resume, surface any operation that was in flight when the prior run
|
|
743
|
+
// died: its outcome is UNKNOWN, so the model is told to VERIFY before any
|
|
744
|
+
// replay rather than silently re-issue an irreversible effect.
|
|
745
|
+
if (resumeId) {
|
|
746
|
+
try {
|
|
747
|
+
const plan = reconcileSideEffects(sideEffectLedger);
|
|
748
|
+
if (plan.inspect.length > 0) {
|
|
749
|
+
const lines = plan.plans
|
|
750
|
+
.filter((p) => p.action === "inspect")
|
|
751
|
+
.map((p) => {
|
|
752
|
+
const op = sideEffectLedger.get(p.opId);
|
|
753
|
+
const kind = op?.kind || "unknown";
|
|
754
|
+
const key = op?.key ? ` (${op.key})` : "";
|
|
755
|
+
return ` • [${kind}]${key} — ${p.reason}`;
|
|
756
|
+
});
|
|
757
|
+
resumeSideEffectContext =
|
|
758
|
+
"Recovery notice — the previous run was interrupted while these " +
|
|
759
|
+
"irreversible operations were in flight; their outcome is UNKNOWN. " +
|
|
760
|
+
"Do NOT blindly re-run them. Verify whether each already took " +
|
|
761
|
+
"effect before repeating it, and ask the user if unsure:\n" +
|
|
762
|
+
lines.join("\n");
|
|
763
|
+
writeErr(
|
|
764
|
+
`⚠ ${plan.inspect.length} interrupted side-effect(s) need verification before replay (resume ${resumeId}).\n`,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
} catch {
|
|
768
|
+
resumeSideEffectContext = null;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
687
773
|
// ── Wire the persistent ApprovalGate with our non-interactive confirmer
|
|
688
774
|
// and force the session-policy tier dictated by --permission-mode. ──────
|
|
689
775
|
let approvalGate = null;
|
|
@@ -744,12 +830,35 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
744
830
|
outputStyleBody = null;
|
|
745
831
|
}
|
|
746
832
|
|
|
833
|
+
// Large-monorepo context lever: `instructionExcludes` (settings.json or an
|
|
834
|
+
// explicit caller/SDK option) suppresses cc.md/CLAUDE.md/AGENTS.md, path-scoped
|
|
835
|
+
// rules, and @imports that resolve into legacy/vendor/generated subtrees.
|
|
836
|
+
// Explicit option wins; otherwise union across the layered settings files.
|
|
837
|
+
let instructionExcludes = Array.isArray(options.instructionExcludes)
|
|
838
|
+
? options.instructionExcludes
|
|
839
|
+
: null;
|
|
840
|
+
if (!instructionExcludes) {
|
|
841
|
+
try {
|
|
842
|
+
const { readStringArraySetting } =
|
|
843
|
+
await import("../lib/settings-loader.cjs");
|
|
844
|
+
const fromSettings = readStringArraySetting("instructionExcludes", {
|
|
845
|
+
cwd,
|
|
846
|
+
settingsFile: options.settingsFile,
|
|
847
|
+
});
|
|
848
|
+
if (fromSettings && fromSettings.length)
|
|
849
|
+
instructionExcludes = fromSettings;
|
|
850
|
+
} catch {
|
|
851
|
+
instructionExcludes = null; // fail-open
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
747
855
|
const systemContent = composeSystemPrompt(
|
|
748
856
|
buildSystemPrompt(cwd, { additionalDirectories }),
|
|
749
857
|
{
|
|
750
858
|
systemPrompt: options.systemPrompt,
|
|
751
859
|
appendSystemPrompt: options.appendSystemPrompt,
|
|
752
860
|
outputStyle: outputStyleBody,
|
|
861
|
+
instructionExcludes,
|
|
753
862
|
},
|
|
754
863
|
);
|
|
755
864
|
|
|
@@ -836,6 +945,9 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
836
945
|
...(sessionStartContext
|
|
837
946
|
? [{ role: "system", content: sessionStartContext }]
|
|
838
947
|
: []),
|
|
948
|
+
...(resumeSideEffectContext
|
|
949
|
+
? [{ role: "system", content: resumeSideEffectContext }]
|
|
950
|
+
: []),
|
|
839
951
|
...history,
|
|
840
952
|
{ role: "user", content: userMessageContent },
|
|
841
953
|
]);
|
|
@@ -1040,7 +1152,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1040
1152
|
try {
|
|
1041
1153
|
const { AsyncHookSupervisor } =
|
|
1042
1154
|
await import("../lib/async-hook-supervisor.cjs");
|
|
1043
|
-
_hookSupervisor = new AsyncHookSupervisor();
|
|
1155
|
+
_hookSupervisor = new AsyncHookSupervisor({ persistStats: true });
|
|
1044
1156
|
} catch {
|
|
1045
1157
|
_hookSupervisor = null; // async hooks are best-effort
|
|
1046
1158
|
}
|
|
@@ -1068,6 +1180,10 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1068
1180
|
model,
|
|
1069
1181
|
provider,
|
|
1070
1182
|
recorder: _otlpRecorder,
|
|
1183
|
+
// --otlp-content: opt in to stamping prompt CONTENT onto --otlp spans. Off by
|
|
1184
|
+
// default → content stays redacted (agent-core omits it entirely, so default
|
|
1185
|
+
// OTLP output is byte-identical). Only meaningful when a recorder is attached.
|
|
1186
|
+
otlpIncludeContent: options.otlpContent === true,
|
|
1071
1187
|
hookSupervisor: _hookSupervisor,
|
|
1072
1188
|
// Extended thinking (Anthropic; opt-in via --think/--ultrathink). null/off
|
|
1073
1189
|
// → chatWithTools sends no thinking field. thinkingBudget (--thinking-budget)
|
|
@@ -1091,6 +1207,13 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1091
1207
|
approvalGate,
|
|
1092
1208
|
permissionRules,
|
|
1093
1209
|
settingsHooks,
|
|
1210
|
+
// Seed the subagent-contract CEILING with this run's permission mode so a
|
|
1211
|
+
// spawned sub-agent inherits/tightens from it (tighten-only): a
|
|
1212
|
+
// `--permission-mode bypassPermissions` run can hand a child bypass (→ allow
|
|
1213
|
+
// confirmer), a `--permission-mode plan` run clamps children to read-only,
|
|
1214
|
+
// while a "default" run resolves children to "default" exactly as before
|
|
1215
|
+
// (byte-identical — the previous null ceiling also yielded "default").
|
|
1216
|
+
subAgentContract: { permissionMode: options.permissionMode || "default" },
|
|
1094
1217
|
classifyAllShell,
|
|
1095
1218
|
enabledToolNames,
|
|
1096
1219
|
disabledTools,
|
|
@@ -1278,6 +1401,138 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1278
1401
|
};
|
|
1279
1402
|
};
|
|
1280
1403
|
|
|
1404
|
+
// --goal-condition: session-level completion-condition engine (P1). After each
|
|
1405
|
+
// outer turn an independent checker judges a completion CONDITION; if unmet and
|
|
1406
|
+
// budget remains, a follow-up user turn re-drives the agent — until met
|
|
1407
|
+
// (goal_completed) or a budget is exhausted (goal_exhausted). OPT-IN: when
|
|
1408
|
+
// unset, `goalEngine` is null and the outer loop runs exactly once, so one-shot
|
|
1409
|
+
// `cc agent -p` behavior is byte-for-byte unchanged.
|
|
1410
|
+
let goalEngine = null;
|
|
1411
|
+
let _goalHelpers = null;
|
|
1412
|
+
let _goalTokensSeen = 0; // per-turn usage deltas (engine totals == run total)
|
|
1413
|
+
let _goalCostSeen = 0;
|
|
1414
|
+
// Model-judged conditions reuse the run's own model as an independent
|
|
1415
|
+
// evaluator (mirrors --goal-assess). Overridable via deps for tests.
|
|
1416
|
+
const _defaultGoalJudge = async (cond, transcript) => {
|
|
1417
|
+
const { chatWithTools } = await import("./agent-core.js");
|
|
1418
|
+
const { firstBalancedJson } = await import("../lib/json-schema-output.js");
|
|
1419
|
+
const judgePrompt =
|
|
1420
|
+
`You are judging whether a coding session met a completion condition.\n` +
|
|
1421
|
+
`Condition: ${cond.text || cond.source}\n\n` +
|
|
1422
|
+
`Latest assistant output:\n${String(transcript.finalText || "").slice(0, 2000)}\n\n` +
|
|
1423
|
+
`Reply with STRICT JSON only: {"met": true|false, "reason": "<short>"}.`;
|
|
1424
|
+
const r = await chatWithTools([{ role: "user", content: judgePrompt }], {
|
|
1425
|
+
model,
|
|
1426
|
+
provider,
|
|
1427
|
+
baseUrl,
|
|
1428
|
+
apiKey,
|
|
1429
|
+
enabledToolNames: [],
|
|
1430
|
+
});
|
|
1431
|
+
const text = r?.message?.content || "";
|
|
1432
|
+
let met = false;
|
|
1433
|
+
let reason = "model judge returned no verdict";
|
|
1434
|
+
const block = firstBalancedJson(text, "{");
|
|
1435
|
+
if (block) {
|
|
1436
|
+
try {
|
|
1437
|
+
const parsed = JSON.parse(block);
|
|
1438
|
+
met = parsed.met === true;
|
|
1439
|
+
if (typeof parsed.reason === "string" && parsed.reason.trim())
|
|
1440
|
+
reason = parsed.reason.trim();
|
|
1441
|
+
else reason = met ? "condition met" : "condition not met";
|
|
1442
|
+
} catch {
|
|
1443
|
+
/* tolerant: a non-JSON verdict stays unmet */
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return {
|
|
1447
|
+
met,
|
|
1448
|
+
reason,
|
|
1449
|
+
evidence: { kind: "model", raw: text.slice(0, 200) },
|
|
1450
|
+
};
|
|
1451
|
+
};
|
|
1452
|
+
// Persist the engine snapshot as a hash-chained `goal_snapshot` session event
|
|
1453
|
+
// (best-effort, only when persisting). Re-read on --resume so an unfinished
|
|
1454
|
+
// goal continues across processes with its outerTurns/tokens/cost/startedAtMs
|
|
1455
|
+
// intact. rebuildMessages ignores this event type, so it never pollutes model
|
|
1456
|
+
// context. Defined here so the outer loop can call it after each evaluate().
|
|
1457
|
+
const persistGoalSnapshot = () => {
|
|
1458
|
+
if (!persist || !goalEngine) return;
|
|
1459
|
+
try {
|
|
1460
|
+
store.appendEvent(sessionId, "goal_snapshot", goalEngine.snapshot());
|
|
1461
|
+
} catch {
|
|
1462
|
+
// best-effort — never fail the run over persistence
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
if (options.goalCondition) {
|
|
1466
|
+
try {
|
|
1467
|
+
const eng = await import("../lib/goal-condition-engine.js");
|
|
1468
|
+
const goalNow = () => (deps.now ? deps.now() : Date.now());
|
|
1469
|
+
// Cross-process resume: if the resumed session persisted an UNFINISHED
|
|
1470
|
+
// goal, continue it (fromSnapshot keeps accumulated progress + startedAtMs)
|
|
1471
|
+
// rather than starting fresh. A finished (done) snapshot is ignored — the
|
|
1472
|
+
// prior goal already concluded, so --goal-condition begins a new cycle. On
|
|
1473
|
+
// restore the persisted condition/budget win over freshly-passed flags, so
|
|
1474
|
+
// a resume faithfully continues the same goal.
|
|
1475
|
+
let restoredSnap = null;
|
|
1476
|
+
if (resumeId) {
|
|
1477
|
+
try {
|
|
1478
|
+
const events = store.readEvents(resumeId) || [];
|
|
1479
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
1480
|
+
const ev = events[i];
|
|
1481
|
+
if (ev && ev.type === "goal_snapshot") {
|
|
1482
|
+
const snap = ev.data;
|
|
1483
|
+
if (snap && snap.state && snap.state.done !== true)
|
|
1484
|
+
restoredSnap = snap;
|
|
1485
|
+
break; // only the latest goal_snapshot matters
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
} catch {
|
|
1489
|
+
restoredSnap = null; // unreadable transcript → start fresh
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (restoredSnap) {
|
|
1493
|
+
goalEngine = eng.GoalConditionEngine.fromSnapshot(restoredSnap, {
|
|
1494
|
+
now: goalNow,
|
|
1495
|
+
});
|
|
1496
|
+
} else {
|
|
1497
|
+
goalEngine = new eng.GoalConditionEngine({
|
|
1498
|
+
condition: options.goalCondition,
|
|
1499
|
+
budget: {
|
|
1500
|
+
maxOuterTurns: options.maxOuterTurns,
|
|
1501
|
+
maxTokens: options.goalMaxTokens,
|
|
1502
|
+
maxCostUsd: options.goalMaxCostUsd,
|
|
1503
|
+
maxTimeMs: options.goalMaxTimeMs,
|
|
1504
|
+
},
|
|
1505
|
+
now: goalNow,
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
_goalHelpers = {
|
|
1509
|
+
isDeterministicCondition: eng.isDeterministicCondition,
|
|
1510
|
+
runDeterministicCheck: eng.runDeterministicCheck,
|
|
1511
|
+
GOAL_DECISION: eng.GOAL_DECISION,
|
|
1512
|
+
};
|
|
1513
|
+
const started = goalEngine.start();
|
|
1514
|
+
emitStream({
|
|
1515
|
+
type: started.type,
|
|
1516
|
+
condition: started.condition,
|
|
1517
|
+
budget: started.budget,
|
|
1518
|
+
resumed: Boolean(restoredSnap),
|
|
1519
|
+
});
|
|
1520
|
+
persistGoalSnapshot(); // checkpoint the starting/restored state
|
|
1521
|
+
if (isText)
|
|
1522
|
+
writeErr(
|
|
1523
|
+
` ◎ goal-condition ${restoredSnap ? "resumed" : "active"}: ` +
|
|
1524
|
+
`${goalEngine.condition.source} ` +
|
|
1525
|
+
`(outer turn ${goalEngine.state.outerTurns + 1}/` +
|
|
1526
|
+
`${goalEngine.budget.maxOuterTurns})\n`,
|
|
1527
|
+
);
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
// A bad spec should have been rejected at the command layer; fail-open so
|
|
1530
|
+
// a malformed condition never aborts an otherwise-valid run.
|
|
1531
|
+
goalEngine = null;
|
|
1532
|
+
if (isText) writeErr(` goal-condition ignored: ${err.message}\n`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1281
1536
|
// A Ctrl-C (SIGINT) / SIGTERM terminates Node WITHOUT unwinding the `finally`
|
|
1282
1537
|
// below, so its background-task reaper is bypassed — a backgrounded run_shell
|
|
1283
1538
|
// task (e.g. a dev server) this run spawned would be orphaned. Install a
|
|
@@ -1322,10 +1577,50 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1322
1577
|
args: event.args,
|
|
1323
1578
|
});
|
|
1324
1579
|
toolCalls.push({ tool: event.tool, args: event.args });
|
|
1580
|
+
// P0-2: record an irreversible effect as STARTED (persisted before
|
|
1581
|
+
// it settles) so a crash before the matching tool-result leaves a
|
|
1582
|
+
// reconcilable "in flight" marker instead of a silent replay.
|
|
1583
|
+
currentSideEffectOpId = null;
|
|
1584
|
+
if (persist) {
|
|
1585
|
+
const se = classifyToolSideEffect(event.tool, event.args);
|
|
1586
|
+
if (se) {
|
|
1587
|
+
const opId = `${sideEffectRunNonce}:${sideEffectSeq++}`;
|
|
1588
|
+
currentSideEffectOpId = opId;
|
|
1589
|
+
sideEffectLedger
|
|
1590
|
+
.prepare(opId, {
|
|
1591
|
+
kind: se.kind,
|
|
1592
|
+
key: se.key,
|
|
1593
|
+
// Content-addressed key: a resumed replay of the SAME effect
|
|
1594
|
+
// derives the SAME key, so an external provider can de-dupe
|
|
1595
|
+
// and countDuplicateCommittedEffects can measure `0` repeats.
|
|
1596
|
+
meta: {
|
|
1597
|
+
tool: event.tool,
|
|
1598
|
+
idempotencyKey: operationIdempotencyKey({
|
|
1599
|
+
tool: event.tool,
|
|
1600
|
+
args: event.args,
|
|
1601
|
+
}),
|
|
1602
|
+
},
|
|
1603
|
+
})
|
|
1604
|
+
.start(opId);
|
|
1605
|
+
persistSideEffectLedger();
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1325
1608
|
break;
|
|
1326
1609
|
}
|
|
1327
1610
|
case "tool-result": {
|
|
1328
1611
|
const err = event.error || event.result?.error || null;
|
|
1612
|
+
// P0-2: settle the in-flight side-effect (commit on success, fail on
|
|
1613
|
+
// a clean error) and persist the updated ledger snapshot.
|
|
1614
|
+
if (persist && currentSideEffectOpId) {
|
|
1615
|
+
if (err)
|
|
1616
|
+
sideEffectLedger.fail(
|
|
1617
|
+
currentSideEffectOpId,
|
|
1618
|
+
String(err).slice(0, 200),
|
|
1619
|
+
);
|
|
1620
|
+
else sideEffectLedger.commit(currentSideEffectOpId);
|
|
1621
|
+
persistSideEffectLedger();
|
|
1622
|
+
currentSideEffectOpId = null;
|
|
1623
|
+
}
|
|
1329
1624
|
if (isText && err) writeErr(` Error: ${err}\n`);
|
|
1330
1625
|
emitStream({
|
|
1331
1626
|
type: "tool_result",
|
|
@@ -1492,6 +1787,103 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1492
1787
|
);
|
|
1493
1788
|
}
|
|
1494
1789
|
}
|
|
1790
|
+
// ── goal-condition re-drive (opt-in via --goal-condition) ─────────────
|
|
1791
|
+
// After the turn settles (and any auto-rewake fix), evaluate the session
|
|
1792
|
+
// completion condition. Unmet + budget remaining → append a follow-up user
|
|
1793
|
+
// turn and re-drive; met → goal_completed; budget spent → goal_exhausted.
|
|
1794
|
+
// Cost / max-turns exhaustion of the INNER loop stops here regardless.
|
|
1795
|
+
if (
|
|
1796
|
+
goalEngine &&
|
|
1797
|
+
_goalHelpers &&
|
|
1798
|
+
!goalEngine.done &&
|
|
1799
|
+
!stopForCost &&
|
|
1800
|
+
endReason !== "cost-budget-exhausted" &&
|
|
1801
|
+
endReason !== "max_turns"
|
|
1802
|
+
) {
|
|
1803
|
+
// Feed this turn's usage DELTA so the engine's totals equal the run's
|
|
1804
|
+
// cumulative usage (recordTurnUsage accumulates).
|
|
1805
|
+
const curTokens =
|
|
1806
|
+
usage.input_tokens +
|
|
1807
|
+
usage.output_tokens +
|
|
1808
|
+
usage.cache_read_input_tokens +
|
|
1809
|
+
usage.cache_creation_input_tokens;
|
|
1810
|
+
const curCost = costBudget ? Number(costBudget.spentUsd) || 0 : 0;
|
|
1811
|
+
goalEngine.recordTurnUsage({
|
|
1812
|
+
tokens: curTokens - _goalTokensSeen,
|
|
1813
|
+
costUsd: curCost - _goalCostSeen,
|
|
1814
|
+
});
|
|
1815
|
+
_goalTokensSeen = curTokens;
|
|
1816
|
+
_goalCostSeen = curCost;
|
|
1817
|
+
|
|
1818
|
+
const cond = goalEngine.condition;
|
|
1819
|
+
let evaluation;
|
|
1820
|
+
try {
|
|
1821
|
+
if (_goalHelpers.isDeterministicCondition(cond)) {
|
|
1822
|
+
const gc = deps.goalCheck || {};
|
|
1823
|
+
const spawnSync =
|
|
1824
|
+
gc.spawnSync || (await import("node:child_process")).spawnSync;
|
|
1825
|
+
const existsSync =
|
|
1826
|
+
gc.existsSync || (await import("node:fs")).existsSync;
|
|
1827
|
+
evaluation = _goalHelpers.runDeterministicCheck(cond, {
|
|
1828
|
+
spawnSync,
|
|
1829
|
+
existsSync,
|
|
1830
|
+
cwd,
|
|
1831
|
+
lastOutput: finalText,
|
|
1832
|
+
});
|
|
1833
|
+
} else {
|
|
1834
|
+
const judge = deps.goalConditionJudge || _defaultGoalJudge;
|
|
1835
|
+
evaluation = await judge(cond, {
|
|
1836
|
+
prompt: options.prompt,
|
|
1837
|
+
finalText,
|
|
1838
|
+
toolCalls,
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
evaluation = {
|
|
1843
|
+
met: false,
|
|
1844
|
+
reason: `goal check failed: ${err.message}`,
|
|
1845
|
+
evidence: { error: true },
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
const { decision, events } = goalEngine.evaluate(evaluation);
|
|
1850
|
+
for (const ev of events) {
|
|
1851
|
+
emitStream(ev);
|
|
1852
|
+
if (isText) {
|
|
1853
|
+
if (ev.type === "goal_completed")
|
|
1854
|
+
writeErr(` ✔ goal-condition met: ${ev.reason}\n`);
|
|
1855
|
+
else if (ev.type === "goal_exhausted")
|
|
1856
|
+
writeErr(
|
|
1857
|
+
` ⛔ goal-condition unmet (${ev.limit}): ${ev.reason}\n`,
|
|
1858
|
+
);
|
|
1859
|
+
else if (ev.type === "goal_evaluated")
|
|
1860
|
+
writeErr(
|
|
1861
|
+
` ◎ goal-condition ${ev.met ? "met" : "not met"}: ${ev.reason}\n`,
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
// Checkpoint the post-evaluate state (outerTurns incremented, and
|
|
1866
|
+
// done/outcome on a terminal decision) so a crash or Ctrl-C between here
|
|
1867
|
+
// and the next turn still resumes at the right point.
|
|
1868
|
+
persistGoalSnapshot();
|
|
1869
|
+
if (decision === _goalHelpers.GOAL_DECISION.CONTINUE) {
|
|
1870
|
+
messages.push({
|
|
1871
|
+
role: "user",
|
|
1872
|
+
content:
|
|
1873
|
+
`The completion condition is not yet met: ${evaluation.reason}.\n` +
|
|
1874
|
+
`Keep working toward: "${cond.source}". When you believe it is ` +
|
|
1875
|
+
`satisfied, make sure it actually passes.`,
|
|
1876
|
+
});
|
|
1877
|
+
finalText = "";
|
|
1878
|
+
endReason = "complete";
|
|
1879
|
+
if (isText)
|
|
1880
|
+
writeErr(
|
|
1881
|
+
` ↻ goal-condition re-drive — outer turn ${goalEngine.state.outerTurns + 1}\n`,
|
|
1882
|
+
);
|
|
1883
|
+
continue; // re-drive: run another outer turn
|
|
1884
|
+
}
|
|
1885
|
+
// complete | exhausted → fall through to the break below.
|
|
1886
|
+
}
|
|
1495
1887
|
break;
|
|
1496
1888
|
}
|
|
1497
1889
|
} catch (err) {
|