chainlesschain 0.162.183 → 0.162.184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BapS7lz4.js → AIOps-DzAl5fGx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-DAHtMGeC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-BHQaexv5.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-Phz_pD0n.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DHt47kCA.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit--Y_8FULl.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-N1zchH7D.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-B_BoyN30.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-Gl4uQHcT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-Cg-eIyAZ.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-D0MGR2wA.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-BdObJEFn.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col--ghxXT3z.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-BUFeXOqz.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-DhQO2_Mr.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-3HoTqKYd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-B3zmO4VW.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-CJDrZOcc.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-SMl15obd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-CgTrTWrl.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-dXFKaKol.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-qQHEhCvI.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-C_ej0MWH.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-D6ajoE7I.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-Cio2y49h.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-ClJz1cYp.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-Dr_8Maca.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-CevLwp5u.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CW9YyQUY.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-CQqeAyER.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-cuGVxHrw.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B_fyjZK9.js → Logs-DIuHri5d.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-C0o4FtpD.js → MarkdownRenderer-R91Qw5JA.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BIStF9d1.js → Marketplace-CY1VhFGx.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-BBAbi1vt.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-BWYGHxJA.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-CPIFkW7A.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-DQyWxCho.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-Bo3GnQbx.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-C8-c4rSW.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-ChqMTtoq.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-C48TyhvI.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-zogUFWxp.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-v-K9XpSB.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-Boi1dQfR.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-DM5YWwgA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-C57LKpKK.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-BFkPqu6Q.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-D6fB9YSp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-Bp7EefEo.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-BVln3hZf.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-Bf8P3nF6.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-DM6wA2E2.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-CGbc5Iho.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-D0p9EOxm.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-B2kJcxS6.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-Cxu7dXYy.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DTlrTPms.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-Cm3X-cCj.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-DNb2kjvt.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CZUFFjc1.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-DAPgloxA.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-dsx9S793.js} +3 -3
- package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-DnnC8kz-.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-VuZknRMK.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-B3XNX8Vx.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-Dl1nVtCj.js} +1 -1
- package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +1 -0
- package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-HzU56n1z.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-CRle7Jl2.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.js → SyncSettings-DWadz7Sl.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D-R1x8vm.js → Tasks-CNVTDGg8.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BKoHztGT.js → Templates-BBAgvrlM.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-BsbbvGN8.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-uscpZI4q.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-mTyjNE6r.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-Bz97oBgs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-BTRKC140.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-B4A_KuyX.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-DES9TmLU.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-ClXs7Qxp.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-IzZqFz-7.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-BtOP04g_.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-_0cqnopY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-ax7193uU.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-Bu4WGJah.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-BX1dlTW0.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C326bjex.js → createContext-CktA_Dj7.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-x877iKKS.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D_INJQT_.js → hasIn-D5wd817l.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-5PYYkr8S.js} +1 -1
- package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-A4uBPQCz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-BAtBNfkr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-BLLsUNDc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BQtoRyTO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUU2606o.js → index-BRfXoRuh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Bad2CYeH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-BcGdL3sG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-C3HmzCLt.js} +1 -1
- package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-C8DkWyNr.js} +1 -1
- package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-C8tLR2TG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-CP8ELJBc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-CUForzsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-CYRE0NJA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-CeVZaJSC.js} +1 -1
- package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-CfzxR61U.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr3uuz0C.js → index-Ci3y1ZAv.js} +3 -3
- package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-CjEJjl5C.js} +1 -1
- package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-Cv7bVWJ5.js} +1 -1
- package/src/assets/web-panel/assets/{index-qMA4GYdQ.js → index-D-o4Rwjk.js} +1 -1
- package/src/assets/web-panel/assets/index-D7pfPouB.js +1 -0
- package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-D9op8poE.js} +1 -1
- package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DF1TysIF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-DYo9S5OT.js} +1 -1
- package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-D_cycOET.js} +1 -1
- package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-Db3CpWTr.js} +1 -1
- package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-DkD1K-fF.js} +1 -1
- package/src/assets/web-panel/assets/{index-C327pLbw.js → index-DmPJ6T6F.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-DmVUeWUk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dzdr0zDf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-PBLzwYnu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-bFCaM7Eq.js} +1 -1
- package/src/assets/web-panel/assets/index-e4i-VLBd.js +1 -0
- package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-h3KJZr95.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-iHcPtQ2e.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-jzlzm4VK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-o14NNnxw.js} +1 -1
- package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-tYPSfGbd.js} +1 -1
- package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-ytp2H5Zx.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-D2Iut83B.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-BuaJVdK9.js} +1 -1
- package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-CaEuM92y.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-wuKNBwys.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-7a3eklaq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Dn4z7KvJ.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-DVn_qT4-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-Dodz1OMQ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-CxoGskbp.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-DBh_epQH.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-RXQ4gG5P.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-1Wxm0HEG.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CI_ySNxJ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-C6973rZP.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-CalynwKr.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-CM1cd-Q0.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +74 -1
- package/src/commands/background-session.js +11 -1
- package/src/commands/batch.js +160 -5
- package/src/commands/plugin.js +90 -27
- package/src/commands/session.js +31 -1
- package/src/commands/team.js +212 -6
- package/src/data/changelog.json +13 -0
- package/src/harness/jsonl-session-store.js +57 -6
- package/src/harness/mcp-client.js +65 -43
- package/src/lib/agent-batch.js +4 -1
- package/src/lib/agent-team/team-worktree.js +3 -0
- package/src/lib/async-hook-supervisor.cjs +82 -10
- package/src/lib/async-hook-supervisor.js +50 -4
- package/src/lib/collaboration-run-store.js +362 -0
- package/src/lib/hook-runner.cjs +98 -17
- package/src/lib/hook-runner.js +77 -6
- package/src/lib/hook-shell-command.cjs +148 -0
- package/src/lib/hooks-v2-runtime.js +188 -77
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +18 -0
- package/src/lib/lsp/lsp-client.js +12 -1
- package/src/lib/plugin-monitor-supervisor.js +53 -16
- package/src/lib/plugin-runtime/bin.js +1 -1
- package/src/lib/plugin-runtime/install.js +249 -57
- package/src/lib/plugin-runtime/mcp.js +21 -2
- package/src/lib/plugin-runtime/monitors.js +19 -3
- package/src/lib/plugin-runtime/sandbox-policy.js +99 -0
- package/src/lib/process-execution-broker/index.js +453 -12
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1347 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +1260 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +580 -56
- package/src/lib/session-usage.js +202 -2
- package/src/lib/stream-retry.js +29 -0
- package/src/repl/agent-repl.js +32 -3
- package/src/runtime/agent-core.js +32 -4
- package/src/runtime/headless-runner.js +37 -0
- package/src/runtime/headless-stream.js +34 -2
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CI_jT0ru.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DdcsOqEz.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
- package/src/assets/web-panel/assets/Skills-DtT6rWOX.js +0 -1
- package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
- package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
- package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
- package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
|
@@ -126,6 +126,8 @@ export class TeamWorktreeCoordinator {
|
|
|
126
126
|
sparsePaths: options.sparsePaths ?? null,
|
|
127
127
|
symlinkDirectories: options.symlinkDirectories ?? null,
|
|
128
128
|
};
|
|
129
|
+
this._onWorktree =
|
|
130
|
+
typeof options.onWorktree === "function" ? options.onWorktree : null;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
isGitRepo() {
|
|
@@ -173,6 +175,7 @@ export class TeamWorktreeCoordinator {
|
|
|
173
175
|
wtOptions,
|
|
174
176
|
);
|
|
175
177
|
this._created.set(key, { branch, path: worktreePath, committed: false });
|
|
178
|
+
this._onWorktree?.({ key, branch, path: worktreePath, holder });
|
|
176
179
|
if (typeof runInWorktree === "function") {
|
|
177
180
|
await runInWorktree({ key, task, holder, cwd: worktreePath });
|
|
178
181
|
} else {
|
|
@@ -36,6 +36,13 @@ const {
|
|
|
36
36
|
tryParseDecision,
|
|
37
37
|
resolveHookSandboxPolicy,
|
|
38
38
|
} = require("./hook-runner.cjs");
|
|
39
|
+
const {
|
|
40
|
+
buildExplicitHookShellInvocation,
|
|
41
|
+
issueTrustedHookSandboxContract,
|
|
42
|
+
requireTrustedHookRoot,
|
|
43
|
+
requiresExplicitHookShell,
|
|
44
|
+
sandboxBoundaryError,
|
|
45
|
+
} = require("./hook-shell-command.cjs");
|
|
39
46
|
|
|
40
47
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
41
48
|
const MAX_TIMEOUT_MS = 600000;
|
|
@@ -117,8 +124,25 @@ class AsyncHookSupervisor {
|
|
|
117
124
|
* the process 'exit' reaper, where async work would be cut off. A child with no
|
|
118
125
|
* real pid (the unit-test fakes) falls back to its own `.kill()`.
|
|
119
126
|
*/
|
|
120
|
-
_killChildTree(
|
|
127
|
+
_killChildTree(
|
|
128
|
+
child,
|
|
129
|
+
signal = "SIGTERM",
|
|
130
|
+
{ sandboxManagedTree = false } = {},
|
|
131
|
+
) {
|
|
121
132
|
if (!child) return;
|
|
133
|
+
if (sandboxManagedTree) {
|
|
134
|
+
// The Linux generic backend launches bubblewrap with --die-with-parent,
|
|
135
|
+
// --new-session, and a private PID namespace. Killing the Broker child
|
|
136
|
+
// therefore tears down the complete sandbox tree; a negative host PID
|
|
137
|
+
// would be both unnecessary and invalid because strong launches must not
|
|
138
|
+
// use detached:true.
|
|
139
|
+
try {
|
|
140
|
+
child.kill(signal);
|
|
141
|
+
} catch {
|
|
142
|
+
/* already gone */
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
122
146
|
const pid = child.pid;
|
|
123
147
|
if (typeof pid !== "number" || pid <= 0) {
|
|
124
148
|
try {
|
|
@@ -361,25 +385,39 @@ class AsyncHookSupervisor {
|
|
|
361
385
|
: DEFAULT_TIMEOUT_MS;
|
|
362
386
|
|
|
363
387
|
let child;
|
|
388
|
+
let sandboxManagedTree = false;
|
|
364
389
|
try {
|
|
365
390
|
const sandboxPolicy = resolveHookSandboxPolicy(hook, opts);
|
|
391
|
+
const explicitSandboxShell = requiresExplicitHookShell(sandboxPolicy);
|
|
392
|
+
const trustedRoot = explicitSandboxShell
|
|
393
|
+
? requireTrustedHookRoot(opts.cwd)
|
|
394
|
+
: null;
|
|
395
|
+
const workingDirectory = trustedRoot || opts.cwd || process.cwd();
|
|
396
|
+
sandboxManagedTree =
|
|
397
|
+
explicitSandboxShell && this._deps.platform === "linux";
|
|
366
398
|
const spawnOptions = {
|
|
367
|
-
cwd:
|
|
368
|
-
shell: true,
|
|
399
|
+
cwd: workingDirectory,
|
|
400
|
+
shell: explicitSandboxShell ? false : true,
|
|
369
401
|
// POSIX: own process group so a timeout / stopAll can signal the whole
|
|
370
402
|
// tree (shell + the real hook command), not just the shell wrapper.
|
|
371
403
|
// No-op on Windows, where the tree is reaped via `taskkill /T` instead.
|
|
372
|
-
detached: this._deps.platform !== "win32",
|
|
404
|
+
detached: this._deps.platform !== "win32" && !sandboxManagedTree,
|
|
373
405
|
env: {
|
|
374
406
|
...process.env,
|
|
375
407
|
CLAUDE_HOOK_EVENT: hook.event || payload.hook_event_name || "",
|
|
376
408
|
},
|
|
377
409
|
...(sandboxPolicy ? { sandboxPolicy } : {}),
|
|
378
410
|
};
|
|
379
|
-
|
|
411
|
+
const brokerRun =
|
|
412
|
+
explicitSandboxShell &&
|
|
413
|
+
opts.broker &&
|
|
414
|
+
typeof opts.broker.spawn === "function"
|
|
415
|
+
? opts.broker.spawn.bind(opts.broker)
|
|
416
|
+
: null;
|
|
417
|
+
if (typeof (brokerRun || this._deps.run) !== "function") {
|
|
380
418
|
throw new Error("async hook process runner unavailable");
|
|
381
419
|
}
|
|
382
|
-
|
|
420
|
+
const processOptions = {
|
|
383
421
|
...spawnOptions,
|
|
384
422
|
origin: hook.origin || "async-hook:command",
|
|
385
423
|
policy: "allow",
|
|
@@ -387,7 +425,39 @@ class AsyncHookSupervisor {
|
|
|
387
425
|
pluginId: hook.pluginId || null,
|
|
388
426
|
pluginVersion: hook.pluginVersion || null,
|
|
389
427
|
pluginSource: hook.pluginSource || null,
|
|
390
|
-
}
|
|
428
|
+
};
|
|
429
|
+
if (explicitSandboxShell) {
|
|
430
|
+
const invocation = buildExplicitHookShellInvocation(hook.command, {
|
|
431
|
+
platform: this._deps.platform,
|
|
432
|
+
});
|
|
433
|
+
const run = brokerRun || this._deps.run;
|
|
434
|
+
let brokerOptions = processOptions;
|
|
435
|
+
if (brokerRun) {
|
|
436
|
+
const issuer =
|
|
437
|
+
opts.broker.issueLinuxWorkspaceSandboxExecutionContract;
|
|
438
|
+
const sandboxExecutionContract = issueTrustedHookSandboxContract({
|
|
439
|
+
issuer,
|
|
440
|
+
receiver: opts.broker,
|
|
441
|
+
file: invocation.file,
|
|
442
|
+
args: invocation.argv,
|
|
443
|
+
options: processOptions,
|
|
444
|
+
trustedRoot,
|
|
445
|
+
label: "trusted async-hook sandbox contract issuance failed",
|
|
446
|
+
});
|
|
447
|
+
if (this._deps.platform === "linux" && !sandboxExecutionContract) {
|
|
448
|
+
throw sandboxBoundaryError(
|
|
449
|
+
"trusted Linux async-hook sandbox contract issuer is unavailable",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
brokerOptions = {
|
|
453
|
+
...processOptions,
|
|
454
|
+
...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
child = run(invocation.file, invocation.argv, brokerOptions);
|
|
458
|
+
} else {
|
|
459
|
+
child = this._deps.run(hook.command, processOptions);
|
|
460
|
+
}
|
|
391
461
|
} catch (err) {
|
|
392
462
|
const sandboxDenied =
|
|
393
463
|
err?.code === "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED";
|
|
@@ -406,7 +476,7 @@ class AsyncHookSupervisor {
|
|
|
406
476
|
let out = "";
|
|
407
477
|
let errOut = "";
|
|
408
478
|
let killedByTimeout = false;
|
|
409
|
-
const rec = { child, timer: null };
|
|
479
|
+
const rec = { child, timer: null, sandboxManagedTree };
|
|
410
480
|
this._running.set(key, rec);
|
|
411
481
|
|
|
412
482
|
try {
|
|
@@ -432,7 +502,7 @@ class AsyncHookSupervisor {
|
|
|
432
502
|
killedByTimeout = true;
|
|
433
503
|
// Tree-kill: a hook that spawned a subprocess (e.g. `npm test`) must not
|
|
434
504
|
// leave that subprocess orphaned when it exceeds its timeout.
|
|
435
|
-
this._killChildTree(child, "SIGTERM");
|
|
505
|
+
this._killChildTree(child, "SIGTERM", { sandboxManagedTree });
|
|
436
506
|
}, timeout);
|
|
437
507
|
if (typeof timer.unref === "function") timer.unref();
|
|
438
508
|
rec.timer = timer;
|
|
@@ -594,7 +664,9 @@ class AsyncHookSupervisor {
|
|
|
594
664
|
}
|
|
595
665
|
// Tree-kill (not a bare child.kill) so a session end / process exit never
|
|
596
666
|
// leaves an orphaned hook subprocess — the guarantee this class documents.
|
|
597
|
-
this._killChildTree(rec.child, "SIGTERM"
|
|
667
|
+
this._killChildTree(rec.child, "SIGTERM", {
|
|
668
|
+
sandboxManagedTree: rec.sandboxManagedTree === true,
|
|
669
|
+
});
|
|
598
670
|
this._running.delete(key);
|
|
599
671
|
}
|
|
600
672
|
try {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import supervisor from "./async-hook-supervisor.cjs";
|
|
2
2
|
import executionBroker from "./process-execution-broker/index.js";
|
|
3
|
+
import hookShellCommand from "./hook-shell-command.cjs";
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
issueTrustedHookSandboxContract,
|
|
7
|
+
requireTrustedHookRoot,
|
|
8
|
+
requiresExplicitHookShell,
|
|
9
|
+
sandboxBoundaryError,
|
|
10
|
+
} = hookShellCommand;
|
|
3
11
|
|
|
4
12
|
const brokerRunner = executionBroker.spawn.bind(executionBroker);
|
|
5
13
|
const brokerSyncRunner = executionBroker.spawnSync.bind(executionBroker);
|
|
@@ -9,12 +17,50 @@ export const _processDeps = {
|
|
|
9
17
|
runSync: brokerSyncRunner,
|
|
10
18
|
};
|
|
11
19
|
|
|
12
|
-
function
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
function normalizeInvocation(argsOrOptions, maybeOptions) {
|
|
21
|
+
return Array.isArray(argsOrOptions)
|
|
22
|
+
? { args: argsOrOptions, options: maybeOptions || {} }
|
|
23
|
+
: { args: [], options: argsOrOptions || {} };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runAsyncHook(command, argsOrOptions = {}, maybeOptions) {
|
|
27
|
+
const { args, options } = normalizeInvocation(argsOrOptions, maybeOptions);
|
|
28
|
+
const callerOptions = { ...options };
|
|
29
|
+
delete callerOptions.sandboxExecutionContract;
|
|
30
|
+
const processOptions = {
|
|
31
|
+
...callerOptions,
|
|
32
|
+
origin: callerOptions.origin || "async-hook:command",
|
|
16
33
|
policy: "allow",
|
|
17
34
|
scope: "async-hook",
|
|
35
|
+
};
|
|
36
|
+
const requiresContract = requiresExplicitHookShell(
|
|
37
|
+
processOptions.sandboxPolicy,
|
|
38
|
+
);
|
|
39
|
+
const trustedRoot = requiresContract
|
|
40
|
+
? requireTrustedHookRoot(processOptions.cwd)
|
|
41
|
+
: null;
|
|
42
|
+
const sandboxExecutionContract = requiresContract
|
|
43
|
+
? issueTrustedHookSandboxContract({
|
|
44
|
+
issuer: executionBroker.issueLinuxWorkspaceSandboxExecutionContract,
|
|
45
|
+
receiver: executionBroker,
|
|
46
|
+
file: command,
|
|
47
|
+
args,
|
|
48
|
+
options: processOptions,
|
|
49
|
+
trustedRoot,
|
|
50
|
+
})
|
|
51
|
+
: null;
|
|
52
|
+
if (
|
|
53
|
+
process.platform === "linux" &&
|
|
54
|
+
requiresContract &&
|
|
55
|
+
!sandboxExecutionContract
|
|
56
|
+
) {
|
|
57
|
+
throw sandboxBoundaryError(
|
|
58
|
+
"trusted Linux async-hook sandbox contract could not be issued",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return _processDeps.run(command, args, {
|
|
62
|
+
...processOptions,
|
|
63
|
+
...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
|
|
18
64
|
});
|
|
19
65
|
}
|
|
20
66
|
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable, secret-free governance records for short-lived `cc team` / `cc
|
|
3
|
+
* batch` worktree runs.
|
|
4
|
+
*
|
|
5
|
+
* These are deliberately separate from background-agent state: a team/batch
|
|
6
|
+
* task is not independently attachable or stoppable, and pretending otherwise
|
|
7
|
+
* could make `cc daemon stop` kill the shared coordinator process. The daemon
|
|
8
|
+
* JSON view may project these records read-only for IDE task rows.
|
|
9
|
+
*
|
|
10
|
+
* Prompts, argv, tool arguments, model output, credentials, and side-effect
|
|
11
|
+
* metadata never enter this store.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
24
|
+
import { getHomeDir } from "./paths.js";
|
|
25
|
+
|
|
26
|
+
export const COLLABORATION_RUN_VERSION = 1;
|
|
27
|
+
const RUN_KINDS = new Set(["team", "batch"]);
|
|
28
|
+
const PERMISSION_MODES = new Set([
|
|
29
|
+
"manual",
|
|
30
|
+
"auto",
|
|
31
|
+
"dontAsk",
|
|
32
|
+
"default",
|
|
33
|
+
"plan",
|
|
34
|
+
"acceptEdits",
|
|
35
|
+
"bypassPermissions",
|
|
36
|
+
]);
|
|
37
|
+
const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
38
|
+
const UNIT_STATUSES = new Set([
|
|
39
|
+
"pending",
|
|
40
|
+
"running",
|
|
41
|
+
"completed",
|
|
42
|
+
"failed",
|
|
43
|
+
"test-failed",
|
|
44
|
+
"no-changes",
|
|
45
|
+
"cancelled",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export const _deps = {
|
|
49
|
+
now: () => Date.now(),
|
|
50
|
+
randomHex: () => randomBytes(3).toString("hex"),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function boundedString(value, max) {
|
|
54
|
+
if (value === null || value === undefined) return null;
|
|
55
|
+
const clean = String(value)
|
|
56
|
+
.replace(/\p{Cc}/gu, "")
|
|
57
|
+
.trim();
|
|
58
|
+
return clean ? clean.slice(0, max) : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function positiveNumber(value) {
|
|
62
|
+
const number = Number(value);
|
|
63
|
+
return Number.isFinite(number) && number > 0 ? number : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nonNegativeInteger(value) {
|
|
67
|
+
const number = Number(value);
|
|
68
|
+
return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function safeKind(kind) {
|
|
72
|
+
const value = String(kind || "");
|
|
73
|
+
if (!RUN_KINDS.has(value)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unsupported collaboration run kind: ${value || "(empty)"}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function safeRunId(id) {
|
|
82
|
+
const value = String(id || "");
|
|
83
|
+
if (value.length > 160 || !/^(team|batch)-[a-zA-Z0-9._-]+$/.test(value)) {
|
|
84
|
+
throw new Error(`Invalid collaboration run id: ${value || "(empty)"}`);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function collaborationRunsDir() {
|
|
90
|
+
const dir =
|
|
91
|
+
process.env.CC_COLLABORATION_RUNS_DIR ||
|
|
92
|
+
join(getHomeDir(), "collaboration-runs");
|
|
93
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
94
|
+
return dir;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createCollaborationRunId(kind) {
|
|
98
|
+
const normalizedKind = safeKind(kind);
|
|
99
|
+
return `${normalizedKind}-${Math.floor(_deps.now())}-${_deps.randomHex()}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createCollaborationSessionId(runId, key) {
|
|
103
|
+
const safeId = safeRunId(runId);
|
|
104
|
+
const digest = createHash("sha256")
|
|
105
|
+
.update(String(key || "unit"))
|
|
106
|
+
.digest("hex")
|
|
107
|
+
.slice(0, 12);
|
|
108
|
+
return `session-${safeId}-${digest}`.slice(0, 256);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function collaborationRunPath(id) {
|
|
112
|
+
return join(collaborationRunsDir(), `${safeRunId(id)}.json`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeBudget(resourceBudget) {
|
|
116
|
+
const source =
|
|
117
|
+
resourceBudget && typeof resourceBudget === "object" ? resourceBudget : {};
|
|
118
|
+
return {
|
|
119
|
+
maxTurns: positiveNumber(source.maxTurns),
|
|
120
|
+
maxCostUsd: positiveNumber(source.maxCostUsd),
|
|
121
|
+
maxTasks: positiveNumber(source.maxTasks),
|
|
122
|
+
maxTokens: positiveNumber(source.maxTokens),
|
|
123
|
+
maxWallMs: positiveNumber(source.maxWallMs),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function summarizeSideEffects(source) {
|
|
128
|
+
const ops =
|
|
129
|
+
source && typeof source.list === "function"
|
|
130
|
+
? source.list()
|
|
131
|
+
: Array.isArray(source?.ops)
|
|
132
|
+
? source.ops
|
|
133
|
+
: null;
|
|
134
|
+
if (ops) {
|
|
135
|
+
const count = (state) => ops.filter((op) => op?.state === state).length;
|
|
136
|
+
return {
|
|
137
|
+
total: ops.length,
|
|
138
|
+
unsettled: ops.filter(
|
|
139
|
+
(op) => op?.state === "prepared" || op?.state === "started",
|
|
140
|
+
).length,
|
|
141
|
+
unknown: count("unknown"),
|
|
142
|
+
committed: count("committed"),
|
|
143
|
+
failed: count("failed"),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const value = source && typeof source === "object" ? source : {};
|
|
147
|
+
return {
|
|
148
|
+
total: nonNegativeInteger(value.total),
|
|
149
|
+
unsettled: nonNegativeInteger(value.unsettled),
|
|
150
|
+
unknown: nonNegativeInteger(value.unknown),
|
|
151
|
+
committed: nonNegativeInteger(value.committed),
|
|
152
|
+
failed: nonNegativeInteger(value.failed),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeUnit(unit, run) {
|
|
157
|
+
const key = boundedString(unit?.key, 256);
|
|
158
|
+
if (!key) throw new Error("Collaboration unit key is required");
|
|
159
|
+
const status = UNIT_STATUSES.has(unit?.status) ? unit.status : "pending";
|
|
160
|
+
const sessionId = boundedString(unit?.sessionId, 256);
|
|
161
|
+
return {
|
|
162
|
+
key,
|
|
163
|
+
owner: `${run.kind}:${run.id}:${key}`.slice(0, 512),
|
|
164
|
+
sessionId,
|
|
165
|
+
branch: boundedString(unit?.branch, 512),
|
|
166
|
+
worktreePath: boundedString(unit?.worktreePath, 4096),
|
|
167
|
+
status,
|
|
168
|
+
startedAt: positiveNumber(unit?.startedAt),
|
|
169
|
+
endedAt: positiveNumber(unit?.endedAt),
|
|
170
|
+
sideEffects: summarizeSideEffects(unit?.sideEffects),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeRun(input) {
|
|
175
|
+
const kind = safeKind(input?.kind);
|
|
176
|
+
const id = safeRunId(input?.id);
|
|
177
|
+
if (!id.startsWith(`${kind}-`)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Collaboration run id "${id}" does not match kind "${kind}"`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const permissionMode = PERMISSION_MODES.has(input?.permissionMode)
|
|
183
|
+
? input.permissionMode
|
|
184
|
+
: "default";
|
|
185
|
+
const status = new Set(["running", ...TERMINAL_RUN_STATUSES]).has(
|
|
186
|
+
input?.status,
|
|
187
|
+
)
|
|
188
|
+
? input.status
|
|
189
|
+
: "running";
|
|
190
|
+
const startedAt = positiveNumber(input?.startedAt) || _deps.now();
|
|
191
|
+
const run = {
|
|
192
|
+
version: COLLABORATION_RUN_VERSION,
|
|
193
|
+
id,
|
|
194
|
+
kind,
|
|
195
|
+
owner: `${kind}:${id}`,
|
|
196
|
+
status,
|
|
197
|
+
repoRoot: boundedString(input?.repoRoot, 4096),
|
|
198
|
+
permissionMode,
|
|
199
|
+
resourceBudget: normalizeBudget(input?.resourceBudget),
|
|
200
|
+
startedAt,
|
|
201
|
+
updatedAt: positiveNumber(input?.updatedAt) || startedAt,
|
|
202
|
+
endedAt: TERMINAL_RUN_STATUSES.has(status)
|
|
203
|
+
? positiveNumber(input?.endedAt) || startedAt
|
|
204
|
+
: null,
|
|
205
|
+
units: [],
|
|
206
|
+
};
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
for (const unit of Array.isArray(input?.units)
|
|
209
|
+
? input.units.slice(0, 1000)
|
|
210
|
+
: []) {
|
|
211
|
+
const normalized = normalizeUnit(unit, run);
|
|
212
|
+
if (seen.has(normalized.key)) {
|
|
213
|
+
throw new Error(`Duplicate collaboration unit key: ${normalized.key}`);
|
|
214
|
+
}
|
|
215
|
+
seen.add(normalized.key);
|
|
216
|
+
run.units.push(normalized);
|
|
217
|
+
}
|
|
218
|
+
return run;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function writeCollaborationRun(input) {
|
|
222
|
+
const run = normalizeRun(input);
|
|
223
|
+
const target = collaborationRunPath(run.id);
|
|
224
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
225
|
+
writeFileSync(temp, JSON.stringify(run, null, 2), { mode: 0o600 });
|
|
226
|
+
renameSync(temp, target);
|
|
227
|
+
return run;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function createCollaborationRun(input) {
|
|
231
|
+
const kind = safeKind(input?.kind);
|
|
232
|
+
const id = input?.id ? safeRunId(input.id) : createCollaborationRunId(kind);
|
|
233
|
+
if (existsSync(collaborationRunPath(id))) {
|
|
234
|
+
throw new Error(`Collaboration run already exists: ${id}`);
|
|
235
|
+
}
|
|
236
|
+
return writeCollaborationRun({
|
|
237
|
+
...input,
|
|
238
|
+
id,
|
|
239
|
+
kind,
|
|
240
|
+
status: "running",
|
|
241
|
+
startedAt: input?.startedAt || _deps.now(),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function readCollaborationRun(id) {
|
|
246
|
+
const target = collaborationRunPath(id);
|
|
247
|
+
if (!existsSync(target)) return null;
|
|
248
|
+
try {
|
|
249
|
+
return normalizeRun(JSON.parse(readFileSync(target, "utf8")));
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function updateCollaborationRun(id, updater) {
|
|
256
|
+
const current = readCollaborationRun(id);
|
|
257
|
+
if (!current) throw new Error(`Collaboration run not found: ${id}`);
|
|
258
|
+
const draft = JSON.parse(JSON.stringify(current));
|
|
259
|
+
const changed =
|
|
260
|
+
typeof updater === "function"
|
|
261
|
+
? updater(draft) || draft
|
|
262
|
+
: { ...draft, ...updater };
|
|
263
|
+
return writeCollaborationRun({
|
|
264
|
+
...changed,
|
|
265
|
+
id: current.id,
|
|
266
|
+
kind: current.kind,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function updateCollaborationUnit(runId, key, patch = {}) {
|
|
271
|
+
const targetKey = boundedString(key, 256);
|
|
272
|
+
return updateCollaborationRun(runId, (run) => {
|
|
273
|
+
const index = run.units.findIndex((unit) => unit.key === targetKey);
|
|
274
|
+
if (index < 0) {
|
|
275
|
+
throw new Error(`Collaboration unit not found: ${targetKey}`);
|
|
276
|
+
}
|
|
277
|
+
const current = run.units[index];
|
|
278
|
+
const status = UNIT_STATUSES.has(patch.status)
|
|
279
|
+
? patch.status
|
|
280
|
+
: current.status;
|
|
281
|
+
run.units[index] = {
|
|
282
|
+
...current,
|
|
283
|
+
...(patch.branch !== undefined
|
|
284
|
+
? { branch: boundedString(patch.branch, 512) }
|
|
285
|
+
: {}),
|
|
286
|
+
...(patch.worktreePath !== undefined
|
|
287
|
+
? { worktreePath: boundedString(patch.worktreePath, 4096) }
|
|
288
|
+
: {}),
|
|
289
|
+
status,
|
|
290
|
+
startedAt:
|
|
291
|
+
patch.startedAt !== undefined
|
|
292
|
+
? positiveNumber(patch.startedAt)
|
|
293
|
+
: current.startedAt,
|
|
294
|
+
endedAt:
|
|
295
|
+
patch.endedAt !== undefined
|
|
296
|
+
? positiveNumber(patch.endedAt)
|
|
297
|
+
: current.endedAt,
|
|
298
|
+
sideEffects:
|
|
299
|
+
patch.sideEffects !== undefined
|
|
300
|
+
? summarizeSideEffects(patch.sideEffects)
|
|
301
|
+
: current.sideEffects,
|
|
302
|
+
};
|
|
303
|
+
run.updatedAt = _deps.now();
|
|
304
|
+
return run;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function finalizeCollaborationRun(id, status = "completed") {
|
|
309
|
+
if (!TERMINAL_RUN_STATUSES.has(status)) {
|
|
310
|
+
throw new Error(`Invalid terminal collaboration status: ${status}`);
|
|
311
|
+
}
|
|
312
|
+
return updateCollaborationRun(id, (run) => {
|
|
313
|
+
run.status = status;
|
|
314
|
+
run.updatedAt = _deps.now();
|
|
315
|
+
run.endedAt = run.updatedAt;
|
|
316
|
+
return run;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function listCollaborationRuns({ limit = 200 } = {}) {
|
|
321
|
+
const capped = Math.max(1, Math.min(1000, Number(limit) || 200));
|
|
322
|
+
return readdirSync(collaborationRunsDir())
|
|
323
|
+
.filter((name) => /^(team|batch)-[a-zA-Z0-9._-]+\.json$/.test(name))
|
|
324
|
+
.map((name) => readCollaborationRun(name.slice(0, -5)))
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
327
|
+
.slice(0, capped);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Project run units into the bounded shape consumed by IDE worktree task rows.
|
|
332
|
+
* The caller can publish this as `managedTasks`; no control operation consumes
|
|
333
|
+
* these records.
|
|
334
|
+
*/
|
|
335
|
+
export function projectCollaborationTasks(runs) {
|
|
336
|
+
const rows = [];
|
|
337
|
+
for (const run of Array.isArray(runs) ? runs.slice(0, 200) : []) {
|
|
338
|
+
for (const unit of Array.isArray(run?.units)
|
|
339
|
+
? run.units.slice(0, 1000)
|
|
340
|
+
: []) {
|
|
341
|
+
if (!unit.branch && !unit.worktreePath) continue;
|
|
342
|
+
rows.push({
|
|
343
|
+
managedTaskId: `${run.id}:${unit.key}`.slice(0, 512),
|
|
344
|
+
runId: run.id,
|
|
345
|
+
runKind: run.kind,
|
|
346
|
+
branch: unit.branch,
|
|
347
|
+
worktreePath: unit.worktreePath,
|
|
348
|
+
status: unit.status,
|
|
349
|
+
governance: {
|
|
350
|
+
version: 1,
|
|
351
|
+
owner: unit.owner,
|
|
352
|
+
sessionId: unit.sessionId,
|
|
353
|
+
permissionMode: run.permissionMode,
|
|
354
|
+
resourceBudget: run.resourceBudget,
|
|
355
|
+
},
|
|
356
|
+
sideEffects: summarizeSideEffects(unit.sideEffects),
|
|
357
|
+
});
|
|
358
|
+
if (rows.length >= 1000) return rows;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return rows;
|
|
362
|
+
}
|