chainlesschain 0.162.166 → 0.162.168
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-CDzzuWZV.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DnurtK34.js → ActionButton-Dy-8MmQi.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DXSsV1am.js → Analytics-BtJ_VWkx.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-dqRpzNuD.js → AppLayout-DkjiI4VS.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BwdmYepK.js → Artifacts-h8PyDO86.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-oSX0kOUG.js → Audit-DiDV0C6l.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C2KxJahT.js → BackgroundAgents-DTw_aOaf.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B4TkxWkC.js → Backup-CihFuUO9.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CX5-KxeU.js → BaseInput-CqP2QNoR.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-O_HzD-PL.js → Chat-tm-ygX-p.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Br8saHQA.js → ChatBubbleRenderer-BVKEhj_y.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-ChepDpC1.js → Checkbox-BFMyRN3Q.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D9z8kc7q.js → Codegen-B1gTeoAS.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B-H5eoNw.js → Col-1GP8MvKL.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Ccyp_Kk5.js → Community-BLY938F6.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BNvBznp_.js → Compact-DVt_p36I.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D1FjRd8n.js → Compliance-DxqEBXVG.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CV9B9u-8.js → Cowork-CyWrM-T8.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BowJ4VIx.js → Cron-Domu6aI9.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BJc6jzRF.js → Crosschain-C8LKpjHJ.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BEp8NV6D.js → DID-XBy15RZ5.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Dls0B3YB.js → Dashboard-DDAyPD-V.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DQ7zXVtD.js → Dropdown-BHV0MZ13.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B9q79tOl.js → EmailListRenderer-DmFGW4PX.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CD8Ie-88.js → FamilyGuardDashboard-CqYdbKvd.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-5o7XU5Vf.js → Federation-YP5-Puv5.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DeKbMvOs.js → FormItemContext-BOmcIcTe.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CZiQvz_z.js → GenericCardRenderer-CvTZdoc4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CTvFxVV8.js → Git-Q2oxua0L.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DwzFIjHt.js → Governance-D-WpyWEU.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CJBk59c-.js → Inference-NUtsYHQo.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DfsaXN2z.js → KnowledgeGraph-BHQLa8fr.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVRYf18S.js → Logs-6WsR5Q1G.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DuWoDwMx.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BuIwyE4t.js → Marketplace-CigohRRW.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D3U0o78E.js → McpTools-jcp6jVLf.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-QYVmyb3b.js → Memory-B00NU1ph.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BFgrj6eZ.js → MobileBridge-Cs_p3PzU.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BFs2WljZ.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BZQu8TcH.js → Mtc-CSEflwzw.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BnFfEkLo.js → MtcAudit-dxSiEZ96.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-iUnNS9Cm.js → Multisig-CDAFEAyq.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DNNz1y9Y.js → NLProgramming-HoFpZNiB.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CZezNYRU.js → Notes-tAoQvVcE.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DBvrplIR.js → NotificationSettings-Czo2M9F4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CTDtee49.js → OrderTableRenderer-CM1kNTp6.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CCOUJX1R.js → Organization-Cudr0VOC.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DcZghdk0.js → Overflow-BSXeDKiN.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jmddWX9q.js → P2P-Cqv_g-F9.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-wfjMcczl.js → PdhVaultBrowser-7rjprPh8.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-DU1GJjca.js → Permissions-97pIDQKE.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BzTMzDF9.js → PersonalDataHub-bparK9eQ.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DPmjlpVc.js → Pipeline-CJgerM09.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B6HRLpfK.js → Privacy-JqFS3PN7.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Cyy86AsN.js → ProjectInit-DMITd-2c.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bl_WQxJ5.js → ProjectSettings-1UkdJ8Ii.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Co9yKThA.js → Projects-CYZqegqw.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DLa4NlIs.js → Providers-ChfXz3U0.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BvzM7q5f.js → QrScannerModal-Rzkka-mf.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BN8yyyUj.js → QuickAsk-DEz9HaON.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-QsVN4w0i.js → Recommend-EFGP8AVu.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-tkDXw-TY.js → RemoteSession-6um0lGGy.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DAifkGsy.js → Reputation-CA1BXTup.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D7dZEyak.js → Row-stTnDJ9y.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DB5sImTA.js → RssFeed-B10qCCoG.js} +2 -2
- package/src/assets/web-panel/assets/{Search-3py3TO9V.js → Search-DSJroZ6p.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DhNUfT_k.js → Security-BpXkwiMW.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DjJAA_5D.js → Services-BPQxjwK6.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-u95JjK3n.js → Skeleton-CfbKAMul.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DPo3L0Tz.js → Skills-CttYTfX0.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DWhkAZV1.js → Sla-ByluHich.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-PUegMtqV.js → SpeechSettings-BdZXKsfV.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BMNYrnrU.js → SyncSettings-xCR7iDzy.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CYm_oTJC.js → Tasks-qTko_INn.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Dw9Er5DN.js → Templates-CkIzhgDJ.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-7P3QIBSo.js → Tenant-BIaCaaK8.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-B2Qq38XP.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-DbCd1bDH.js → TimelineRenderer-D5eQDnEF.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-EQofpA0t.js → Tokens-DmeJ8NMQ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-ChNLLypd.js → Trigger-C5sgb3C7.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-dlVGUKs5.js → Trust-H2vOb3Rb.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BXyBUhNF.js → UkeySign-NmPtHK2u.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BE8-68ge.js → VideoEditing-DHv-Ti7K.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-7ZIJOwNg.js → Wallet-BYZ3-PRP.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DgNd2Amr.js → WebAuthn-BaSlP7om.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Z-Un-9l5.js → WorkflowEditor-CZ7IHuIi.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CtVDEkUc.js → chat-Dd9kY8gn.js} +1 -1
- package/src/assets/web-panel/assets/{colors-PXtJ2f1B.js → colors-yhzo6QSV.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-oN96nuBi.js → compact-item-C1JneVdM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-okCJXQXa.js → createContext-C_waVCg2.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DaDlyVDY.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-pcHm--4o.js → hasIn-CkVg0Xnh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeAWCGEK.js → index-1VYhIr6D.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC0bqrqV.js → index-BTMiVIBI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQYes2mk.js → index-BYOyb5X2.js} +1 -1
- package/src/assets/web-panel/assets/{index-vW0lwj0Y.js → index-BjDnI7oM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUUJ2WfG.js → index-BlJNvg-2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0rfJBU3.js → index-BlzbSUK9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Rk0X8lVz.js → index-BrN2hAeE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDR2mi1p.js → index-BsSsurcn.js} +1 -1
- package/src/assets/web-panel/assets/index-BtsLExcc.js +1 -0
- package/src/assets/web-panel/assets/{index-jAY55MSx.js → index-Bwuu3Bf3.js} +1 -1
- package/src/assets/web-panel/assets/{index-_SaFHNsG.js → index-Bx3A6X4J.js} +1 -1
- package/src/assets/web-panel/assets/{index-T3QE8LuV.js → index-C-Sscgnv.js} +1 -1
- package/src/assets/web-panel/assets/index-C3qDvvIb.js +1 -0
- package/src/assets/web-panel/assets/{index-BUTyx-Lk.js → index-C4HNdMCt.js} +1 -1
- package/src/assets/web-panel/assets/{index-nMqLdwao.js → index-CBwmwTNa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuME9k4Y.js → index-CErewFeT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHJIfdCV.js → index-CGtoL5mR.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0bfypsF.js → index-CUDjGt_V.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl6N7ZyX.js → index-C_qqYZT7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXhezK4d.js → index-C_riAa6d.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnhHxZul.js → index-CfEMjbhu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs1fGk1Z.js → index-ClQhh6v3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjVIZfdA.js → index-CmiLmG0i.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWUJlCvK.js → index-D1xIbAxQ.js} +3 -3
- package/src/assets/web-panel/assets/{index-DfL10EcN.js → index-D6UXDH2D.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4ravjdv.js → index-D7kQ_4uL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtsyPnF5.js → index-DPkqQ2Pr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ1ZzJjN.js → index-DUA96wEj.js} +1 -1
- package/src/assets/web-panel/assets/{index-5aO2V0V4.js → index-DXHk9Vof.js} +1 -1
- package/src/assets/web-panel/assets/{index-CopGaCI_.js → index-DapftLUr.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1efSh1X.js → index-Dez7TUgG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBcKBb1E.js → index-DhZgULsY.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bht9n8Sm.js → index-Diquc7-l.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAQktO-6.js → index-DuGtLiTW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK0H3pQW.js → index-U_nLXGIx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZKk4Lnm.js → index-_M8El5iz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cc_MkksW.js → index-_VmzCYFn.js} +1 -1
- package/src/assets/web-panel/assets/{index-mB1UV7wA.js → index-_rZxgFtc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEtzqJw3.js → index-ewb0-LoH.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CG1jyiI7.js → initDefaultProps-DNN6qtGa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C8BYa98D.js → motion-BcM9exeo.js} +1 -1
- package/src/assets/web-panel/assets/{move-D1hqwitW.js → move-DsKJ-YvA.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C2lol8rp.js → mtc-parser-glgGkxaK.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Ct6Zghfi.js → omit-BwHCU50S.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CSYbYfdc.js → pickAttrs-BVp-bLt7.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-WCk8di0z.js → placementArrow-CZNe0r-w.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CEC8CZ-3.js → responsiveObserve-DUafj9pU.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BsXyd7Lq.js → slide-H4NqXQxu.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-tDKldiUT.js → statusUtils-UjMzzGZ5.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BIS2unhx.js → styleChecker-qO5aCjyk.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-yFTVMDUv.js → useFlexGapSupport-ChecYwC8.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CZYbJwkO.js → useFs-BR0QvwO3.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-sbEHRLAM.js → usePersonalDataHub-DitK2Qgz.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BVjeSEQK.js → vnode-dxSb40dY.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BZKVvEZ6.js → zoom-ekJk8YB2.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 +8 -2
- package/src/harness/jsonl-session-store.js +9 -0
- package/src/harness/session-mirror.js +212 -6
- package/src/harness/worktree-isolator.js +10 -2
- package/src/lib/agent-core.js +1 -0
- package/src/lib/async-hook-queue.cjs +288 -0
- package/src/lib/async-hook-supervisor.cjs +52 -0
- package/src/lib/background-phase-reporter.js +116 -0
- package/src/lib/doctor-checkup.js +184 -4
- package/src/lib/hook-runner.cjs +248 -36
- package/src/lib/install-command-policy.js +343 -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/code-intelligence.js +5 -1
- package/src/lib/lsp/lsp-server-registry.js +14 -0
- package/src/lib/lsp/workspace-roots.js +115 -0
- package/src/lib/runtime-checkup.js +33 -0
- package/src/lib/settings-hook-events.cjs +173 -3
- package/src/lib/settings-hooks.cjs +4 -0
- package/src/lib/shell-approval.js +47 -1
- package/src/lib/sub-agent-context.js +13 -0
- package/src/repl/add-dir.js +59 -1
- package/src/repl/agent-repl.js +85 -3
- package/src/runtime/agent-core.js +411 -82
- package/src/runtime/headless-runner.js +111 -9
- package/src/runtime/headless-stream.js +39 -0
- package/src/runtime/system-prompt.js +39 -12
- package/src/workers/background-agent-worker.js +8 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-BL_WsWZM.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Bp5QB-46.js +0 -1
- package/src/assets/web-panel/assets/Terminal-ClPrfWW7.js +0 -3
- 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
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveServer,
|
|
12
12
|
resolveBin,
|
|
13
13
|
probeServers,
|
|
14
|
+
describeServer,
|
|
14
15
|
registerLanguageServer,
|
|
15
16
|
_resetPluginServers,
|
|
16
17
|
_deps,
|
|
@@ -126,3 +127,29 @@ describe("probeServers", () => {
|
|
|
126
127
|
expect(ids).toContain("typescript-language-server");
|
|
127
128
|
});
|
|
128
129
|
});
|
|
130
|
+
|
|
131
|
+
describe("describeServer", () => {
|
|
132
|
+
it("names the builtin server for a language even when not installed", () => {
|
|
133
|
+
existsFor([]); // nothing on PATH
|
|
134
|
+
const ts = describeServer("typescript");
|
|
135
|
+
expect(ts.id).toBe("typescript-language-server");
|
|
136
|
+
expect(ts.bins).toContain("typescript-language-server");
|
|
137
|
+
// Independent of install state — it describes the EXPECTED server.
|
|
138
|
+
expect(describeServer("python").id).toBe("pyright");
|
|
139
|
+
expect(describeServer("go").id).toBe("gopls");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("returns null for an unsupported language", () => {
|
|
143
|
+
expect(describeServer("cobol")).toBe(null);
|
|
144
|
+
expect(describeServer(undefined)).toBe(null);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("prefers a registered plugin server over the builtin", () => {
|
|
148
|
+
registerLanguageServer({
|
|
149
|
+
languageId: "typescript",
|
|
150
|
+
command: "my-ts-lsp",
|
|
151
|
+
id: "plugin-ts",
|
|
152
|
+
});
|
|
153
|
+
expect(describeServer("typescript").id).toBe("plugin-ts");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -64,7 +64,11 @@ export class CodeIntelligence {
|
|
|
64
64
|
/* plugin LSP contributions are best-effort */
|
|
65
65
|
}
|
|
66
66
|
this.manager =
|
|
67
|
-
opts.manager ||
|
|
67
|
+
opts.manager ||
|
|
68
|
+
new LSPManager({
|
|
69
|
+
projectRoot: opts.projectRoot,
|
|
70
|
+
...(opts.lspOptions || {}),
|
|
71
|
+
});
|
|
68
72
|
this._ownsManager = !opts.manager;
|
|
69
73
|
// One-shot callers (the `cc code-intel` CLI) spawn a cold server per run and
|
|
70
74
|
// must wait for the project to load before querying; the long-lived agent
|
|
@@ -232,3 +232,17 @@ export function probeServers(projectRoot) {
|
|
|
232
232
|
export function supportedExtensions() {
|
|
233
233
|
return Object.keys(EXTENSION_LANGUAGE).sort();
|
|
234
234
|
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Describe the server that WOULD serve a languageId, whether or not its binary
|
|
238
|
+
* is installed — the first plugin candidate, else the builtin. Unlike
|
|
239
|
+
* `resolveServer` (which returns null when nothing is on PATH), this names the
|
|
240
|
+
* expected server so a doctor / status view can say *what to install*. Returns
|
|
241
|
+
* `{ id, bins }` or null for an unsupported language.
|
|
242
|
+
*/
|
|
243
|
+
export function describeServer(languageId) {
|
|
244
|
+
const candidates = [...(pluginServers.get(languageId) || [])];
|
|
245
|
+
if (BUILTIN_SERVERS[languageId]) candidates.push(BUILTIN_SERVERS[languageId]);
|
|
246
|
+
const def = candidates[0];
|
|
247
|
+
return def ? { id: def.id, bins: [...def.bins] } : null;
|
|
248
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace-roots — pure helpers for multi-root LSP workspaces (P2 LSP slice,
|
|
3
|
+
* CLAUDE_CODE_CLI_INCREMENTAL_GAP_ANALYSIS_2026-07-12 §"LSP 与 Code Review").
|
|
4
|
+
*
|
|
5
|
+
* A session started with `--add-dir` works across several project roots, but
|
|
6
|
+
* `code_intelligence` always keyed its shared language-server pool on the cwd
|
|
7
|
+
* alone — a query for a file living in an additional root spawned (or reused)
|
|
8
|
+
* the WRONG project's server, and `workspace_symbols` could only ever see the
|
|
9
|
+
* cwd project. These helpers make root selection explicit and testable:
|
|
10
|
+
*
|
|
11
|
+
* - `workspaceRootsFor(cwd, additionalDirectories)` — the deduped, resolved
|
|
12
|
+
* root list (cwd first, mirroring the roots the system prompt advertises).
|
|
13
|
+
* - `pickRootForFile(file, roots)` — the root that CONTAINS the file
|
|
14
|
+
* (deepest match wins so nested roots resolve to the more specific
|
|
15
|
+
* project); falls back to the first root (cwd) for out-of-root files,
|
|
16
|
+
* which is the pre-multi-root behavior.
|
|
17
|
+
* - `mergeWorkspaceSymbolResults(results, roots)` — fold per-root
|
|
18
|
+
* `workspaceSymbols` outcomes into one: available when ANY root answered,
|
|
19
|
+
* symbols concatenated in root order and stamped with their `root` so
|
|
20
|
+
* same-named symbols from different projects stay unambiguous.
|
|
21
|
+
*
|
|
22
|
+
* Pure + IO-free (path math only) so agent-core can consume them without any
|
|
23
|
+
* new export surface of its own.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
|
|
28
|
+
/** Case-fold for prefix comparison on case-insensitive filesystems. */
|
|
29
|
+
function fold(p) {
|
|
30
|
+
return process.platform === "win32" ? p.toLowerCase() : p;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolved, deduped workspace roots — cwd first, then each additional
|
|
35
|
+
* directory. Non-string/empty entries are dropped; never returns empty (falls
|
|
36
|
+
* back to process.cwd()).
|
|
37
|
+
*/
|
|
38
|
+
export function workspaceRootsFor(cwd, additionalDirectories) {
|
|
39
|
+
const out = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const push = (dir) => {
|
|
42
|
+
if (typeof dir !== "string" || dir.trim() === "") return;
|
|
43
|
+
const resolved = path.resolve(dir);
|
|
44
|
+
const key = fold(resolved);
|
|
45
|
+
if (seen.has(key)) return;
|
|
46
|
+
seen.add(key);
|
|
47
|
+
out.push(resolved);
|
|
48
|
+
};
|
|
49
|
+
push(cwd || process.cwd());
|
|
50
|
+
for (const d of Array.isArray(additionalDirectories)
|
|
51
|
+
? additionalDirectories
|
|
52
|
+
: []) {
|
|
53
|
+
push(d);
|
|
54
|
+
}
|
|
55
|
+
if (out.length === 0) out.push(path.resolve(process.cwd()));
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The workspace root that contains `file` — deepest containing root wins
|
|
61
|
+
* (nested roots resolve to the more specific project). Files outside every
|
|
62
|
+
* root fall back to the FIRST root (cwd), preserving the single-root
|
|
63
|
+
* behavior byte-for-byte.
|
|
64
|
+
*/
|
|
65
|
+
export function pickRootForFile(file, roots) {
|
|
66
|
+
const list = Array.isArray(roots) && roots.length > 0 ? roots : null;
|
|
67
|
+
if (!list) return path.resolve(process.cwd());
|
|
68
|
+
if (typeof file !== "string" || file.trim() === "") return list[0];
|
|
69
|
+
const f = fold(path.resolve(file));
|
|
70
|
+
let best = null;
|
|
71
|
+
for (const root of list) {
|
|
72
|
+
const r = fold(path.resolve(root));
|
|
73
|
+
if (f === r || f.startsWith(r + path.sep)) {
|
|
74
|
+
if (best === null || r.length > fold(best).length) {
|
|
75
|
+
best = path.resolve(root);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return best || list[0];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Merge per-root `workspaceSymbols` results (same order as `roots`).
|
|
84
|
+
* Any available root makes the merged result available; each symbol is
|
|
85
|
+
* stamped with its `root`. All-unavailable folds the reasons together so the
|
|
86
|
+
* caller still sees WHY (first reason wins the summary, all are listed).
|
|
87
|
+
*/
|
|
88
|
+
export function mergeWorkspaceSymbolResults(results, roots = []) {
|
|
89
|
+
const list = Array.isArray(results) ? results : [];
|
|
90
|
+
const symbols = [];
|
|
91
|
+
const reasons = [];
|
|
92
|
+
let anyAvailable = false;
|
|
93
|
+
for (let i = 0; i < list.length; i++) {
|
|
94
|
+
const r = list[i];
|
|
95
|
+
if (r && r.available) {
|
|
96
|
+
anyAvailable = true;
|
|
97
|
+
for (const s of Array.isArray(r.symbols) ? r.symbols : []) {
|
|
98
|
+
symbols.push(roots[i] ? { ...s, root: roots[i] } : s);
|
|
99
|
+
}
|
|
100
|
+
} else if (r) {
|
|
101
|
+
reasons.push(
|
|
102
|
+
roots[i]
|
|
103
|
+
? `${roots[i]}: ${r.reason || "unavailable"}`
|
|
104
|
+
: r.reason || "unavailable",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!anyAvailable) {
|
|
109
|
+
return {
|
|
110
|
+
available: false,
|
|
111
|
+
reason: reasons.join("; ") || "language server unavailable",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return { available: true, symbols };
|
|
115
|
+
}
|
|
@@ -437,6 +437,39 @@ export function checkDuplicateSkills(entries = []) {
|
|
|
437
437
|
return out;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
/**
|
|
441
|
+
* LSP READINESS: languages the project actually contains but for which no
|
|
442
|
+
* language server is installed — the silent reason `code_intelligence` /
|
|
443
|
+
* `cc code-intel` quietly degrades to plain text search. Unlike
|
|
444
|
+
* `checkPluginsAndLsp` (live server health) this is a STATIC install-gap check a
|
|
445
|
+
* separate `cc doctor` process can compute without any server running: it
|
|
446
|
+
* correlates source files present with `resolveServer` availability. Only
|
|
447
|
+
* present-but-missing languages warn; installed languages produce nothing (no
|
|
448
|
+
* noise). Each `languages` entry: `{ languageId, fileCount, available, serverId,
|
|
449
|
+
* exampleBin }`.
|
|
450
|
+
*/
|
|
451
|
+
export function checkLspReadiness(languages = []) {
|
|
452
|
+
const out = [];
|
|
453
|
+
for (const l of languages) {
|
|
454
|
+
if (!l || !l.languageId) continue;
|
|
455
|
+
const fileCount = Number(l.fileCount) || 0;
|
|
456
|
+
if (fileCount <= 0 || l.available) continue;
|
|
457
|
+
const server = l.serverId || `a ${l.languageId} language server`;
|
|
458
|
+
const install = l.exampleBin ? ` (e.g. install ${l.exampleBin})` : "";
|
|
459
|
+
out.push(
|
|
460
|
+
finding(
|
|
461
|
+
"lsp-server-missing",
|
|
462
|
+
"lsp",
|
|
463
|
+
"warn",
|
|
464
|
+
`${fileCount} ${l.languageId} file(s) present but no language server installed${install}`,
|
|
465
|
+
`install ${server} for real code-intelligence, or ignore to keep text-search fallback`,
|
|
466
|
+
{ ref: l.languageId, fileCount, serverId: l.serverId || null },
|
|
467
|
+
),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
|
|
440
473
|
/** Plugins / LSP servers reported unhealthy or dead. */
|
|
441
474
|
export function checkPluginsAndLsp(plugins = []) {
|
|
442
475
|
const out = [];
|
|
@@ -47,7 +47,15 @@ function withDeliveryId(event, payload, { sessionId, traceId, parentId } = {}) {
|
|
|
47
47
|
/* best-effort — recording must not break the turn */
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// trace_id/parent_id are stamped onto the DELIVERED payload only when the
|
|
51
|
+
// caller actually threaded them (agent-core context) — absent they are
|
|
52
|
+
// omitted entirely, keeping every legacy payload byte-identical.
|
|
53
|
+
return {
|
|
54
|
+
...payload,
|
|
55
|
+
event_id: env.event_id,
|
|
56
|
+
...(env.trace_id ? { trace_id: env.trace_id } : {}),
|
|
57
|
+
...(env.parent_id ? { parent_id: env.parent_id } : {}),
|
|
58
|
+
};
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
@@ -178,6 +186,159 @@ function runSessionStartHooks(settingsHooks, { source, cwd, sessionId } = {}) {
|
|
|
178
186
|
return { additionalContext: aggregateContext(outcome.results) };
|
|
179
187
|
}
|
|
180
188
|
|
|
189
|
+
/**
|
|
190
|
+
* CwdChanged settings hooks (observe + context injection). Fired when the
|
|
191
|
+
* working directory changes mid-session (`/cd`) — a distinct lifecycle event
|
|
192
|
+
* (Claude-Code-parity extension) automation can react to (re-audit trust of the
|
|
193
|
+
* new dir, reload a per-dir tool config, …). Observe-only: a cwd change never
|
|
194
|
+
* gates flow, so any `block` decision is ignored and only emitted context is
|
|
195
|
+
* returned. No-op (byte-unchanged) without a registered CwdChanged hook.
|
|
196
|
+
* @returns {{ additionalContext:string|null }}
|
|
197
|
+
*/
|
|
198
|
+
function runCwdChangedHooks(
|
|
199
|
+
settingsHooks,
|
|
200
|
+
{ oldCwd, newCwd, cwd, sessionId } = {},
|
|
201
|
+
) {
|
|
202
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
203
|
+
const matched = collectHooks(settingsHooks, "CwdChanged", newCwd || "");
|
|
204
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
205
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
206
|
+
const payload = withDeliveryId(
|
|
207
|
+
"CwdChanged",
|
|
208
|
+
{
|
|
209
|
+
hook_event_name: "CwdChanged",
|
|
210
|
+
old_cwd: oldCwd || null,
|
|
211
|
+
cwd: newCwd || cwd || null,
|
|
212
|
+
session_id: sessionId || null,
|
|
213
|
+
},
|
|
214
|
+
{ sessionId },
|
|
215
|
+
);
|
|
216
|
+
const outcome = runHooks(sync, payload, {
|
|
217
|
+
cwd: newCwd || cwd,
|
|
218
|
+
event: "CwdChanged",
|
|
219
|
+
});
|
|
220
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* WorktreeCreate settings hooks (observe + context injection). Fired right after
|
|
225
|
+
* `cc agent --worktree` carves a fresh isolated git worktree — a hook can react
|
|
226
|
+
* (register the worktree with an external tracker, seed per-branch tooling,
|
|
227
|
+
* warm a build cache). The matcher target is the branch name, so a regex matcher
|
|
228
|
+
* can scope to a branch pattern. Observe-only: worktree setup already succeeded,
|
|
229
|
+
* so a `block` decision is ignored and only emitted context is returned. No-op
|
|
230
|
+
* (byte-unchanged) without a registered WorktreeCreate hook.
|
|
231
|
+
* @returns {{ additionalContext:string|null }}
|
|
232
|
+
*/
|
|
233
|
+
function runWorktreeCreateHooks(
|
|
234
|
+
settingsHooks,
|
|
235
|
+
{ worktreePath, branch, baseSha, cwd, sessionId } = {},
|
|
236
|
+
) {
|
|
237
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
238
|
+
const matched = collectHooks(settingsHooks, "WorktreeCreate", branch || "");
|
|
239
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
240
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
241
|
+
const payload = withDeliveryId(
|
|
242
|
+
"WorktreeCreate",
|
|
243
|
+
{
|
|
244
|
+
hook_event_name: "WorktreeCreate",
|
|
245
|
+
worktree_path: worktreePath || null,
|
|
246
|
+
branch: branch || null,
|
|
247
|
+
base_sha: baseSha || null,
|
|
248
|
+
cwd: cwd || worktreePath || null,
|
|
249
|
+
session_id: sessionId || null,
|
|
250
|
+
},
|
|
251
|
+
{ sessionId },
|
|
252
|
+
);
|
|
253
|
+
const outcome = runHooks(sync, payload, {
|
|
254
|
+
cwd: cwd || worktreePath,
|
|
255
|
+
event: "WorktreeCreate",
|
|
256
|
+
});
|
|
257
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* WorktreeRemove settings hooks (observe + context injection). Fired when a
|
|
262
|
+
* `cc agent --worktree` session finishes and its worktree is torn down (or when
|
|
263
|
+
* a `keep` decision leaves it in place — `removed` distinguishes the two) — a
|
|
264
|
+
* hook can react (unregister the worktree, prune a per-branch cache, notify CI).
|
|
265
|
+
* Matcher target is the branch name. Observe-only: teardown already happened, so
|
|
266
|
+
* a `block` decision is ignored and only emitted context is returned. No-op
|
|
267
|
+
* (byte-unchanged) without a registered WorktreeRemove hook.
|
|
268
|
+
* @returns {{ additionalContext:string|null }}
|
|
269
|
+
*/
|
|
270
|
+
function runWorktreeRemoveHooks(
|
|
271
|
+
settingsHooks,
|
|
272
|
+
{ worktreePath, branch, removed, reason, cwd, sessionId } = {},
|
|
273
|
+
) {
|
|
274
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
275
|
+
const matched = collectHooks(settingsHooks, "WorktreeRemove", branch || "");
|
|
276
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
277
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
278
|
+
const payload = withDeliveryId(
|
|
279
|
+
"WorktreeRemove",
|
|
280
|
+
{
|
|
281
|
+
hook_event_name: "WorktreeRemove",
|
|
282
|
+
worktree_path: worktreePath || null,
|
|
283
|
+
branch: branch || null,
|
|
284
|
+
removed: removed === true,
|
|
285
|
+
reason: reason || null,
|
|
286
|
+
cwd: cwd || worktreePath || null,
|
|
287
|
+
session_id: sessionId || null,
|
|
288
|
+
},
|
|
289
|
+
{ sessionId },
|
|
290
|
+
);
|
|
291
|
+
const outcome = runHooks(sync, payload, {
|
|
292
|
+
cwd: cwd || worktreePath,
|
|
293
|
+
event: "WorktreeRemove",
|
|
294
|
+
});
|
|
295
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* InstructionsLoaded settings hooks (observe + context injection). Fired once at
|
|
300
|
+
* session start after the project-instruction block (cc.md / CLAUDE.md / AGENTS.md
|
|
301
|
+
* hierarchy + `.claude/rules/*`) is composed into the system prompt — a hook can
|
|
302
|
+
* react to WHICH instruction files are authoritative this session (audit them,
|
|
303
|
+
* warn on an unexpected local override, load a companion tool config). The
|
|
304
|
+
* caller passes the EXACT loaded set; this producer trims each entry to
|
|
305
|
+
* `{path, scope, truncated}` (never the file CONTENT — a hook that wants content
|
|
306
|
+
* reads the path). Observe-only: instructions are already loaded, so a `block`
|
|
307
|
+
* decision is ignored and only emitted context is returned. No-op (byte-unchanged)
|
|
308
|
+
* without a registered InstructionsLoaded hook.
|
|
309
|
+
* @returns {{ additionalContext:string|null }}
|
|
310
|
+
*/
|
|
311
|
+
function runInstructionsLoadedHooks(
|
|
312
|
+
settingsHooks,
|
|
313
|
+
{ files, cwd, sessionId } = {},
|
|
314
|
+
) {
|
|
315
|
+
if (!settingsHooks) return { additionalContext: null };
|
|
316
|
+
const matched = collectHooks(settingsHooks, "InstructionsLoaded", "");
|
|
317
|
+
const { sync } = partitionAsyncHooks(matched);
|
|
318
|
+
if (sync.length === 0) return { additionalContext: null };
|
|
319
|
+
const list = Array.isArray(files) ? files : [];
|
|
320
|
+
const payload = withDeliveryId(
|
|
321
|
+
"InstructionsLoaded",
|
|
322
|
+
{
|
|
323
|
+
hook_event_name: "InstructionsLoaded",
|
|
324
|
+
files: list.map((f) => ({
|
|
325
|
+
path: f?.path || null,
|
|
326
|
+
scope: f?.scope || null,
|
|
327
|
+
truncated: f?.truncated === true,
|
|
328
|
+
})),
|
|
329
|
+
count: list.length,
|
|
330
|
+
cwd: cwd || null,
|
|
331
|
+
session_id: sessionId || null,
|
|
332
|
+
},
|
|
333
|
+
{ sessionId },
|
|
334
|
+
);
|
|
335
|
+
const outcome = runHooks(sync, payload, {
|
|
336
|
+
cwd,
|
|
337
|
+
event: "InstructionsLoaded",
|
|
338
|
+
});
|
|
339
|
+
return { additionalContext: aggregateContext(outcome.results) };
|
|
340
|
+
}
|
|
341
|
+
|
|
181
342
|
/**
|
|
182
343
|
* Generic observe-only fire (SessionEnd / Stop / PreCompact). Returns the raw
|
|
183
344
|
* runHooks outcome so callers can read a block reason; never gates flow here.
|
|
@@ -190,7 +351,7 @@ function runObserveHooks(
|
|
|
190
351
|
settingsHooks,
|
|
191
352
|
event,
|
|
192
353
|
payload = {},
|
|
193
|
-
{ cwd, matchTarget } = {},
|
|
354
|
+
{ cwd, matchTarget, traceId, parentId } = {},
|
|
194
355
|
) {
|
|
195
356
|
if (!settingsHooks) return { decision: "continue", results: [] };
|
|
196
357
|
const matched = collectHooks(settingsHooks, event, matchTarget || "");
|
|
@@ -198,14 +359,23 @@ function runObserveHooks(
|
|
|
198
359
|
if (sync.length === 0) return { decision: "continue", results: [] };
|
|
199
360
|
return runHooks(
|
|
200
361
|
sync,
|
|
201
|
-
withDeliveryId(
|
|
362
|
+
withDeliveryId(
|
|
363
|
+
event,
|
|
364
|
+
{ hook_event_name: event, cwd, ...payload },
|
|
365
|
+
{ traceId, parentId },
|
|
366
|
+
),
|
|
202
367
|
{ cwd, event },
|
|
203
368
|
);
|
|
204
369
|
}
|
|
205
370
|
|
|
206
371
|
module.exports = {
|
|
372
|
+
withDeliveryId,
|
|
207
373
|
runUserPromptSubmitHooks,
|
|
208
374
|
runSessionStartHooks,
|
|
375
|
+
runCwdChangedHooks,
|
|
376
|
+
runWorktreeCreateHooks,
|
|
377
|
+
runWorktreeRemoveHooks,
|
|
378
|
+
runInstructionsLoadedHooks,
|
|
209
379
|
runObserveHooks,
|
|
210
380
|
aggregateContext,
|
|
211
381
|
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,
|
|
@@ -87,6 +87,10 @@ export class SubAgentContext {
|
|
|
87
87
|
// restricts run_skill/list_skills in this context's loop to those skills.
|
|
88
88
|
this.skillAllowlist =
|
|
89
89
|
options.skillAllowlist != null ? options.skillAllowlist : null;
|
|
90
|
+
// Hook-envelope tracing: the spawning run's id — this child loop stamps it
|
|
91
|
+
// as parent_id on every settings-hook payload it fires (its own runId
|
|
92
|
+
// becomes the trace_id). null → top-level semantics (no parent).
|
|
93
|
+
this._hookParentTraceId = options.hookParentTraceId || null;
|
|
90
94
|
this.cwd = options.cwd || process.cwd();
|
|
91
95
|
this.status = "active";
|
|
92
96
|
this.result = null;
|
|
@@ -98,6 +102,10 @@ export class SubAgentContext {
|
|
|
98
102
|
this._worktreePath = null;
|
|
99
103
|
this._worktreeBranch = null;
|
|
100
104
|
this._repoDir = this.cwd;
|
|
105
|
+
// Optional worktree creation options (large-monorepo): { sparsePaths,
|
|
106
|
+
// symlinkDirectories } — only materialize the needed packages and reuse
|
|
107
|
+
// approved dep dirs. null → full checkout (byte-identical default).
|
|
108
|
+
this._worktreeOptions = options.worktreeOptions || null;
|
|
101
109
|
|
|
102
110
|
// ── Isolated state ──────────────────────────────────────────────
|
|
103
111
|
// Independent message history — never shared with parent
|
|
@@ -254,6 +262,7 @@ export class SubAgentContext {
|
|
|
254
262
|
this.cwd = wtPath;
|
|
255
263
|
return this._runCore(userPrompt, loopOptions);
|
|
256
264
|
},
|
|
265
|
+
this._worktreeOptions || {},
|
|
257
266
|
);
|
|
258
267
|
|
|
259
268
|
// Annotate result with worktree info + diff preview
|
|
@@ -328,6 +337,10 @@ export class SubAgentContext {
|
|
|
328
337
|
subAgentContract: this.subAgentContract,
|
|
329
338
|
// Skill allow-list restricting run_skill/list_skills in this loop.
|
|
330
339
|
skillAllowlist: this.skillAllowlist,
|
|
340
|
+
// Parent trace for hook envelopes (absent → key omitted, no parent_id).
|
|
341
|
+
...(this._hookParentTraceId
|
|
342
|
+
? { hookParentTraceId: this._hookParentTraceId }
|
|
343
|
+
: {}),
|
|
331
344
|
...loopOptions,
|
|
332
345
|
};
|
|
333
346
|
if (this.iterationBudget) {
|
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
|
+
}
|