chainlesschain 0.162.166 → 0.162.167
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/assets/{AIOps-LbDx7jL6.js → AIOps-BZJrchIA.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DnurtK34.js → ActionButton-Bhlrylt7.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DXSsV1am.js → Analytics-pHtXVQYs.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-dqRpzNuD.js → AppLayout-ORpuri4R.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BwdmYepK.js → Artifacts-eC77yr1_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-oSX0kOUG.js → Audit-B2dZIiqz.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C2KxJahT.js → BackgroundAgents-COZiTiwP.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B4TkxWkC.js → Backup-vY_Srl5d.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CX5-KxeU.js → BaseInput-Dili5BdM.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-O_HzD-PL.js → Chat-EzKBf0XL.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Br8saHQA.js → ChatBubbleRenderer-D1CtRAyy.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-ChepDpC1.js → Checkbox-Beh6BmuW.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D9z8kc7q.js → Codegen-C3fA6kBU.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B-H5eoNw.js → Col-CefMqONH.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Ccyp_Kk5.js → Community-CBAxFJtB.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BNvBznp_.js → Compact--flHc-sM.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D1FjRd8n.js → Compliance-CPeNLvA6.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CV9B9u-8.js → Cowork-Bv5yv4BU.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BowJ4VIx.js → Cron-C22yMEas.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BJc6jzRF.js → Crosschain-Dsb2bcpu.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BEp8NV6D.js → DID-DAxei4w0.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Dls0B3YB.js → Dashboard-DEUwuVZ8.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DQ7zXVtD.js → Dropdown-BVp4si2j.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B9q79tOl.js → EmailListRenderer-CyFkybOG.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CD8Ie-88.js → FamilyGuardDashboard-BWmmsbVx.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-5o7XU5Vf.js → Federation-Cxw27A5d.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DeKbMvOs.js → FormItemContext-Q9XthzL1.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CZiQvz_z.js → GenericCardRenderer-DS5DIdsW.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CTvFxVV8.js → Git-BOliHfxP.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DwzFIjHt.js → Governance-CD4oxa-W.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CJBk59c-.js → Inference-CMlcivo8.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DfsaXN2z.js → KnowledgeGraph-BwhERcC4.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVRYf18S.js → Logs-BjVcZR5P.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BfRhXdyG.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BuIwyE4t.js → Marketplace-DQLU3E2w.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D3U0o78E.js → McpTools-CeK0XkpB.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-QYVmyb3b.js → Memory-DLFkgwaz.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BFgrj6eZ.js → MobileBridge-Bh6ymXyj.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Bp5QB-46.js → MobileProjects-D950tUIe.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BZQu8TcH.js → Mtc-Bx5x9_2m.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BnFfEkLo.js → MtcAudit-a1-Fexsq.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-iUnNS9Cm.js → Multisig-eaw0X8X1.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DNNz1y9Y.js → NLProgramming-BaNsPV4k.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CZezNYRU.js → Notes-C7pevN2X.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DBvrplIR.js → NotificationSettings-HDqeoP0r.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CTDtee49.js → OrderTableRenderer-DvzVl5T3.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CCOUJX1R.js → Organization-D_xHDlGW.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DcZghdk0.js → Overflow-B5sRnueL.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jmddWX9q.js → P2P-C-IRjOPW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-wfjMcczl.js → PdhVaultBrowser-BqgUPlcU.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-DU1GJjca.js → Permissions-Bm_T1uWw.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BzTMzDF9.js → PersonalDataHub-DujDllye.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DPmjlpVc.js → Pipeline-DyNPOLIG.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B6HRLpfK.js → Privacy-CYKTW3iK.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Cyy86AsN.js → ProjectInit-CGf03uZ0.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bl_WQxJ5.js → ProjectSettings-DIec4bTI.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Co9yKThA.js → Projects-BY43-bmi.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DLa4NlIs.js → Providers-RrnUmgPk.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BvzM7q5f.js → QrScannerModal-BBWfqd4y.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BN8yyyUj.js → QuickAsk-BsICSrFp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-QsVN4w0i.js → Recommend-B8y0b4ys.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-tkDXw-TY.js → RemoteSession-0QgdB1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DAifkGsy.js → Reputation-DBJGM0YO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D7dZEyak.js → Row-DtWQ5otv.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DB5sImTA.js → RssFeed-2zBxHd5H.js} +3 -3
- package/src/assets/web-panel/assets/{Search-3py3TO9V.js → Search-BofliSkZ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DhNUfT_k.js → Security-ockRBBEL.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DjJAA_5D.js → Services-QyvG7Vd0.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-u95JjK3n.js → Skeleton-DwbOndX3.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DPo3L0Tz.js → Skills-cHWa63sZ.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DWhkAZV1.js → Sla-BHnTEydQ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-PUegMtqV.js → SpeechSettings-DE2hrrLb.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BMNYrnrU.js → SyncSettings-CZ9vnbJH.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DWgoAHvM.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Dw9Er5DN.js → Templates-DiR3ad4X.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-7P3QIBSo.js → Tenant-NqcW3frV.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ClPrfWW7.js → Terminal-iYeqRQZ0.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DbCd1bDH.js → TimelineRenderer-C5GOoxFz.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-EQofpA0t.js → Tokens-82dz0XdU.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-ChNLLypd.js → Trigger-B3JCNrOi.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-dlVGUKs5.js → Trust-BehFYsN_.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BXyBUhNF.js → UkeySign-BfOxJjVf.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BE8-68ge.js → VideoEditing-DdITCCPz.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-7ZIJOwNg.js → Wallet-B0EhkCmD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DgNd2Amr.js → WebAuthn-DJ4i1f3-.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-Z-Un-9l5.js → WorkflowEditor-Dr9XTqIT.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CtVDEkUc.js → chat-Dc2nFyOj.js} +1 -1
- package/src/assets/web-panel/assets/{colors-PXtJ2f1B.js → colors-Drw9djBR.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-oN96nuBi.js → compact-item-BdLXbU52.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-okCJXQXa.js → createContext-BpQMZiWY.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CnfpDzMr.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-pcHm--4o.js → hasIn-CbYwawHf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZKk4Lnm.js → index-B1ANzVc6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQYes2mk.js → index-B3uRI8_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cc_MkksW.js → index-B5KboakZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ1ZzJjN.js → index-B9_u4Qhe.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bht9n8Sm.js → index-BAq-4KqY.js} +1 -1
- package/src/assets/web-panel/assets/{index-_SaFHNsG.js → index-BBueKSTR.js} +1 -1
- package/src/assets/web-panel/assets/{index-jAY55MSx.js → index-BIcEW07n.js} +1 -1
- package/src/assets/web-panel/assets/{index-nMqLdwao.js → index-BWUoc9-s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHJIfdCV.js → index-BZxzdAI0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAQktO-6.js → index-BiGDrEt4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjVIZfdA.js → index-Blv7c80V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEtzqJw3.js → index-BmQlot_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0bfypsF.js → index-Bo_KwJTF.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0rfJBU3.js → index-Bu8Vy-mj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuME9k4Y.js → index-C1eo1Qkd.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1efSh1X.js → index-C3Alz2QD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBcKBb1E.js → index-CBwQwtsE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4ravjdv.js → index-CJQiSQw7.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs1fGk1Z.js → index-CKmz3Is-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUUJ2WfG.js → index-COoIIMbO.js} +1 -1
- package/src/assets/web-panel/assets/index-CYTEB0tb.js +1 -0
- package/src/assets/web-panel/assets/index-CgsEp5bm.js +1 -0
- package/src/assets/web-panel/assets/{index-DC0bqrqV.js → index-CpmlTfpv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl6N7ZyX.js → index-D7Ipfs4F.js} +1 -1
- package/src/assets/web-panel/assets/{index-vW0lwj0Y.js → index-DHMeEu-P.js} +1 -1
- package/src/assets/web-panel/assets/{index-mB1UV7wA.js → index-DKQlF7Pw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDR2mi1p.js → index-DO7LD_1t.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK0H3pQW.js → index-DSmZjrFy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtsyPnF5.js → index-DTeqWdAS.js} +1 -1
- package/src/assets/web-panel/assets/{index-T3QE8LuV.js → index-DWUN8c01.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUTyx-Lk.js → index-D_lkTnZ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-5aO2V0V4.js → index-DyU21xVi.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWUJlCvK.js → index-GG_MXjSL.js} +3 -3
- package/src/assets/web-panel/assets/{index-CopGaCI_.js → index-IG3__pGj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnhHxZul.js → index-UX_Mfyfv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXhezK4d.js → index-VIyNlXPX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Rk0X8lVz.js → index-XYe6kBmi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfL10EcN.js → index-_vVucYqC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeAWCGEK.js → index-aHrUfrv1.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CG1jyiI7.js → initDefaultProps-DybC85E4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C8BYa98D.js → motion-BNNuyDg7.js} +1 -1
- package/src/assets/web-panel/assets/{move-D1hqwitW.js → move-Bm8gSjEN.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C2lol8rp.js → mtc-parser-CuHQvzzr.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Ct6Zghfi.js → omit-lv4NO7Ng.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CSYbYfdc.js → pickAttrs-BoSMfjHq.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-WCk8di0z.js → placementArrow-BOMDSvgz.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CEC8CZ-3.js → responsiveObserve-C064Ubmu.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BsXyd7Lq.js → slide-CeqhbKoG.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-tDKldiUT.js → statusUtils-CunFoYww.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BIS2unhx.js → styleChecker-Cxz3wPL7.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-yFTVMDUv.js → useFlexGapSupport-DJsIaU2S.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CZYbJwkO.js → useFs-DnAygceJ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-sbEHRLAM.js → usePersonalDataHub-CrEUY7ta.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BVjeSEQK.js → vnode-Cq3rN2eT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BZKVvEZ6.js → zoom-TMzcOx__.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +75 -0
- package/src/commands/session.js +132 -0
- package/src/data/changelog.json +7 -2
- package/src/harness/jsonl-session-store.js +9 -0
- package/src/harness/session-mirror.js +212 -6
- package/src/lib/async-hook-queue.cjs +288 -0
- package/src/lib/async-hook-supervisor.cjs +52 -0
- package/src/lib/doctor-checkup.js +184 -4
- package/src/lib/install-command-policy.js +333 -0
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/lsp/__tests__/lsp-server-registry.test.js +27 -0
- package/src/lib/lsp/lsp-server-registry.js +14 -0
- package/src/lib/runtime-checkup.js +33 -0
- package/src/lib/settings-hook-events.cjs +157 -0
- package/src/lib/settings-hooks.cjs +4 -0
- package/src/lib/shell-approval.js +47 -1
- package/src/repl/add-dir.js +59 -1
- package/src/repl/agent-repl.js +85 -3
- package/src/runtime/headless-runner.js +69 -1
- package/src/runtime/headless-stream.js +22 -0
- package/src/runtime/system-prompt.js +39 -12
- package/src/assets/web-panel/assets/MarkdownRenderer-BL_WsWZM.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CYm_oTJC.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BzV6dk1B.js +0 -1
- package/src/assets/web-panel/assets/index-BFrAwXHp.js +0 -1
- package/src/assets/web-panel/assets/index-DJQFP47u.js +0 -1
|
@@ -178,6 +178,159 @@ function runSessionStartHooks(settingsHooks, { source, cwd, sessionId } = {}) {
|
|
|
178
178
|
return { additionalContext: aggregateContext(outcome.results) };
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* CwdChanged settings hooks (observe + context injection). Fired when the
|
|
183
|
+
* working directory changes mid-session (`/cd`) — a distinct lifecycle event
|
|
184
|
+
* (Claude-Code-parity extension) automation can react to (re-audit trust of the
|
|
185
|
+
* new dir, reload a per-dir tool config, …). Observe-only: a cwd change never
|
|
186
|
+
* gates flow, so any `block` decision is ignored and only emitted context is
|
|
187
|
+
* returned. No-op (byte-unchanged) without a registered CwdChanged hook.
|
|
188
|
+
* @returns {{ additionalContext:string|null }}
|
|
189
|
+
*/
|
|
190
|
+
function runCwdChangedHooks(
|
|
191
|
+
settingsHooks,
|
|
192
|
+
{ oldCwd, newCwd, cwd, sessionId } = {},
|
|
193
|
+
) {
|
|
194
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
195
|
+
const matched = collectHooks(settingsHooks, "CwdChanged", newCwd || "");
|
|
196
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
197
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
198
|
+
const payload = withDeliveryId(
|
|
199
|
+
"CwdChanged",
|
|
200
|
+
{
|
|
201
|
+
hook_event_name: "CwdChanged",
|
|
202
|
+
old_cwd: oldCwd || null,
|
|
203
|
+
cwd: newCwd || cwd || null,
|
|
204
|
+
session_id: sessionId || null,
|
|
205
|
+
},
|
|
206
|
+
{ sessionId },
|
|
207
|
+
);
|
|
208
|
+
const outcome = runHooks(sync, payload, {
|
|
209
|
+
cwd: newCwd || cwd,
|
|
210
|
+
event: "CwdChanged",
|
|
211
|
+
});
|
|
212
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* WorktreeCreate settings hooks (observe + context injection). Fired right after
|
|
217
|
+
* `cc agent --worktree` carves a fresh isolated git worktree — a hook can react
|
|
218
|
+
* (register the worktree with an external tracker, seed per-branch tooling,
|
|
219
|
+
* warm a build cache). The matcher target is the branch name, so a regex matcher
|
|
220
|
+
* can scope to a branch pattern. Observe-only: worktree setup already succeeded,
|
|
221
|
+
* so a `block` decision is ignored and only emitted context is returned. No-op
|
|
222
|
+
* (byte-unchanged) without a registered WorktreeCreate hook.
|
|
223
|
+
* @returns {{ additionalContext:string|null }}
|
|
224
|
+
*/
|
|
225
|
+
function runWorktreeCreateHooks(
|
|
226
|
+
settingsHooks,
|
|
227
|
+
{ worktreePath, branch, baseSha, cwd, sessionId } = {},
|
|
228
|
+
) {
|
|
229
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
230
|
+
const matched = collectHooks(settingsHooks, "WorktreeCreate", branch || "");
|
|
231
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
232
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
233
|
+
const payload = withDeliveryId(
|
|
234
|
+
"WorktreeCreate",
|
|
235
|
+
{
|
|
236
|
+
hook_event_name: "WorktreeCreate",
|
|
237
|
+
worktree_path: worktreePath || null,
|
|
238
|
+
branch: branch || null,
|
|
239
|
+
base_sha: baseSha || null,
|
|
240
|
+
cwd: cwd || worktreePath || null,
|
|
241
|
+
session_id: sessionId || null,
|
|
242
|
+
},
|
|
243
|
+
{ sessionId },
|
|
244
|
+
);
|
|
245
|
+
const outcome = runHooks(sync, payload, {
|
|
246
|
+
cwd: cwd || worktreePath,
|
|
247
|
+
event: "WorktreeCreate",
|
|
248
|
+
});
|
|
249
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* WorktreeRemove settings hooks (observe + context injection). Fired when a
|
|
254
|
+
* `cc agent --worktree` session finishes and its worktree is torn down (or when
|
|
255
|
+
* a `keep` decision leaves it in place — `removed` distinguishes the two) — a
|
|
256
|
+
* hook can react (unregister the worktree, prune a per-branch cache, notify CI).
|
|
257
|
+
* Matcher target is the branch name. Observe-only: teardown already happened, so
|
|
258
|
+
* a `block` decision is ignored and only emitted context is returned. No-op
|
|
259
|
+
* (byte-unchanged) without a registered WorktreeRemove hook.
|
|
260
|
+
* @returns {{ additionalContext:string|null }}
|
|
261
|
+
*/
|
|
262
|
+
function runWorktreeRemoveHooks(
|
|
263
|
+
settingsHooks,
|
|
264
|
+
{ worktreePath, branch, removed, reason, cwd, sessionId } = {},
|
|
265
|
+
) {
|
|
266
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
267
|
+
const matched = collectHooks(settingsHooks, "WorktreeRemove", branch || "");
|
|
268
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
269
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
270
|
+
const payload = withDeliveryId(
|
|
271
|
+
"WorktreeRemove",
|
|
272
|
+
{
|
|
273
|
+
hook_event_name: "WorktreeRemove",
|
|
274
|
+
worktree_path: worktreePath || null,
|
|
275
|
+
branch: branch || null,
|
|
276
|
+
removed: removed === true,
|
|
277
|
+
reason: reason || null,
|
|
278
|
+
cwd: cwd || worktreePath || null,
|
|
279
|
+
session_id: sessionId || null,
|
|
280
|
+
},
|
|
281
|
+
{ sessionId },
|
|
282
|
+
);
|
|
283
|
+
const outcome = runHooks(sync, payload, {
|
|
284
|
+
cwd: cwd || worktreePath,
|
|
285
|
+
event: "WorktreeRemove",
|
|
286
|
+
});
|
|
287
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* InstructionsLoaded settings hooks (observe + context injection). Fired once at
|
|
292
|
+
* session start after the project-instruction block (cc.md / CLAUDE.md / AGENTS.md
|
|
293
|
+
* hierarchy + `.claude/rules/*`) is composed into the system prompt — a hook can
|
|
294
|
+
* react to WHICH instruction files are authoritative this session (audit them,
|
|
295
|
+
* warn on an unexpected local override, load a companion tool config). The
|
|
296
|
+
* caller passes the EXACT loaded set; this producer trims each entry to
|
|
297
|
+
* `{path, scope, truncated}` (never the file CONTENT — a hook that wants content
|
|
298
|
+
* reads the path). Observe-only: instructions are already loaded, so a `block`
|
|
299
|
+
* decision is ignored and only emitted context is returned. No-op (byte-unchanged)
|
|
300
|
+
* without a registered InstructionsLoaded hook.
|
|
301
|
+
* @returns {{ additionalContext:string|null }}
|
|
302
|
+
*/
|
|
303
|
+
function runInstructionsLoadedHooks(
|
|
304
|
+
settingsHooks,
|
|
305
|
+
{ files, cwd, sessionId } = {},
|
|
306
|
+
) {
|
|
307
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
308
|
+
const matched = collectHooks(settingsHooks, "InstructionsLoaded", "");
|
|
309
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
310
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
311
|
+
const list = Array.isArray(files) ? files : [];
|
|
312
|
+
const payload = withDeliveryId(
|
|
313
|
+
"InstructionsLoaded",
|
|
314
|
+
{
|
|
315
|
+
hook_event_name: "InstructionsLoaded",
|
|
316
|
+
files: list.map((f) => ({
|
|
317
|
+
path: f?.path || null,
|
|
318
|
+
scope: f?.scope || null,
|
|
319
|
+
truncated: f?.truncated === true,
|
|
320
|
+
})),
|
|
321
|
+
count: list.length,
|
|
322
|
+
cwd: cwd || null,
|
|
323
|
+
session_id: sessionId || null,
|
|
324
|
+
},
|
|
325
|
+
{ sessionId },
|
|
326
|
+
);
|
|
327
|
+
const outcome = runHooks(sync, payload, {
|
|
328
|
+
cwd,
|
|
329
|
+
event: "InstructionsLoaded",
|
|
330
|
+
});
|
|
331
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
332
|
+
}
|
|
333
|
+
|
|
181
334
|
/**
|
|
182
335
|
* Generic observe-only fire (SessionEnd / Stop / PreCompact). Returns the raw
|
|
183
336
|
* runHooks outcome so callers can read a block reason; never gates flow here.
|
|
@@ -206,6 +359,10 @@ function runObserveHooks(
|
|
|
206
359
|
module.exports = {
|
|
207
360
|
runUserPromptSubmitHooks,
|
|
208
361
|
runSessionStartHooks,
|
|
362
|
+
runCwdChangedHooks,
|
|
363
|
+
runWorktreeCreateHooks,
|
|
364
|
+
runWorktreeRemoveHooks,
|
|
365
|
+
runInstructionsLoadedHooks,
|
|
209
366
|
runObserveHooks,
|
|
210
367
|
aggregateContext,
|
|
211
368
|
partitionAsyncHooks,
|
|
@@ -24,6 +24,13 @@ import {
|
|
|
24
24
|
APPROVAL_RISK as RISK,
|
|
25
25
|
APPROVAL_DECISION as DECISION,
|
|
26
26
|
} from "@chainlesschain/session-core";
|
|
27
|
+
import {
|
|
28
|
+
classifyCodeAcquisition,
|
|
29
|
+
hasGlobalInstall,
|
|
30
|
+
applyRiskFloor,
|
|
31
|
+
recordInstallCommandAudit,
|
|
32
|
+
resolveInstallPolicy,
|
|
33
|
+
} from "./install-command-policy.js";
|
|
27
34
|
|
|
28
35
|
export const SHELL_TO_RISK = {
|
|
29
36
|
allow: RISK.LOW,
|
|
@@ -37,13 +44,49 @@ export async function evaluateShellCommandWithApproval({
|
|
|
37
44
|
sessionId = null,
|
|
38
45
|
approvalGate = null,
|
|
39
46
|
shellPolicyOptions = {},
|
|
47
|
+
installPolicy = null,
|
|
40
48
|
} = {}) {
|
|
41
49
|
const shellPolicy = sharedShellPolicy.evaluateShellCommandPolicy(
|
|
42
50
|
command,
|
|
43
51
|
shellPolicyOptions,
|
|
44
52
|
);
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
let riskLevel = SHELL_TO_RISK[shellPolicy.decision] || RISK.MEDIUM;
|
|
55
|
+
|
|
56
|
+
// Unified install-command classification (OPT-IN via installPolicy). A package
|
|
57
|
+
// install (npm/pip/winget/brew/…) fetches and runs third-party code, so it is
|
|
58
|
+
// a distinct, auditable permission type regardless of which tool does it. When
|
|
59
|
+
// a riskFloor is configured it RAISES (never lowers) the risk before gating;
|
|
60
|
+
// audit records the attempt. Absent/disabled → nothing runs, byte-unchanged.
|
|
61
|
+
// Explicit policy wins; otherwise self-activate from env (CC_INSTALL_AUDIT /
|
|
62
|
+
// CC_INSTALL_RISK_FLOOR) so the real run_shell path picks it up with no extra
|
|
63
|
+
// wiring. Both unset → disabled → the block below is skipped, byte-unchanged.
|
|
64
|
+
const effectiveInstallPolicy =
|
|
65
|
+
installPolicy || resolveInstallPolicy({ env: process.env });
|
|
66
|
+
let install = null;
|
|
67
|
+
if (effectiveInstallPolicy && effectiveInstallPolicy.enabled) {
|
|
68
|
+
const cls = classifyCodeAcquisition(command);
|
|
69
|
+
if (cls.flagged) {
|
|
70
|
+
install = cls;
|
|
71
|
+
if (effectiveInstallPolicy.riskFloor) {
|
|
72
|
+
riskLevel = applyRiskFloor(riskLevel, effectiveInstallPolicy.riskFloor);
|
|
73
|
+
}
|
|
74
|
+
if (effectiveInstallPolicy.audit) {
|
|
75
|
+
recordInstallCommandAudit(
|
|
76
|
+
{
|
|
77
|
+
command,
|
|
78
|
+
shellDecision: shellPolicy.decision,
|
|
79
|
+
riskLevel,
|
|
80
|
+
installs: cls.installs,
|
|
81
|
+
remoteExec: cls.remoteExec,
|
|
82
|
+
global: hasGlobalInstall(cls),
|
|
83
|
+
sessionId,
|
|
84
|
+
},
|
|
85
|
+
effectiveInstallPolicy.auditOpts || {},
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
47
90
|
|
|
48
91
|
// Hard-blocked rules bypass the gate entirely — the gate tier cannot
|
|
49
92
|
// up-authorize them.
|
|
@@ -56,6 +99,7 @@ export async function evaluateShellCommandWithApproval({
|
|
|
56
99
|
shellPolicy,
|
|
57
100
|
riskLevel,
|
|
58
101
|
policy: null,
|
|
102
|
+
install,
|
|
59
103
|
};
|
|
60
104
|
}
|
|
61
105
|
|
|
@@ -69,6 +113,7 @@ export async function evaluateShellCommandWithApproval({
|
|
|
69
113
|
shellPolicy,
|
|
70
114
|
riskLevel,
|
|
71
115
|
policy: null,
|
|
116
|
+
install,
|
|
72
117
|
};
|
|
73
118
|
}
|
|
74
119
|
|
|
@@ -87,6 +132,7 @@ export async function evaluateShellCommandWithApproval({
|
|
|
87
132
|
shellPolicy,
|
|
88
133
|
riskLevel,
|
|
89
134
|
policy: gateResult.policy,
|
|
135
|
+
install,
|
|
90
136
|
// Explainability passthrough (autoMode.decisions wrapper attaches these):
|
|
91
137
|
// which gate rule fired and why — surfaced in the permission chain.
|
|
92
138
|
gateReason: gateResult.reason ?? null,
|
package/src/repl/add-dir.js
CHANGED
|
@@ -61,10 +61,68 @@ export function resolveAddDir(input, { cwd, existing = [], deps } = {}) {
|
|
|
61
61
|
*/
|
|
62
62
|
export function formatAddDirRoots(cwd, extraRoots) {
|
|
63
63
|
const extras = Array.isArray(extraRoots) ? extraRoots : [];
|
|
64
|
-
const lines = [
|
|
64
|
+
const lines = [
|
|
65
|
+
`Working roots (${1 + extras.length}):`,
|
|
66
|
+
` ${cwd} (primary)`,
|
|
67
|
+
];
|
|
65
68
|
for (const r of extras) lines.push(` ${r}`);
|
|
66
69
|
if (extras.length === 0) {
|
|
67
70
|
lines.push(" (no extra roots — add one with /add-dir <dir>)");
|
|
68
71
|
}
|
|
69
72
|
return lines.join("\n");
|
|
70
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The full workspace root list a session advertises to MCP servers (`roots/list`):
|
|
77
|
+
* the primary cwd first, then each extra `--add-dir` / `/add-dir` root, all
|
|
78
|
+
* resolved absolute and de-duplicated (the primary is dropped from the extras if
|
|
79
|
+
* a caller re-added it). Pure. Mirrors what the system prompt advertises so an
|
|
80
|
+
* MCP server sees the same roots the model does.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} cwd primary working directory
|
|
83
|
+
* @param {string[]} extraRoots additional roots
|
|
84
|
+
* @returns {string[]} deduped absolute dirs, primary first
|
|
85
|
+
*/
|
|
86
|
+
export function workspaceRootDirs(cwd, extraRoots) {
|
|
87
|
+
const primary = nodePath.resolve(cwd);
|
|
88
|
+
const out = [primary];
|
|
89
|
+
const seen = new Set([primary]);
|
|
90
|
+
for (const r of Array.isArray(extraRoots) ? extraRoots : []) {
|
|
91
|
+
if (!r) continue;
|
|
92
|
+
const abs = nodePath.resolve(cwd, String(r));
|
|
93
|
+
if (seen.has(abs)) continue;
|
|
94
|
+
seen.add(abs);
|
|
95
|
+
out.push(abs);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Best-effort broadcast of a changed workspace-root list to every connected MCP
|
|
102
|
+
* client (via `client.setRoots(dirs)`, which advertises the new list AND fires
|
|
103
|
+
* `notifications/roots/list_changed` only when it actually changed). Skips
|
|
104
|
+
* nullish/duplicate clients and any without `setRoots`; a failing client never
|
|
105
|
+
* disturbs the others or the caller. Returns the number of clients notified.
|
|
106
|
+
* Claude-Code 2.1.203 roots-change parity for the `/add-dir` (and startup
|
|
107
|
+
* `--add-dir`) vector, mirroring what `/cd` already does via
|
|
108
|
+
* `notifyRootsListChanged`.
|
|
109
|
+
*
|
|
110
|
+
* @param {Array<object|null|undefined>} clients MCP clients (may repeat/null)
|
|
111
|
+
* @param {string[]} dirs absolute root dirs
|
|
112
|
+
* @returns {number}
|
|
113
|
+
*/
|
|
114
|
+
export function notifyMcpRootsChanged(clients, dirs) {
|
|
115
|
+
let notified = 0;
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
for (const c of Array.isArray(clients) ? clients : []) {
|
|
118
|
+
if (!c || seen.has(c) || typeof c.setRoots !== "function") continue;
|
|
119
|
+
seen.add(c);
|
|
120
|
+
try {
|
|
121
|
+
c.setRoots(dirs);
|
|
122
|
+
notified += 1;
|
|
123
|
+
} catch {
|
|
124
|
+
/* best-effort — one server's failure must not block the rest */
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return notified;
|
|
128
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -1045,6 +1045,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
1045
1045
|
// `undefined` so the runtime default-on path — and CC_PROJECT_MEMORY=0 — keep
|
|
1046
1046
|
// their existing behaviour byte-identically.
|
|
1047
1047
|
const _leanNoProjectMemory = options.projectMemory === false;
|
|
1048
|
+
let _loadedReplInstructions = null;
|
|
1048
1049
|
let _replBaseSystem = composeSystemPrompt(
|
|
1049
1050
|
buildSystemPrompt(process.cwd(), {
|
|
1050
1051
|
additionalDirectories,
|
|
@@ -1054,6 +1055,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
1054
1055
|
systemPrompt: options.systemPrompt,
|
|
1055
1056
|
appendSystemPrompt: options.appendSystemPrompt,
|
|
1056
1057
|
projectMemory: _leanNoProjectMemory ? false : undefined,
|
|
1058
|
+
onInstructionsLoaded: (loaded) => {
|
|
1059
|
+
_loadedReplInstructions = loaded;
|
|
1060
|
+
},
|
|
1057
1061
|
},
|
|
1058
1062
|
);
|
|
1059
1063
|
let _activeOutputStyle = null; // { name, body }
|
|
@@ -1088,6 +1092,25 @@ export async function startAgentRepl(options = {}) {
|
|
|
1088
1092
|
// best-effort — no output style
|
|
1089
1093
|
}
|
|
1090
1094
|
|
|
1095
|
+
// settings.json InstructionsLoaded hooks (observe-only): fire once with the
|
|
1096
|
+
// exact loaded instruction file set so a hook can audit which cc.md/CLAUDE.md/
|
|
1097
|
+
// AGENTS.md/rules are authoritative this session. Best-effort; no-op when
|
|
1098
|
+
// project memory is off or no hook is registered.
|
|
1099
|
+
if (_settingsHooks && _loadedReplInstructions) {
|
|
1100
|
+
try {
|
|
1101
|
+
const { runInstructionsLoadedHooks } =
|
|
1102
|
+
await import("../lib/settings-hook-events.cjs");
|
|
1103
|
+
const ctx = runInstructionsLoadedHooks(_settingsHooks, {
|
|
1104
|
+
files: _loadedReplInstructions.files,
|
|
1105
|
+
cwd: process.cwd(),
|
|
1106
|
+
sessionId,
|
|
1107
|
+
}).additionalContext;
|
|
1108
|
+
if (ctx) messages.push({ role: "system", content: ctx });
|
|
1109
|
+
} catch (_err) {
|
|
1110
|
+
// best-effort
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1091
1114
|
// settings.json SessionStart hooks → inject session context (observe-only).
|
|
1092
1115
|
if (_settingsHooks) {
|
|
1093
1116
|
try {
|
|
@@ -1267,6 +1290,23 @@ export async function startAgentRepl(options = {}) {
|
|
|
1267
1290
|
}
|
|
1268
1291
|
}
|
|
1269
1292
|
|
|
1293
|
+
// Seed connected MCP servers with the startup workspace roots when the session
|
|
1294
|
+
// began with extra `--add-dir` roots — otherwise `roots/list` would only ever
|
|
1295
|
+
// return the cwd until the first mid-session /add-dir. No-op (byte-unchanged)
|
|
1296
|
+
// when there are no extra roots. Best-effort.
|
|
1297
|
+
if (additionalDirectories.length > 0) {
|
|
1298
|
+
try {
|
|
1299
|
+
const { workspaceRootDirs, notifyMcpRootsChanged } =
|
|
1300
|
+
await import("./add-dir.js");
|
|
1301
|
+
notifyMcpRootsChanged(
|
|
1302
|
+
[_adhocMcp?.mcpClient, _bundleMcpClient],
|
|
1303
|
+
workspaceRootDirs(process.cwd(), additionalDirectories),
|
|
1304
|
+
);
|
|
1305
|
+
} catch {
|
|
1306
|
+
/* best-effort — root advertisement must not block session startup */
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1270
1310
|
// Apply bundle approval policy to this session (after both gate and sessionId are ready)
|
|
1271
1311
|
if (_bundleResolved?.approvalPolicy?.default && _approvalGate && sessionId) {
|
|
1272
1312
|
try {
|
|
@@ -2382,8 +2422,12 @@ export async function startAgentRepl(options = {}) {
|
|
|
2382
2422
|
if (trimmed === "/add-dir" || trimmed.startsWith("/add-dir ")) {
|
|
2383
2423
|
const arg = trimmed.slice("/add-dir".length).trim();
|
|
2384
2424
|
try {
|
|
2385
|
-
const {
|
|
2386
|
-
|
|
2425
|
+
const {
|
|
2426
|
+
resolveAddDir,
|
|
2427
|
+
formatAddDirRoots,
|
|
2428
|
+
workspaceRootDirs,
|
|
2429
|
+
notifyMcpRootsChanged,
|
|
2430
|
+
} = await import("./add-dir.js");
|
|
2387
2431
|
if (!arg) {
|
|
2388
2432
|
logger.log(
|
|
2389
2433
|
"\n" +
|
|
@@ -2417,6 +2461,14 @@ export async function startAgentRepl(options = {}) {
|
|
|
2417
2461
|
messages[0].content = _activeOutputStyle
|
|
2418
2462
|
? `${_replBaseSystem}\n\n${_activeOutputStyle.body}`
|
|
2419
2463
|
: _replBaseSystem;
|
|
2464
|
+
// The workspace root LIST changed — advertise it to connected MCP
|
|
2465
|
+
// servers and fire notifications/roots/list_changed so they re-query
|
|
2466
|
+
// roots/list (Claude-Code 2.1.203 parity, same as /cd but with an
|
|
2467
|
+
// explicit new root, not just a cwd-derived change).
|
|
2468
|
+
notifyMcpRootsChanged(
|
|
2469
|
+
[_adhocMcp?.mcpClient, _bundleMcpClient],
|
|
2470
|
+
workspaceRootDirs(process.cwd(), additionalDirectories),
|
|
2471
|
+
);
|
|
2420
2472
|
logger.log(chalk.green(`Added working root: ${res.dir}`));
|
|
2421
2473
|
}
|
|
2422
2474
|
}
|
|
@@ -3002,9 +3054,11 @@ export async function startAgentRepl(options = {}) {
|
|
|
3002
3054
|
logger.info(`cwd: ${process.cwd()}`);
|
|
3003
3055
|
} else {
|
|
3004
3056
|
try {
|
|
3057
|
+
const oldCwd = process.cwd();
|
|
3005
3058
|
const expanded = target.replace(/^~(?=$|[\\/])/, os.homedir());
|
|
3006
3059
|
process.chdir(path.resolve(process.cwd(), expanded));
|
|
3007
|
-
|
|
3060
|
+
const newCwd = process.cwd();
|
|
3061
|
+
logger.log(chalk.green(`cwd → ${newCwd}`));
|
|
3008
3062
|
// MCP roots derive from cwd — tell connected servers to re-query
|
|
3009
3063
|
// roots/list (Claude-Code 2.1.203 change-notification parity).
|
|
3010
3064
|
for (const c of [_adhocMcp?.mcpClient, _bundleMcpClient]) {
|
|
@@ -3016,6 +3070,34 @@ export async function startAgentRepl(options = {}) {
|
|
|
3016
3070
|
}
|
|
3017
3071
|
}
|
|
3018
3072
|
}
|
|
3073
|
+
// CwdChanged settings hooks (lifecycle-event parity): the working dir
|
|
3074
|
+
// just changed, so fire CwdChanged with old→new cwd — a hook can
|
|
3075
|
+
// observe/react (re-audit the new dir's trust, reload per-dir config).
|
|
3076
|
+
// Observe-only (a cwd change never gates flow); best-effort; no-op
|
|
3077
|
+
// (byte-unchanged) without a registered CwdChanged hook.
|
|
3078
|
+
if (_settingsHooks && oldCwd !== newCwd) {
|
|
3079
|
+
try {
|
|
3080
|
+
const { runCwdChangedHooks, dispatchAsyncHooks } =
|
|
3081
|
+
await import("../lib/settings-hook-events.cjs");
|
|
3082
|
+
const payload = { oldCwd, newCwd, sessionId };
|
|
3083
|
+
runCwdChangedHooks(_settingsHooks, payload);
|
|
3084
|
+
if (!_asyncHookSupervisor) {
|
|
3085
|
+
const { AsyncHookSupervisor } =
|
|
3086
|
+
await import("../lib/async-hook-supervisor.cjs");
|
|
3087
|
+
_asyncHookSupervisor = new AsyncHookSupervisor({
|
|
3088
|
+
persistStats: true,
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
dispatchAsyncHooks(
|
|
3092
|
+
_settingsHooks,
|
|
3093
|
+
"CwdChanged",
|
|
3094
|
+
{ old_cwd: oldCwd, cwd: newCwd, session_id: sessionId || null },
|
|
3095
|
+
{ cwd: newCwd, supervisor: _asyncHookSupervisor },
|
|
3096
|
+
);
|
|
3097
|
+
} catch {
|
|
3098
|
+
/* CwdChanged firing is best-effort */
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3019
3101
|
} catch (err) {
|
|
3020
3102
|
logger.error(`/cd failed: ${err.message}`);
|
|
3021
3103
|
}
|
|
@@ -707,6 +707,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
707
707
|
let sideEffectSeq = 0;
|
|
708
708
|
let currentSideEffectOpId = null;
|
|
709
709
|
let resumeSideEffectContext = null;
|
|
710
|
+
let resumeAsyncHookContext = null;
|
|
710
711
|
const persistSideEffectLedger = () => {
|
|
711
712
|
if (!persist) return;
|
|
712
713
|
try {
|
|
@@ -767,6 +768,34 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
767
768
|
} catch {
|
|
768
769
|
resumeSideEffectContext = null;
|
|
769
770
|
}
|
|
771
|
+
// On resume, recover any async-hook REWAKE (a background check that opted
|
|
772
|
+
// in and FAILED) that the previous run parked but died before draining —
|
|
773
|
+
// surface it to the model instead of silently swallowing the failure. The
|
|
774
|
+
// take also clears the bucket so it isn't replayed on a later resume.
|
|
775
|
+
try {
|
|
776
|
+
const { takePending } = await import("../lib/async-hook-queue.cjs");
|
|
777
|
+
const recovered = takePending(
|
|
778
|
+
{ sessionId, now: deps.now ? deps.now() : Date.now() },
|
|
779
|
+
deps.asyncHookQueuePath || undefined,
|
|
780
|
+
deps.asyncHookQueueFs || undefined,
|
|
781
|
+
);
|
|
782
|
+
if (Array.isArray(recovered) && recovered.length > 0) {
|
|
783
|
+
const lines = recovered.map((r) => {
|
|
784
|
+
const detail = r.error || r.additionalContext || "failed";
|
|
785
|
+
return ` • ${r.command} — ${detail}`;
|
|
786
|
+
});
|
|
787
|
+
resumeAsyncHookContext =
|
|
788
|
+
"Recovery notice — the previous run was interrupted after these " +
|
|
789
|
+
"background (async) hook checks FAILED but before you were told. " +
|
|
790
|
+
"Review each failure and decide whether it still needs action:\n" +
|
|
791
|
+
lines.join("\n");
|
|
792
|
+
writeErr(
|
|
793
|
+
`⚠ Recovered ${recovered.length} failed async-hook check(s) from interrupted run (resume ${resumeId}).\n`,
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
} catch {
|
|
797
|
+
resumeAsyncHookContext = null;
|
|
798
|
+
}
|
|
770
799
|
}
|
|
771
800
|
}
|
|
772
801
|
|
|
@@ -856,6 +885,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
856
885
|
// rules.md (in buildSystemPrompt) + the cc.md/CLAUDE.md block. Absent flag
|
|
857
886
|
// (undefined) leaves both paths byte-identical.
|
|
858
887
|
const _leanNoProjectMemory = options.projectMemory === false;
|
|
888
|
+
let _loadedInstructions = null;
|
|
859
889
|
const systemContent = composeSystemPrompt(
|
|
860
890
|
buildSystemPrompt(cwd, {
|
|
861
891
|
additionalDirectories,
|
|
@@ -867,6 +897,9 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
867
897
|
outputStyle: outputStyleBody,
|
|
868
898
|
instructionExcludes,
|
|
869
899
|
projectMemory: _leanNoProjectMemory ? false : undefined,
|
|
900
|
+
onInstructionsLoaded: (loaded) => {
|
|
901
|
+
_loadedInstructions = loaded;
|
|
902
|
+
},
|
|
870
903
|
},
|
|
871
904
|
);
|
|
872
905
|
|
|
@@ -896,6 +929,26 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
896
929
|
}
|
|
897
930
|
}
|
|
898
931
|
|
|
932
|
+
// settings.json InstructionsLoaded hooks (observe-only): the project-instruction
|
|
933
|
+
// block was just composed — fire with the EXACT loaded file set so a hook can
|
|
934
|
+
// audit which cc.md/CLAUDE.md/AGENTS.md/rules are authoritative this session.
|
|
935
|
+
// Any emitted context is injected like SessionStart's. Best-effort; no-op when
|
|
936
|
+
// project memory is off (no loaded set) or no hook is registered.
|
|
937
|
+
let instructionsLoadedContext = null;
|
|
938
|
+
if (settingsHooks && _loadedInstructions) {
|
|
939
|
+
try {
|
|
940
|
+
const { runInstructionsLoadedHooks } =
|
|
941
|
+
await import("../lib/settings-hook-events.cjs");
|
|
942
|
+
instructionsLoadedContext = runInstructionsLoadedHooks(settingsHooks, {
|
|
943
|
+
files: _loadedInstructions.files,
|
|
944
|
+
cwd,
|
|
945
|
+
sessionId,
|
|
946
|
+
}).additionalContext;
|
|
947
|
+
} catch (_err) {
|
|
948
|
+
instructionsLoadedContext = null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
899
952
|
// settings.json SessionStart hooks → inject session context (observe-only).
|
|
900
953
|
let sessionStartContext = null;
|
|
901
954
|
if (settingsHooks) {
|
|
@@ -950,12 +1003,18 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
950
1003
|
// No-op on a healthy alternating transcript.
|
|
951
1004
|
const messages = mergeConsecutiveMessages([
|
|
952
1005
|
{ role: "system", content: systemContent },
|
|
1006
|
+
...(instructionsLoadedContext
|
|
1007
|
+
? [{ role: "system", content: instructionsLoadedContext }]
|
|
1008
|
+
: []),
|
|
953
1009
|
...(sessionStartContext
|
|
954
1010
|
? [{ role: "system", content: sessionStartContext }]
|
|
955
1011
|
: []),
|
|
956
1012
|
...(resumeSideEffectContext
|
|
957
1013
|
? [{ role: "system", content: resumeSideEffectContext }]
|
|
958
1014
|
: []),
|
|
1015
|
+
...(resumeAsyncHookContext
|
|
1016
|
+
? [{ role: "system", content: resumeAsyncHookContext }]
|
|
1017
|
+
: []),
|
|
959
1018
|
...history,
|
|
960
1019
|
{ role: "user", content: userMessageContent },
|
|
961
1020
|
]);
|
|
@@ -1160,7 +1219,16 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
1160
1219
|
try {
|
|
1161
1220
|
const { AsyncHookSupervisor } =
|
|
1162
1221
|
await import("../lib/async-hook-supervisor.cjs");
|
|
1163
|
-
_hookSupervisor = new AsyncHookSupervisor({
|
|
1222
|
+
_hookSupervisor = new AsyncHookSupervisor({
|
|
1223
|
+
persistStats: true,
|
|
1224
|
+
// Durably park failed-rewake signals keyed by session so a crash before
|
|
1225
|
+
// the turn loop drains them doesn't lose the failure — recovered on the
|
|
1226
|
+
// next `--resume` (see the resume block above). Only a PERSISTABLE
|
|
1227
|
+
// session can be resumed, so the queue follows `persist`; a one-shot /
|
|
1228
|
+
// --ephemeral run stays byte-unchanged (no session id → no writes).
|
|
1229
|
+
persistQueue: persist,
|
|
1230
|
+
sessionId: persist ? sessionId : null,
|
|
1231
|
+
});
|
|
1164
1232
|
} catch {
|
|
1165
1233
|
_hookSupervisor = null; // async hooks are best-effort
|
|
1166
1234
|
}
|
|
@@ -1078,6 +1078,7 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
1078
1078
|
// cc.md/CLAUDE.md instruction block. Only `=== false` changes anything; when
|
|
1079
1079
|
// the flag is absent (undefined) both paths stay byte-identical.
|
|
1080
1080
|
const _leanNoProjectMemory = options.projectMemory === false;
|
|
1081
|
+
let _loadedInstructions = null;
|
|
1081
1082
|
const systemContent = composeSystemPrompt(
|
|
1082
1083
|
buildSystemPrompt(cwd, {
|
|
1083
1084
|
additionalDirectories,
|
|
@@ -1093,10 +1094,31 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
1093
1094
|
outputStyle: outputStyleBody,
|
|
1094
1095
|
instructionExcludes,
|
|
1095
1096
|
projectMemory: _leanNoProjectMemory ? false : undefined,
|
|
1097
|
+
onInstructionsLoaded: (loaded) => {
|
|
1098
|
+
_loadedInstructions = loaded;
|
|
1099
|
+
},
|
|
1096
1100
|
},
|
|
1097
1101
|
);
|
|
1098
1102
|
const messages = [{ role: "system", content: systemContent }];
|
|
1099
1103
|
|
|
1104
|
+
// settings.json InstructionsLoaded hooks (observe-only): fire once with the
|
|
1105
|
+
// exact loaded instruction file set right after the system prompt is built.
|
|
1106
|
+
// Best-effort; no-op when project memory is off or no hook is registered.
|
|
1107
|
+
if (settingsHooks && _loadedInstructions) {
|
|
1108
|
+
try {
|
|
1109
|
+
const { runInstructionsLoadedHooks } =
|
|
1110
|
+
await import("../lib/settings-hook-events.cjs");
|
|
1111
|
+
const ctx = runInstructionsLoadedHooks(settingsHooks, {
|
|
1112
|
+
files: _loadedInstructions.files,
|
|
1113
|
+
cwd,
|
|
1114
|
+
sessionId,
|
|
1115
|
+
}).additionalContext;
|
|
1116
|
+
if (ctx) messages.push({ role: "system", content: ctx });
|
|
1117
|
+
} catch (_err) {
|
|
1118
|
+
// best-effort
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1100
1122
|
// §3.5.13 flywheel consumption (design module 101): in a PDH session, lead
|
|
1101
1123
|
// with the user's standing feedback preferences learned ACROSS sessions
|
|
1102
1124
|
// (corrections + net sentiment) so the agent honours past corrections from
|