chainlesschain 0.162.180 → 0.162.182
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 +7 -3
- package/scripts/build-windows-sandbox-helper.ps1 +163 -0
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D_TIL54O.js → AIOps-DzZ5_xoj.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-eQwy9r-f.js → ActionButton-DeLXddIg.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D2eNuDgT.js → Analytics-rvDCmet1.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CFhwOFsf.js → AppLayout-Yt6QVDnp.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CJUMw6PB.js → Artifacts-BS112K5L.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CDB9V58o.js → Audit-0wJuQrNu.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-TBppYFjN.js → BackgroundAgents-B90PMgbn.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CYEOUpzJ.js → Backup-TkgMglQQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C88fFT2u.js → BaseInput-CQVS9p1l.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-mkr1vC3N.js → Chat-YTjQHoC7.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CVU4xqti.js → Checkbox-5bwaOabq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-rrNcByz3.js → Codegen-BFmNQ_Xe.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Cqu-zCvh.js → Col-BT_u4Dzp.js} +1 -1
- package/src/assets/web-panel/assets/{Community-RGRlSgXV.js → Community-CJP52u5b.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DBm6epOK.js → Compact-BpzhJE9i.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BgKw5iaW.js → Compliance-Cg7rfslp.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BqQcNO3Z.js → Cowork-JisxL6Yy.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DO5gTFNb.js → Cron-Bu7Ug_MC.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B8hbJNuN.js → Crosschain-CvuqoLdj.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B08bFJ2C.js → DID-C2u9G8Pu.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-xNdy_ifo.js → Dashboard-BFt5Yv3d.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D-KIZgWL.js → Dropdown-DIaT9Wxq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DKCvsaGo.js → EmailListRenderer-D_A601Y4.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D_cfqama.js → FamilyGuardDashboard--IyQ8I8w.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B-UqQD68.js → Federation-BihWHxuY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BWWvWuJW.js → FormItemContext-BB8dOXc_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CPchx6N7.js → GenericCardRenderer-DQYhj1HH.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Vx4co9Ff.js → Git-B-2QZxGf.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D4x0pp_m.js → Governance-CusSb-nG.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-T3KvwPYR.js → Inference-BAjmvPPL.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Ddv3KUv3.js → KnowledgeGraph-DoNYWKYq.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DfH-Dkae.js → Logs-B8fKWD-n.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DHW3PbCq.js → MarkdownRenderer-BlJm1J7E.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-tjgb0R3D.js → Marketplace-CyOscbSL.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DHAk5nYX.js → McpTools-Ex6Om5da.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-A2Yx7thM.js → Memory-CDcD8l8r.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BEmTVfN5.js → MobileBridge-xfBqoFDg.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DuFHUgE4.js → MobileProjects-BwObMMA2.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BDSll0OZ.js → Mtc-CIWd36OV.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CuBvluaS.js → MtcAudit-CPX0ouuA.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BmXAA-Sb.js → Multisig-BDJsIUuW.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-ci2stZAe.js → NLProgramming-DEkbuD3N.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-D-1RpqnS.js → Notes-DJpHRhH1.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0GyitIT.js → NotificationSettings-C1UZ-Qd5.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-U2bN-hj5.js → OrderTableRenderer-GlJtqLjT.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BNZgpPci.js → Organization-Bbhj0fac.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CDidKmUN.js → Overflow-1H04x3kx.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BnreBaXq.js → P2P-CDpPe3et.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DwJUxO9j.js → PdhVaultBrowser-MUX0Bpvw.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BQGgJPH0.js → Permissions-A07-0Ci1.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-k_fSkCwL.js → PersonalDataHub-DhhDWxAZ.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-WGqKMqau.js → Pipeline-CyfbJfe-.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DtHj9Ymq.js → Privacy-CNFps3ED.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-B3wH2SFC.js → ProjectInit--T-u3Dr5.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Cd0cgcA1.js → ProjectSettings-DJmrK2fC.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DTpxEXur.js → Providers-DLzzbOZd.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-B2v6gR-C.js → QrScannerModal-CexOomKS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-D3Nr9xXR.js → QuickAsk-QklGi19_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-C9inoMky.js → Recommend-Bb9kwSw8.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-DZuVNNN5.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-41KFG646.js → Reputation-Dr5cLP0N.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DqAaqqCv.js → Row-oCQHYv5t.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CMDpCDKc.js → RssFeed-IFDEykIR.js} +3 -3
- package/src/assets/web-panel/assets/{Search-B6mtegNL.js → Search-CspVIxDc.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BaePFVZ8.js → Security-Bm66o_DH.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DeG3Qscl.js → Services-DvanNqXl.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D_QP7F90.js → Skeleton-CHjEe9_O.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DLGyUgIA.js → Skills-Cb8jWjMX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BN2pxW1Q.js → Sla-DxZm1491.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BWsZPd8B.js → SpeechSettings-DWxEEclC.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-VOZRPODW.js → SyncSettings-FIvOFw5p.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +1 -0
- package/src/assets/web-panel/assets/{Templates-eJSob-q0.js → Templates-CaNjzV_t.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Bdwk8Rwy.js → Tenant-DjlP6TmH.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-D5qkbPKT.js → TimelineRenderer-DoZ8y7y8.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DKgwn8hI.js → Tokens-390cg2TC.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-HlEHniGM.js → Trigger-CLrtCj5_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CdPcsBpN.js → Trust-DpESfSPn.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-vAdXd-vG.js → UkeySign-CN6279O0.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9hHBYFnH.js → VideoEditing-DPtAUlSe.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dxh5d1yR.js → Wallet-nKOMV_1n.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BsFmLHkp.js → WebAuthn-HuXlSOc4.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Cn0OIwLn.js → WorkflowEditor-CUfFjARi.js} +1 -1
- package/src/assets/web-panel/assets/chat-6Eq0PcZJ.js +1 -0
- package/src/assets/web-panel/assets/{colors-Du9jzLN0.js → colors-BP_Sf6Iy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DhEscfzw.js → compact-item-BufMKIh5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CwzEfF3i.js → createContext-BBMXxWSW.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DT_GSE6J.js → hasIn-fUK_tRO2.js} +1 -1
- package/src/assets/web-panel/assets/{index-tND8-TDU.js → index-2A5ph8b2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8s5FxJY.js → index-3FuwBewR.js} +1 -1
- package/src/assets/web-panel/assets/{index-53z1YUxX.js → index-6GmBa4Kg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFD0cEna.js → index-B2vS66eI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQWxDOvM.js → index-B7Etk0Te.js} +1 -1
- package/src/assets/web-panel/assets/{index-VeHs6qH0.js → index-B9WYSn4m.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGvgnfl9.js → index-BHy6yNiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3llLkYM.js → index-BMLHv389.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dny_ZnAO.js → index-BWKoBU8R.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzjzrOli.js → index-Bkg4ZePE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DN60VBn5.js → index-C44atv4k.js} +1 -1
- package/src/assets/web-panel/assets/{index-v5HLYCVa.js → index-CE2DdTP2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BF-ZCVGE.js → index-CGjmILDm.js} +1 -1
- package/src/assets/web-panel/assets/{index-gFjYdstW.js → index-CW_G-MXw.js} +1 -1
- package/src/assets/web-panel/assets/{index-NA2oBLSB.js → index-CZ6Yt17L.js} +1 -1
- package/src/assets/web-panel/assets/{index-Mobw3PHs.js → index-CeASi4fr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWL3SYN3.js → index-CgiECHu2.js} +1 -1
- package/src/assets/web-panel/assets/{index-1LoutPJd.js → index-Cp8vhyZy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnHhFnwt.js → index-CwSf5pnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6QDWRLk.js → index-D4_MuGkd.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-QkW1fp.js → index-D9vU25ij.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4SN3by5.js → index-DASluSqY.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyEhUJcu.js → index-DLpIs_P_.js} +1 -1
- package/src/assets/web-panel/assets/index-DOZTSoS7.js +1 -0
- package/src/assets/web-panel/assets/{index-BrkJ7JPm.js → index-DPdUNk1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cnok8mhZ.js → index-DVsqvI4M.js} +1 -1
- package/src/assets/web-panel/assets/index-DhpvjpqI.js +1 -0
- package/src/assets/web-panel/assets/{index-CAVW0cWM.js → index-DmkMXwnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cc9aPkv-.js → index-DocdRcbd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnVVYKCE.js → index-Dyngbd29.js} +20 -20
- package/src/assets/web-panel/assets/{index-D8gt0j7r.js → index-GwdRUZ-x.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIYTjbv_.js → index-LvCBfTLo.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhXay17G.js → index-cpEli44X.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQWENaZf.js → index-fzaMtSJ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfRDyY1q.js → index-pum7HYBA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgSR2WMG.js → index-t8N1tSeX.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5IytTND.js → index-uCbEN7cY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtRwLhee.js → index-wKTLbKyc.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4kZoXNr.js → index-wV6iGdpY.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Cvt59c10.js → initDefaultProps-V5Fo95FM.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D15pPEzl.js → motion-BYWU0hUT.js} +1 -1
- package/src/assets/web-panel/assets/{move-N55KeXJ7.js → move-Cr1M-J2h.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CyFO1bYP.js → mtc-parser-BysQKi2N.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B5IXfxU2.js → omit-D1zHU8GC.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CpVqsulV.js → pickAttrs-CUArxtfi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-ME36sMF3.js → placementArrow-ChoYEys0.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BXAIR1r-.js → responsiveObserve-BQRs3Mo7.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BkOX3-Dp.js → slide-B8zbsCkg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cy4Aklpt.js → statusUtils-C2U8Gs6h.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-gUd2T206.js → styleChecker-Dd8MgL_V.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CaVD4o99.js → useFlexGapSupport-hknqY5NH.js} +1 -1
- package/src/assets/web-panel/assets/{useFs--pFGl0KD.js → useFs-CLTAmcfw.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D45iP86i.js → usePersonalDataHub-ByegHbiB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BayoChH_.js → vnode-DqlAy5DP.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CpP38poi.js → zoom-DiXiztQn.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +115 -3
- package/src/commands/agent.js +10 -1
- package/src/commands/background-session.js +2 -1
- package/src/commands/context.js +69 -2
- package/src/data/changelog.json +26 -0
- package/src/gateways/terminal/PtyManager.js +45 -4
- package/src/gateways/ws/background-agent-protocol.js +38 -0
- package/src/gateways/ws/message-dispatcher.js +1 -0
- package/src/gateways/ws/remote-session-protocol.js +65 -6
- package/src/gateways/ws/ws-server.js +18 -2
- package/src/harness/background-task-manager.js +70 -2
- package/src/harness/feature-flags.js +15 -8
- package/src/harness/mcp-client.js +618 -43
- package/src/harness/worktree-isolator.js +14 -3
- package/src/lazy-dispatch.js +30 -1
- package/src/lib/agent-authority.js +22 -0
- package/src/lib/agent-core.js +3 -0
- package/src/lib/agent-sandbox.js +161 -0
- package/src/lib/agent-schedule-store.js +45 -22
- package/src/lib/agent-team/team-runner.js +22 -0
- package/src/lib/artifact-store.js +35 -0
- package/src/lib/async-hook-supervisor.cjs +13 -2
- package/src/lib/audit-mtc.js +3 -2
- package/src/lib/background-agent-supervisor.js +104 -3
- package/src/lib/background-interaction-journal.js +407 -0
- package/src/lib/background-interaction-resolver.js +23 -34
- package/src/lib/background-session-transport.js +12 -2
- package/src/lib/capability-manifest.js +41 -3
- package/src/lib/capability-negotiation.js +1 -1
- package/src/lib/channels/channel-manager.js +33 -0
- package/src/lib/channels/telegram-channel.js +23 -5
- package/src/lib/channels/webhook-channel.js +38 -15
- package/src/lib/chrome-connector.js +169 -23
- package/src/lib/config-manager.js +23 -9
- package/src/lib/context-breakdown.js +31 -0
- package/src/lib/cowork-cron.js +274 -32
- package/src/lib/cross-chain-mtc.js +3 -2
- package/src/lib/durable-security-store.js +124 -0
- package/src/lib/event-runtime-host.js +67 -6
- package/src/lib/event-runtime-store.js +163 -0
- package/src/lib/goal-store.js +32 -12
- package/src/lib/headless-manifest.js +2 -1
- package/src/lib/hook-environment.cjs +58 -0
- package/src/lib/hook-manager.js +12 -5
- package/src/lib/hook-runner.cjs +122 -28
- package/src/lib/hook-runner.js +7 -2
- package/src/lib/hooks-v2-producers.js +36 -0
- package/src/lib/hooks-v2-runtime.js +329 -52
- package/src/lib/interaction-adapter.js +60 -19
- package/src/lib/interaction-binding.js +64 -0
- package/src/lib/json-schema-output.js +439 -14
- package/src/lib/json-schema-validate.js +514 -883
- package/src/lib/lan-pairing-tokens.js +115 -57
- package/src/lib/lsp/__tests__/lsp-client.test.js +32 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +25 -0
- package/src/lib/lsp/lsp-client.js +4 -0
- package/src/lib/lsp/lsp-manager.js +3 -0
- package/src/lib/lsp/lsp-server-registry.js +18 -0
- package/src/lib/mcp-client.js +1 -0
- package/src/lib/mcp-oauth.js +35 -21
- package/src/lib/packer/pkg-config-generator.js +3 -0
- package/src/lib/permission-decision.js +99 -0
- package/src/lib/plugin-monitor-supervisor.js +3 -0
- package/src/lib/plugin-runtime/bin.js +1148 -70
- package/src/lib/plugin-runtime/capabilities.js +50 -0
- package/src/lib/plugin-runtime/capability-consent.js +42 -35
- package/src/lib/plugin-runtime/hooks.js +36 -9
- package/src/lib/plugin-runtime/install.js +23 -7
- package/src/lib/plugin-runtime/lsp.js +4 -0
- package/src/lib/plugin-runtime/manifest.js +112 -8
- package/src/lib/plugin-runtime/mcp.js +30 -5
- package/src/lib/plugin-runtime/monitors.js +11 -0
- package/src/lib/plugin-runtime/plugin-options.js +94 -38
- package/src/lib/plugin-runtime/policy.js +29 -2
- package/src/lib/plugin-runtime/sandbox-policy.js +84 -0
- package/src/lib/plugin-runtime/scopes.js +27 -13
- package/src/lib/plugin-runtime/trust.js +36 -31
- package/src/lib/process-execution-broker/credential-agent.js +114 -16
- package/src/lib/process-execution-broker/credential-transport-worker.js +312 -0
- package/src/lib/process-execution-broker/credential-transport.js +378 -0
- package/src/lib/process-execution-broker/index.js +765 -49
- package/src/lib/process-execution-broker/platform-sandbox.js +4233 -138
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +3856 -0
- package/src/lib/project-mcp-trust.js +21 -26
- package/src/lib/protocol-replay.js +19 -12
- package/src/lib/repl-bang-memorize.js +67 -10
- package/src/lib/settings-hooks.cjs +13 -0
- package/src/lib/skill-loader.js +83 -10
- package/src/lib/slash-commands.js +27 -4
- package/src/lib/sync-credentials.js +5 -4
- package/src/lib/turn-binding.js +8 -6
- package/src/repl/agent-repl.js +117 -31
- package/src/repl/repl-turn-binding.js +5 -2
- package/src/runtime/agent-core.js +699 -70
- package/src/runtime/agent-runtime.js +13 -1
- package/src/runtime/coding-agent-contract-shared.cjs +3 -3
- package/src/runtime/headless-runner.js +105 -7
- package/src/runtime/headless-stream.js +209 -23
- package/src/runtime/mcp-config.js +29 -17
- package/src/runtime/policies/agent-policy.js +2 -0
- package/src/workers/background-agent-worker.js +128 -13
- package/src/assets/web-panel/assets/ChatBubbleRenderer-C7bofJuA.js +0 -1
- package/src/assets/web-panel/assets/Projects-BbgmyQnS.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Bqk3EYSu.js +0 -4
- package/src/assets/web-panel/assets/Tasks-3bDAUz8K.js +0 -1
- package/src/assets/web-panel/assets/Terminal-CsMLKIoV.js +0 -3
- package/src/assets/web-panel/assets/chat-CJ19O6Dh.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BBMFnsja.js +0 -1
- package/src/assets/web-panel/assets/index-Do8JMV4N.js +0 -1
- package/src/assets/web-panel/assets/index-UYAJ6iNM.js +0 -1
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
finishAgentWorktree,
|
|
21
21
|
validateAgentWorktree,
|
|
22
22
|
} from "./agent-worktree.js";
|
|
23
|
+
import { rejectPendingBackgroundInteractions } from "./background-interaction-journal.js";
|
|
23
24
|
import executionBroker from "./process-execution-broker/index.js";
|
|
24
25
|
|
|
25
26
|
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
|
@@ -464,6 +465,67 @@ export function effectiveBackgroundAgentState(state, options = {}) {
|
|
|
464
465
|
/* best-effort */
|
|
465
466
|
}
|
|
466
467
|
}
|
|
468
|
+
if (
|
|
469
|
+
options.persist !== false &&
|
|
470
|
+
state.sessionId &&
|
|
471
|
+
!isSameProcess(state.pid, state.startedAt)
|
|
472
|
+
) {
|
|
473
|
+
const pending = state.pendingQuestion;
|
|
474
|
+
const requestId = pending?.requestId || pending?.intId;
|
|
475
|
+
try {
|
|
476
|
+
const recovery = rejectPendingBackgroundInteractions(
|
|
477
|
+
state.sessionId,
|
|
478
|
+
state.id,
|
|
479
|
+
{
|
|
480
|
+
fallbackRequest: requestId
|
|
481
|
+
? {
|
|
482
|
+
requestId,
|
|
483
|
+
binding: pending.binding,
|
|
484
|
+
payload: {
|
|
485
|
+
kind: "question",
|
|
486
|
+
question: pending.question || pending.prompt || "",
|
|
487
|
+
options: pending.options || null,
|
|
488
|
+
multiSelect: pending.multiSelect === true,
|
|
489
|
+
timeoutMs: pending.timeoutMs,
|
|
490
|
+
},
|
|
491
|
+
createdAt: pending.askedAt,
|
|
492
|
+
}
|
|
493
|
+
: null,
|
|
494
|
+
code: "INTERACTION_WORKER_LOST",
|
|
495
|
+
message:
|
|
496
|
+
"The background worker was lost before the interaction settled",
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
const recoveredRecord = requestId
|
|
500
|
+
? recovery.journal.get(requestId)
|
|
501
|
+
: null;
|
|
502
|
+
if (
|
|
503
|
+
recovery.changed ||
|
|
504
|
+
(recoveredRecord && recoveredRecord.status !== "pending")
|
|
505
|
+
) {
|
|
506
|
+
next = {
|
|
507
|
+
...next,
|
|
508
|
+
phase: null,
|
|
509
|
+
pendingQuestion: null,
|
|
510
|
+
interactionRecovery: {
|
|
511
|
+
status: "rejected",
|
|
512
|
+
requestIds: recovery.rejected.map((record) => record.requestId),
|
|
513
|
+
recoveredAt: t,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
} catch (error) {
|
|
518
|
+
next = {
|
|
519
|
+
...next,
|
|
520
|
+
interactionRecovery: {
|
|
521
|
+
status: "failed",
|
|
522
|
+
code: error?.code || "INTERACTION_RECOVERY_FAILED",
|
|
523
|
+
message: error?.message || String(error),
|
|
524
|
+
recoveredAt: t,
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
467
529
|
if (options.persist !== false) {
|
|
468
530
|
try {
|
|
469
531
|
writeBackgroundAgentState(next);
|
|
@@ -838,11 +900,17 @@ export function launchBackgroundAgent({
|
|
|
838
900
|
};
|
|
839
901
|
writeBackgroundAgentState(state);
|
|
840
902
|
let child;
|
|
903
|
+
let workerLogFd;
|
|
841
904
|
try {
|
|
905
|
+
// Preserve loader/bootstrap failures that happen before the worker can
|
|
906
|
+
// open its own append handle. This also gives detached Windows targets
|
|
907
|
+
// concrete inheritable stdout/stderr handles instead of relying on the
|
|
908
|
+
// hosted process' possibly absent standard handles.
|
|
909
|
+
workerLogFd = openSync(job.logFile, "a");
|
|
842
910
|
child = _deps.spawn(process.execPath, [worker, jobFile], {
|
|
843
911
|
cwd,
|
|
844
912
|
detached: true,
|
|
845
|
-
stdio: "ignore",
|
|
913
|
+
stdio: ["ignore", workerLogFd, workerLogFd],
|
|
846
914
|
windowsHide: true,
|
|
847
915
|
origin: "background-agent:worker",
|
|
848
916
|
policy: "allow",
|
|
@@ -853,6 +921,10 @@ export function launchBackgroundAgent({
|
|
|
853
921
|
rmSync(jobFile, { force: true });
|
|
854
922
|
rmSync(statePath(id), { force: true });
|
|
855
923
|
throw error;
|
|
924
|
+
} finally {
|
|
925
|
+
if (Number.isInteger(workerLogFd)) {
|
|
926
|
+
closeSync(workerLogFd);
|
|
927
|
+
}
|
|
856
928
|
}
|
|
857
929
|
// Async spawn failures (EPERM, cwd raced away between the check and the
|
|
858
930
|
// spawn, …) arrive as an 'error' event on the detached child. Reap them
|
|
@@ -875,13 +947,42 @@ export function launchBackgroundAgent({
|
|
|
875
947
|
/* best-effort */
|
|
876
948
|
}
|
|
877
949
|
});
|
|
950
|
+
child.on("exit", (code, signal) => {
|
|
951
|
+
try {
|
|
952
|
+
rmSync(jobFile, { force: true });
|
|
953
|
+
const current = readBackgroundAgentState(id);
|
|
954
|
+
// A healthy worker persists completed/failed before exiting. If the
|
|
955
|
+
// process (or its platform wrapper) exits while the state still says
|
|
956
|
+
// running, bootstrap or finalization failed and the session must not
|
|
957
|
+
// remain a phantom live task until heartbeat reconciliation.
|
|
958
|
+
if (current && current.status === "running") {
|
|
959
|
+
writeBackgroundAgentState({
|
|
960
|
+
...current,
|
|
961
|
+
status: "failed",
|
|
962
|
+
endedAt: Date.now(),
|
|
963
|
+
exitCode: Number.isInteger(code) ? code : null,
|
|
964
|
+
signal: signal || null,
|
|
965
|
+
lostReason: `worker-exited-before-finalize: exit=${
|
|
966
|
+
Number.isInteger(code) ? code : "null"
|
|
967
|
+
} signal=${signal || "none"}`,
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
} catch {
|
|
971
|
+
/* best-effort */
|
|
972
|
+
}
|
|
973
|
+
});
|
|
878
974
|
}
|
|
879
975
|
child.unref();
|
|
880
976
|
const current = readBackgroundAgentState(id) || state;
|
|
881
977
|
const withPid = {
|
|
882
978
|
...current,
|
|
883
|
-
|
|
884
|
-
|
|
979
|
+
// A platform wrapper (notably the Windows Job Object adapter) can return
|
|
980
|
+
// from spawn after the real worker has already claimed the state file.
|
|
981
|
+
// Preserve that authoritative process.pid instead of racing it back to
|
|
982
|
+
// the wrapper pid. With an ordinary direct spawn both fields are still
|
|
983
|
+
// null here and are initialized from child.pid as before.
|
|
984
|
+
pid: current.pid ?? child.pid,
|
|
985
|
+
workerPid: current.workerPid ?? child.pid,
|
|
885
986
|
};
|
|
886
987
|
writeBackgroundAgentState(withPid);
|
|
887
988
|
return withPid;
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable pending/settlement journal for background human interactions.
|
|
3
|
+
*
|
|
4
|
+
* Each mutation appends a full snapshot to the session's tamper-evident JSONL
|
|
5
|
+
* transcript. The worker persists a terminal settlement before delivering it
|
|
6
|
+
* to the suspended child, so retries can be classified as the same idempotent
|
|
7
|
+
* answer or a conflicting second settlement even after a process restart.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import {
|
|
12
|
+
appendEvent as storeAppendEvent,
|
|
13
|
+
readEvents as storeReadEvents,
|
|
14
|
+
} from "../harness/jsonl-session-store.js";
|
|
15
|
+
import {
|
|
16
|
+
normalizeInteractionBinding,
|
|
17
|
+
sameInteractionBinding,
|
|
18
|
+
} from "./interaction-binding.js";
|
|
19
|
+
|
|
20
|
+
export const BACKGROUND_INTERACTION_JOURNAL_EVENT =
|
|
21
|
+
"background_interaction_journal";
|
|
22
|
+
export const BACKGROUND_INTERACTION_JOURNAL_VERSION = 1;
|
|
23
|
+
export const MAX_BACKGROUND_INTERACTION_RECORDS = 256;
|
|
24
|
+
|
|
25
|
+
const TERMINAL_STATUSES = new Set(["resolved", "rejected", "cancelled"]);
|
|
26
|
+
|
|
27
|
+
export const _deps = {
|
|
28
|
+
appendEvent: storeAppendEvent,
|
|
29
|
+
readEvents: storeReadEvents,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function journalError(message, code, details = {}) {
|
|
33
|
+
const error = new Error(message);
|
|
34
|
+
error.code = code;
|
|
35
|
+
Object.assign(error, details);
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function cloneJson(value) {
|
|
40
|
+
if (value === undefined) return null;
|
|
41
|
+
return JSON.parse(JSON.stringify(value));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function canonicalize(value) {
|
|
45
|
+
if (value === undefined) return "__undefined__";
|
|
46
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return `[${value.map((entry) => canonicalize(entry)).join(",")}]`;
|
|
49
|
+
}
|
|
50
|
+
return `{${Object.keys(value)
|
|
51
|
+
.sort()
|
|
52
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`)
|
|
53
|
+
.join(",")}}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function fingerprint(value) {
|
|
57
|
+
return createHash("sha256").update(canonicalize(value)).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeError(error, fallbackCode = "INTERACTION_REJECTED") {
|
|
61
|
+
if (!error) return null;
|
|
62
|
+
if (typeof error === "string") {
|
|
63
|
+
return { code: fallbackCode, message: error };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
code: String(error.code || fallbackCode),
|
|
67
|
+
message: String(error.message || error),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeSettlement(outcome = {}) {
|
|
72
|
+
const status = String(outcome.status || "");
|
|
73
|
+
if (!TERMINAL_STATUSES.has(status)) {
|
|
74
|
+
throw journalError(
|
|
75
|
+
`Invalid background interaction settlement status: ${status || "<missing>"}`,
|
|
76
|
+
"INTERACTION_SETTLEMENT_INVALID",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
status,
|
|
81
|
+
answer: status === "resolved" ? cloneJson(outcome.answer) : null,
|
|
82
|
+
error:
|
|
83
|
+
status === "resolved"
|
|
84
|
+
? null
|
|
85
|
+
: normalizeError(
|
|
86
|
+
outcome.error,
|
|
87
|
+
status === "cancelled"
|
|
88
|
+
? "INTERACTION_CANCELLED"
|
|
89
|
+
: "INTERACTION_REJECTED",
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class BackgroundInteractionJournal {
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
this.backgroundAgentId = String(options.backgroundAgentId || "");
|
|
97
|
+
if (!this.backgroundAgentId) {
|
|
98
|
+
throw journalError(
|
|
99
|
+
"backgroundAgentId is required for the interaction journal",
|
|
100
|
+
"INTERACTION_JOURNAL_INVALID",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
this._now = options.now || Date.now;
|
|
104
|
+
this.records = [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static fromJSON(snapshot, options = {}) {
|
|
108
|
+
if (
|
|
109
|
+
!snapshot ||
|
|
110
|
+
snapshot.version !== BACKGROUND_INTERACTION_JOURNAL_VERSION ||
|
|
111
|
+
typeof snapshot.backgroundAgentId !== "string" ||
|
|
112
|
+
!Array.isArray(snapshot.records)
|
|
113
|
+
) {
|
|
114
|
+
throw journalError(
|
|
115
|
+
"Invalid background interaction journal snapshot",
|
|
116
|
+
"INTERACTION_JOURNAL_CORRUPT",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const journal = new BackgroundInteractionJournal({
|
|
120
|
+
backgroundAgentId: snapshot.backgroundAgentId,
|
|
121
|
+
now: options.now,
|
|
122
|
+
});
|
|
123
|
+
journal.records = snapshot.records.map((record) => ({
|
|
124
|
+
requestId: String(record.requestId || ""),
|
|
125
|
+
binding: normalizeInteractionBinding(record.binding),
|
|
126
|
+
payload: cloneJson(record.payload || {}),
|
|
127
|
+
payloadFingerprint: String(record.payloadFingerprint || ""),
|
|
128
|
+
status: String(record.status || "pending"),
|
|
129
|
+
createdAt: Number(record.createdAt) || 0,
|
|
130
|
+
settledAt: record.settledAt == null ? null : Number(record.settledAt),
|
|
131
|
+
settlement: record.settlement ? cloneJson(record.settlement) : null,
|
|
132
|
+
settlementFingerprint: record.settlementFingerprint
|
|
133
|
+
? String(record.settlementFingerprint)
|
|
134
|
+
: null,
|
|
135
|
+
}));
|
|
136
|
+
return journal;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
toJSON() {
|
|
140
|
+
return {
|
|
141
|
+
version: BACKGROUND_INTERACTION_JOURNAL_VERSION,
|
|
142
|
+
backgroundAgentId: this.backgroundAgentId,
|
|
143
|
+
records: cloneJson(this.records),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get(requestId) {
|
|
148
|
+
return (
|
|
149
|
+
this.records.find((record) => record.requestId === String(requestId)) ||
|
|
150
|
+
null
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pending() {
|
|
155
|
+
return this.records.filter((record) => record.status === "pending");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
recordPending({ requestId, binding, payload = {}, createdAt } = {}) {
|
|
159
|
+
const id = String(requestId || "");
|
|
160
|
+
const normalizedBinding = normalizeInteractionBinding(binding);
|
|
161
|
+
if (!id || normalizedBinding.sequence === null) {
|
|
162
|
+
throw journalError(
|
|
163
|
+
"A requestId and complete interaction binding are required",
|
|
164
|
+
"INTERACTION_REQUEST_INVALID",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const safePayload = cloneJson(payload);
|
|
168
|
+
const payloadFingerprint = fingerprint(safePayload);
|
|
169
|
+
const existing = this.get(id);
|
|
170
|
+
if (existing) {
|
|
171
|
+
if (
|
|
172
|
+
sameInteractionBinding(existing.binding, normalizedBinding) &&
|
|
173
|
+
existing.payloadFingerprint === payloadFingerprint
|
|
174
|
+
) {
|
|
175
|
+
return { applied: false, record: cloneJson(existing) };
|
|
176
|
+
}
|
|
177
|
+
throw journalError(
|
|
178
|
+
`Interaction request ${id} was replayed with different binding or payload`,
|
|
179
|
+
"INTERACTION_REQUEST_CONFLICT",
|
|
180
|
+
{ requestId: id },
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const record = {
|
|
185
|
+
requestId: id,
|
|
186
|
+
binding: normalizedBinding,
|
|
187
|
+
payload: safePayload,
|
|
188
|
+
payloadFingerprint,
|
|
189
|
+
status: "pending",
|
|
190
|
+
createdAt: Number(createdAt) || this._now(),
|
|
191
|
+
settledAt: null,
|
|
192
|
+
settlement: null,
|
|
193
|
+
settlementFingerprint: null,
|
|
194
|
+
};
|
|
195
|
+
this.records.push(record);
|
|
196
|
+
this._prune();
|
|
197
|
+
return { applied: true, record: cloneJson(record) };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
settle(requestId, binding, outcome = {}) {
|
|
201
|
+
const id = String(requestId || "");
|
|
202
|
+
const record = this.get(id);
|
|
203
|
+
if (!record) {
|
|
204
|
+
throw journalError(
|
|
205
|
+
`Unknown background interaction request: ${id || "<missing>"}`,
|
|
206
|
+
"INTERACTION_REQUEST_UNKNOWN",
|
|
207
|
+
{ requestId: id },
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (!sameInteractionBinding(record.binding, binding)) {
|
|
211
|
+
throw journalError(
|
|
212
|
+
`Interaction response binding does not match request ${id}`,
|
|
213
|
+
"INTERACTION_BINDING_MISMATCH",
|
|
214
|
+
{ requestId: id },
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const settlement = normalizeSettlement(outcome);
|
|
218
|
+
const settlementFingerprint = fingerprint(settlement);
|
|
219
|
+
if (record.status !== "pending") {
|
|
220
|
+
if (record.settlementFingerprint === settlementFingerprint) {
|
|
221
|
+
return { applied: false, record: cloneJson(record) };
|
|
222
|
+
}
|
|
223
|
+
throw journalError(
|
|
224
|
+
`Interaction request ${id} is already settled`,
|
|
225
|
+
"INTERACTION_ALREADY_SETTLED",
|
|
226
|
+
{ requestId: id, status: record.status },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
record.status = settlement.status;
|
|
231
|
+
record.settledAt = this._now();
|
|
232
|
+
record.settlement = settlement;
|
|
233
|
+
record.settlementFingerprint = settlementFingerprint;
|
|
234
|
+
return { applied: true, record: cloneJson(record) };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
rejectPending(error = {}) {
|
|
238
|
+
const rejected = [];
|
|
239
|
+
for (const record of this.pending()) {
|
|
240
|
+
const result = this.settle(record.requestId, record.binding, {
|
|
241
|
+
status: "rejected",
|
|
242
|
+
error,
|
|
243
|
+
});
|
|
244
|
+
rejected.push(result.record);
|
|
245
|
+
}
|
|
246
|
+
return rejected;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
_prune() {
|
|
250
|
+
while (this.records.length > MAX_BACKGROUND_INTERACTION_RECORDS) {
|
|
251
|
+
const terminalIndex = this.records.findIndex((record) =>
|
|
252
|
+
TERMINAL_STATUSES.has(record.status),
|
|
253
|
+
);
|
|
254
|
+
if (terminalIndex === -1) {
|
|
255
|
+
throw journalError(
|
|
256
|
+
`Background interaction journal exceeded ${MAX_BACKGROUND_INTERACTION_RECORDS} pending requests`,
|
|
257
|
+
"INTERACTION_PENDING_LIMIT",
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
this.records.splice(terminalIndex, 1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export class BackgroundInteractionPersistenceError extends Error {
|
|
266
|
+
constructor(operation, sessionId, cause) {
|
|
267
|
+
const detail =
|
|
268
|
+
cause?.message || String(cause || "unknown persistence error");
|
|
269
|
+
super(
|
|
270
|
+
`Background interaction journal ${operation} failed for ${sessionId}: ${detail}`,
|
|
271
|
+
{ cause },
|
|
272
|
+
);
|
|
273
|
+
this.name = "BackgroundInteractionPersistenceError";
|
|
274
|
+
this.code =
|
|
275
|
+
operation === "read"
|
|
276
|
+
? "INTERACTION_JOURNAL_READ_FAILED"
|
|
277
|
+
: "INTERACTION_JOURNAL_PERSIST_FAILED";
|
|
278
|
+
this.operation = operation;
|
|
279
|
+
this.sessionId = sessionId;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function persistBackgroundInteractionJournal(
|
|
284
|
+
sessionId,
|
|
285
|
+
journal,
|
|
286
|
+
options = {},
|
|
287
|
+
) {
|
|
288
|
+
const failIfUnavailable = options.failIfUnavailable !== false;
|
|
289
|
+
try {
|
|
290
|
+
if (!sessionId || !(journal instanceof BackgroundInteractionJournal)) {
|
|
291
|
+
throw new TypeError("sessionId and interaction journal are required");
|
|
292
|
+
}
|
|
293
|
+
_deps.appendEvent(
|
|
294
|
+
sessionId,
|
|
295
|
+
BACKGROUND_INTERACTION_JOURNAL_EVENT,
|
|
296
|
+
journal.toJSON(),
|
|
297
|
+
);
|
|
298
|
+
return true;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (!failIfUnavailable) return false;
|
|
301
|
+
throw new BackgroundInteractionPersistenceError(
|
|
302
|
+
"write",
|
|
303
|
+
sessionId || "<missing-session>",
|
|
304
|
+
error,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function loadBackgroundInteractionJournal(
|
|
310
|
+
sessionId,
|
|
311
|
+
backgroundAgentId,
|
|
312
|
+
options = {},
|
|
313
|
+
) {
|
|
314
|
+
const failIfUnavailable = options.failIfUnavailable !== false;
|
|
315
|
+
try {
|
|
316
|
+
if (!sessionId || !backgroundAgentId) {
|
|
317
|
+
throw new TypeError("sessionId and backgroundAgentId are required");
|
|
318
|
+
}
|
|
319
|
+
const events = _deps.readEvents(sessionId) || [];
|
|
320
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
321
|
+
const event = events[index];
|
|
322
|
+
if (
|
|
323
|
+
event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT &&
|
|
324
|
+
event.data?.backgroundAgentId === backgroundAgentId
|
|
325
|
+
) {
|
|
326
|
+
return BackgroundInteractionJournal.fromJSON(event.data, options);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return new BackgroundInteractionJournal({
|
|
330
|
+
backgroundAgentId,
|
|
331
|
+
now: options.now,
|
|
332
|
+
});
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (!failIfUnavailable) {
|
|
335
|
+
return new BackgroundInteractionJournal({
|
|
336
|
+
backgroundAgentId,
|
|
337
|
+
now: options.now,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
throw new BackgroundInteractionPersistenceError(
|
|
341
|
+
"read",
|
|
342
|
+
sessionId || "<missing-session>",
|
|
343
|
+
error,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Clone-mutate-persist transaction. The live journal is not changed when the
|
|
350
|
+
* append fails, so a caller may safely retry the same settlement.
|
|
351
|
+
*/
|
|
352
|
+
export function updateBackgroundInteractionJournal(
|
|
353
|
+
sessionId,
|
|
354
|
+
journal,
|
|
355
|
+
mutate,
|
|
356
|
+
options = {},
|
|
357
|
+
) {
|
|
358
|
+
const draft = BackgroundInteractionJournal.fromJSON(journal.toJSON(), {
|
|
359
|
+
now: options.now || journal._now,
|
|
360
|
+
});
|
|
361
|
+
const result = mutate(draft);
|
|
362
|
+
if (!options.persistIf || options.persistIf(result) !== false) {
|
|
363
|
+
persistBackgroundInteractionJournal(sessionId, draft, options);
|
|
364
|
+
}
|
|
365
|
+
return { journal: draft, result };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Recovery entry point used when a worker is observed lost or starts again.
|
|
370
|
+
* An optional state-file fallback lets pre-journal pendingQuestion records be
|
|
371
|
+
* imported once and deterministically rejected.
|
|
372
|
+
*/
|
|
373
|
+
export function rejectPendingBackgroundInteractions(
|
|
374
|
+
sessionId,
|
|
375
|
+
backgroundAgentId,
|
|
376
|
+
options = {},
|
|
377
|
+
) {
|
|
378
|
+
let journal = loadBackgroundInteractionJournal(
|
|
379
|
+
sessionId,
|
|
380
|
+
backgroundAgentId,
|
|
381
|
+
options,
|
|
382
|
+
);
|
|
383
|
+
const fallback = options.fallbackRequest;
|
|
384
|
+
const hasFallback =
|
|
385
|
+
fallback?.requestId &&
|
|
386
|
+
normalizeInteractionBinding(fallback.binding).sequence !== null &&
|
|
387
|
+
!journal.get(fallback.requestId);
|
|
388
|
+
if (!journal.pending().length && !hasFallback) {
|
|
389
|
+
return { journal, rejected: [], changed: false };
|
|
390
|
+
}
|
|
391
|
+
const mutation = updateBackgroundInteractionJournal(
|
|
392
|
+
sessionId,
|
|
393
|
+
journal,
|
|
394
|
+
(draft) => {
|
|
395
|
+
if (hasFallback) draft.recordPending(fallback);
|
|
396
|
+
return draft.rejectPending({
|
|
397
|
+
code: options.code || "INTERACTION_WORKER_LOST",
|
|
398
|
+
message:
|
|
399
|
+
options.message ||
|
|
400
|
+
"The background worker exited before the interaction settled",
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
options,
|
|
404
|
+
);
|
|
405
|
+
journal = mutation.journal;
|
|
406
|
+
return { journal, rejected: mutation.result, changed: true };
|
|
407
|
+
}
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
|
+
import {
|
|
17
|
+
hasCompleteInteractionBinding,
|
|
18
|
+
normalizeInteractionBinding,
|
|
19
|
+
sameInteractionBinding,
|
|
20
|
+
} from "./interaction-binding.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
normalizeInteractionBinding,
|
|
24
|
+
sameInteractionBinding,
|
|
25
|
+
} from "./interaction-binding.js";
|
|
16
26
|
|
|
17
27
|
export const BACKGROUND_INTERACTION_PROTOCOL_VERSION = 1;
|
|
18
28
|
export const DEFAULT_BACKGROUND_INTERACTION_TIMEOUT_MS = 60_000;
|
|
@@ -30,31 +40,6 @@ function asNullableString(value) {
|
|
|
30
40
|
return String(value);
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
function normalizeBinding(binding = {}) {
|
|
34
|
-
return {
|
|
35
|
-
backgroundAgentId: asNullableString(binding.backgroundAgentId),
|
|
36
|
-
sessionId: asNullableString(binding.sessionId),
|
|
37
|
-
turnId: asNullableString(binding.turnId),
|
|
38
|
-
toolUseId: asNullableString(binding.toolUseId),
|
|
39
|
-
sequence:
|
|
40
|
-
Number.isSafeInteger(binding.sequence) && binding.sequence > 0
|
|
41
|
-
? binding.sequence
|
|
42
|
-
: null,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function sameBinding(left, right) {
|
|
47
|
-
const a = normalizeBinding(left);
|
|
48
|
-
const b = normalizeBinding(right);
|
|
49
|
-
return (
|
|
50
|
-
a.backgroundAgentId === b.backgroundAgentId &&
|
|
51
|
-
a.sessionId === b.sessionId &&
|
|
52
|
-
a.turnId === b.turnId &&
|
|
53
|
-
a.toolUseId === b.toolUseId &&
|
|
54
|
-
a.sequence === b.sequence
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
43
|
function validRequest(message) {
|
|
59
44
|
return (
|
|
60
45
|
message &&
|
|
@@ -65,12 +50,16 @@ function validRequest(message) {
|
|
|
65
50
|
message.requestId.length <= 256 &&
|
|
66
51
|
message.payload &&
|
|
67
52
|
typeof message.payload === "object" &&
|
|
68
|
-
|
|
53
|
+
hasCompleteInteractionBinding(message.binding)
|
|
69
54
|
);
|
|
70
55
|
}
|
|
71
56
|
|
|
72
57
|
function sendIpc(target, message, onError = () => {}) {
|
|
73
|
-
if (
|
|
58
|
+
if (
|
|
59
|
+
!target ||
|
|
60
|
+
typeof target.send !== "function" ||
|
|
61
|
+
target.connected === false
|
|
62
|
+
)
|
|
74
63
|
return false;
|
|
75
64
|
try {
|
|
76
65
|
target.send(message, (error) => {
|
|
@@ -138,7 +127,8 @@ export function createBackgroundInteractionClient(options = {}) {
|
|
|
138
127
|
return;
|
|
139
128
|
}
|
|
140
129
|
const entry = pending.get(message.requestId);
|
|
141
|
-
if (!entry || !
|
|
130
|
+
if (!entry || !sameInteractionBinding(entry.binding, message.binding))
|
|
131
|
+
return;
|
|
142
132
|
|
|
143
133
|
if (message.status === "resolved") {
|
|
144
134
|
settle(entry, entry.resolve, message.answer);
|
|
@@ -170,7 +160,7 @@ export function createBackgroundInteractionClient(options = {}) {
|
|
|
170
160
|
|
|
171
161
|
sequence += 1;
|
|
172
162
|
const requestId = `${backgroundAgentId}:${idFactory()}`;
|
|
173
|
-
const binding =
|
|
163
|
+
const binding = normalizeInteractionBinding({
|
|
174
164
|
backgroundAgentId,
|
|
175
165
|
sessionId: payload.sessionId ?? sessionId,
|
|
176
166
|
turnId: payload.turnId ?? turnId,
|
|
@@ -301,7 +291,7 @@ export function attachInteractionRequestHandler(
|
|
|
301
291
|
protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
|
|
302
292
|
requestId:
|
|
303
293
|
typeof message?.requestId === "string" ? message.requestId : "",
|
|
304
|
-
binding:
|
|
294
|
+
binding: normalizeInteractionBinding(message?.binding),
|
|
305
295
|
status: "rejected",
|
|
306
296
|
error: { code, message: detail },
|
|
307
297
|
};
|
|
@@ -315,7 +305,7 @@ export function attachInteractionRequestHandler(
|
|
|
315
305
|
message.protocolVersion === BACKGROUND_INTERACTION_PROTOCOL_VERSION
|
|
316
306
|
) {
|
|
317
307
|
const active = inflight.get(message.requestId);
|
|
318
|
-
if (active &&
|
|
308
|
+
if (active && sameInteractionBinding(active.binding, message.binding)) {
|
|
319
309
|
inflight.delete(message.requestId);
|
|
320
310
|
active.controller.abort(message.reason || "cancelled");
|
|
321
311
|
}
|
|
@@ -331,12 +321,11 @@ export function attachInteractionRequestHandler(
|
|
|
331
321
|
return;
|
|
332
322
|
}
|
|
333
323
|
|
|
334
|
-
const binding =
|
|
324
|
+
const binding = normalizeInteractionBinding(message.binding);
|
|
335
325
|
const expectedAgentId = asNullableString(options.backgroundAgentId);
|
|
336
326
|
const expectedSessionId = asNullableString(options.sessionId);
|
|
337
327
|
if (
|
|
338
|
-
(expectedAgentId &&
|
|
339
|
-
binding.backgroundAgentId !== expectedAgentId) ||
|
|
328
|
+
(expectedAgentId && binding.backgroundAgentId !== expectedAgentId) ||
|
|
340
329
|
(expectedSessionId && binding.sessionId !== expectedSessionId)
|
|
341
330
|
) {
|
|
342
331
|
rejectRequest(
|
|
@@ -198,7 +198,8 @@ export function startBackgroundSessionServer(opts) {
|
|
|
198
198
|
case "interaction_response": {
|
|
199
199
|
try {
|
|
200
200
|
// Support both field names: requestId (client) and intId (wire legacy)
|
|
201
|
-
const { requestId, intId, answer, result, error } =
|
|
201
|
+
const { requestId, intId, binding, answer, result, error } =
|
|
202
|
+
message;
|
|
202
203
|
const resolvedId = requestId || intId;
|
|
203
204
|
if (!resolvedId) {
|
|
204
205
|
writeMessage(socket, {
|
|
@@ -208,14 +209,23 @@ export function startBackgroundSessionServer(opts) {
|
|
|
208
209
|
return;
|
|
209
210
|
}
|
|
210
211
|
// Single-object callback matching worker expectation: { requestId, answer, error }
|
|
211
|
-
onInteractionResponse?.({
|
|
212
|
+
const settlement = onInteractionResponse?.({
|
|
212
213
|
requestId: resolvedId,
|
|
214
|
+
binding,
|
|
213
215
|
answer: answer ?? result,
|
|
214
216
|
error,
|
|
215
217
|
});
|
|
216
218
|
writeMessage(socket, {
|
|
217
219
|
type: "received",
|
|
218
220
|
requestId: resolvedId,
|
|
221
|
+
accepted:
|
|
222
|
+
typeof settlement === "object"
|
|
223
|
+
? settlement.accepted === true
|
|
224
|
+
: settlement === true,
|
|
225
|
+
duplicate:
|
|
226
|
+
typeof settlement === "object"
|
|
227
|
+
? settlement.duplicate === true
|
|
228
|
+
: false,
|
|
219
229
|
});
|
|
220
230
|
} catch (err) {
|
|
221
231
|
writeMessage(socket, {
|