chainlesschain 0.162.38 → 0.162.40
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/README.md +368 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-DV0Q9zKL.js → AIOps-CPmKv82o.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C6vH8rhL.js → ActionButton-BNDYY7Qd.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BvPDc2ui.js → Analytics-BgCMCOsk.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CWnyqTqY.js → AppLayout-Dv4oJcqS.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-BzenidV4.js → Audit-5iV3yrGa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CSl7bNwK.js → Backup-CHDhnbzF.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DAY3iHIq.js → BaseInput-B6reFkra.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Jyhm9fgk.js → Chat-DwS5YyE2.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CwlAnVjy.js → ChatBubbleRenderer-CqXa87Hw.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-D4rwURAi.js → Checkbox-yiW0M4RE.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DYdjTEfC.js → Codegen-DoiVuD_g.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DsVyZ_fS.js → Col-BVASLexk.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CjCpl27Q.js → Community-D6KQ7JoU.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-kt18dsjm.js → Compact-Bl9Uhb6v.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BV5urquU.js → Compliance-MM31-dba.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-C4SovPWC.js → Cowork-PjU_1ieD.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-uuNs_xzA.js → Cron-DorNtPZL.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DR5a65tR.js → Crosschain-Bm5ts2Kw.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B1KTf2-5.js → DID-7Y3jlFdY.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Dkj7XgED.js → Dashboard-1oE532bG.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BhXCuJ19.js → Dropdown-hJlOPs0s.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DG8365Iv.js → EmailListRenderer-BEqJxKaO.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BdHGPu39.js → FamilyGuardDashboard-BvCGwB6X.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Dwvxl0zR.js → Federation-CsXI72e5.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BVmhCVWU.js → FormItemContext-Dh9SMul-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DDPjvF2s.js → GenericCardRenderer-9edWzrtG.js} +1 -1
- package/src/assets/web-panel/assets/{Git-foK6WTSr.js → Git-ZYhNL8Xk.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CfqMdu6Y.js → Governance-BwAdp8QA.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BKrLO4GO.js → Inference-5C-M1XsH.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-6o6Q-mmF.js → KnowledgeGraph-zFAi-zCi.js} +1 -1
- package/src/assets/web-panel/assets/Logs-BZsEdbgE.js +2 -0
- package/src/assets/web-panel/assets/{Marketplace-BWkfEocP.js → Marketplace-BP6gErRK.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BPebQbWU.js → McpTools-CXVzoLrd.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-C0Dq-X3C.js → Memory-BIpChb4-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DRBoutTY.js → MobileBridge-B4O7wDT8.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-7VPMoHus.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-Cj3QPM9p.js → Mtc-BTmEyTM5.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-rBQYbfQR.js → MtcAudit-CsbG9LlV.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Dbuy4OY4.js → Multisig-CL8yoGon.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CMnt1se-.js → NLProgramming-C2cIlIp_.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BX9tSCiF.js → Notes-7aBk_n_M.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BFeirVRq.js → NotificationSettings-BuhQk4rJ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-ybiMlKQW.js → OrderTableRenderer-mqMFZu0x.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-kTfRxKqk.js → Organization-CAdq-170.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CtuCAzwV.js → Overflow--Xn0E787.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-KfbciaP3.js → P2P-DYt3YAXI.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-bqEUFhgC.js → PdhVaultBrowser-Bgb_v8WN.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-BgMypz-z.js → Permissions-DoFlmoaW.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-C3zUE-1z.js → PersonalDataHub-C-FJB3a0.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-iX-pYHpC.js → Pipeline-3bL2RzzL.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B01uzeFM.js → Privacy-c4igYUCF.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-TsfbzJp7.js → ProjectInit-C0QS1UPR.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-iGvMp8sM.js → ProjectSettings-CkYC0xkE.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Be9k29iQ.js → Projects-Di17SYft.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-C9Pc8dqo.js → Providers-41NySsLt.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DN_yFiVO.js → QuickAsk-DHq9pD7z.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CvSNgl7H.js → Recommend-CLjgFPLv.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-S6BCz8xH.js → Reputation-EIrgErm3.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CTRYCaqP.js → Row-GAvKzKH7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Cu8_P5ll.js → RssFeed-CYCNsVmD.js} +3 -3
- package/src/assets/web-panel/assets/{Search-rZ1Xza_U.js → Search-DWOE32k8.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CF43IJHX.js → Security-Dgh8Jevn.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BobNHzne.js → Services-BxdgP67N.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWJ2kfuI.js → Skeleton-D-xT4ZkA.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-AmEZgHYr.js → Skills-BKN4lfSa.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DTS-fBiY.js → Sla--N1TudpS.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DEr6MHRU.js → SpeechSettings-B0vfJpEh.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CVs9alv_.js → SyncSettings-BuBAbPAh.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-4XugjJ87.js +1 -0
- package/src/assets/web-panel/assets/{Templates-CTNjZRKA.js → Templates-DI2giLgc.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DPbXg0Pg.js → Tenant-BiTWvm0g.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DhKXcPw2.js → Terminal-vV6AWGDi.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-B0DMZOpk.js → TimelineRenderer-BmgzKdAp.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-RvWuBXgg.js → Tokens-Nvupdm6p.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-2O-BaTQG.js → Trigger-DRfR77WJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-6qY35L-C.js → Trust-De0Jal_6.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DhV1wYtQ.js → UkeySign-Dzo4-VAM.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DgqA5UZm.js → VideoEditing-hg2ytiJB.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DJRYdUAK.js → Wallet--bU5-gRh.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C2W-x0cg.js → WebAuthn-DZptt-PV.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BP2tkDHe.js → WorkflowEditor-Dy9223bY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CGVfeoTn.js → chat-DaxGeI9w.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BmjRolM1.js → colors-Cu2VEci3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BvJJkjZE.js → compact-item-CGolhyJq.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DyhlvRYs.js → createContext-DY7EFhkD.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DV2BNd59.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BoBMR89s.js → hasIn-Bpc-NoFN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSgbOGaP.js → index-1D4sfByw.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1t-r7yV.js → index-8h9y5S6X.js} +1 -1
- package/src/assets/web-panel/assets/{index-FKFT-QTk.js → index-BP9P6chP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIaGw7vl.js → index-BQ2z6Ky5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQJVedQ3.js → index-BRAgl2J_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BycpeGfj.js → index-BTvwiqJE.js} +1 -1
- package/src/assets/web-panel/assets/index-BZqtTmyG.js +1 -0
- package/src/assets/web-panel/assets/{index-D0YToIi_.js → index-BjfxHEmX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BT1SQ9nj.js → index-BlHq81Ow.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyS4I4L-.js → index-Bn5gM9Oy.js} +1 -1
- package/src/assets/web-panel/assets/{index-81tWFqfN.js → index-Bz83ngs0.js} +1 -1
- package/src/assets/web-panel/assets/{index-xZdOioVg.js → index-C-Hkl_2G.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbh-lCxq.js → index-C0_zeYnx.js} +1 -1
- package/src/assets/web-panel/assets/{index-xPSzUoWT.js → index-C2RpsAiO.js} +1 -1
- package/src/assets/web-panel/assets/{index-n-N19np-.js → index-CBSk_VrT.js} +1 -1
- package/src/assets/web-panel/assets/{index-VXVukhBA.js → index-CFAnEzRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtKdCXHW.js → index-CGqeHu_F.js} +1 -1
- package/src/assets/web-panel/assets/{index-Beh7jDbS.js → index-CJFYF8F9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvvNnWXe.js → index-CLNqZF55.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeeLHcMY.js → index-CaKXhpEu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuQrONgf.js → index-Ciw5-X1B.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDPMHKQi.js → index-D0GN5tdM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bm_MmdwP.js → index-D63ObMdQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIPZ6hbJ.js → index-DAov-rJR.js} +1 -1
- package/src/assets/web-panel/assets/{index-39VDXdn6.js → index-DElatOQ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwTgvhOL.js → index-DNX81oSR.js} +1 -1
- package/src/assets/web-panel/assets/index-DUpwdJt9.js +1 -0
- package/src/assets/web-panel/assets/{index-DgbWSwr5.js → index-DZ4Vm8dQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-93XwJd.js → index-DexYD87j.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0xn6hOr.js → index-DfKmAEtE.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZNIms1nA.js → index-DldaToUA.js} +1 -1
- package/src/assets/web-panel/assets/{index-wLAjVpmJ.js → index-DpRSzAFl.js} +1 -1
- package/src/assets/web-panel/assets/{index-Te0ruvY_.js → index-DxXkr-NS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Czsbrn75.js → index-RumxOD0S.js} +1 -1
- package/src/assets/web-panel/assets/{index-vF1pR00A.js → index-VBRPxZeE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqGNmoKy.js → index-eF9RV_4c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzDVBBcg.js → index-lfP8sdzB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y1b8i0NV.js → index-oJQgRCrR.js} +3 -3
- package/src/assets/web-panel/assets/{index-ByWpNjTj.js → index-rkm7dHwG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BLKSE8he.js → initDefaultProps-CkJZfCo8.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Bb59qqLK.js → motion-BerbusV1.js} +1 -1
- package/src/assets/web-panel/assets/{move-CB3pYCk6.js → move-DyRzKPD4.js} +1 -1
- package/src/assets/web-panel/assets/{omit-iImQWuU7.js → omit-CCdrTUAs.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DRP2Chqo.js → pickAttrs-mVDeZx2m.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BrlfD4tF.js → placementArrow-Bb_-Fs_o.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cqxkuh5H.js → responsiveObserve-C6TMj1R_.js} +1 -1
- package/src/assets/web-panel/assets/{slide-nxKEuLMj.js → slide-CdCNsy1J.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-30E47KSk.js → statusUtils-Ccxd1rFd.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dn2_-5bn.js → styleChecker-3IL-yw1V.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DkZ00X6F.js → useFlexGapSupport-CH8DjUHl.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-ByrwSCOr.js → useFs-Cn9nE2sp.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BDY6jtUD.js → usePersonalDataHub-BPyT0HO7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BL2q5BLv.js → vnode-Mfm7vy07.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BSkPKE42.js → zoom-CTpAiAE9.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/init.js +84 -2
- package/src/commands/session.js +36 -12
- package/src/index.js +10 -0
- package/src/lib/agent-session-export.js +124 -0
- package/src/lib/ide-context.js +333 -0
- package/src/lib/project-instructions.js +275 -0
- package/src/lib/project-inventory.js +355 -0
- package/src/lib/repl-bang-memorize.js +142 -0
- package/src/lib/repl-completer.js +154 -0
- package/src/lib/update-notice-refresh.mjs +10 -0
- package/src/lib/update-notice.js +154 -0
- package/src/repl/agent-repl.js +154 -0
- package/src/runtime/agent-core.js +195 -0
- package/src/runtime/headless-runner.js +19 -0
- package/src/runtime/headless-stream.js +19 -9
- package/src/runtime/system-prompt.js +21 -1
- package/src/assets/web-panel/assets/Logs-L5ZIW0Dz.js +0 -2
- package/src/assets/web-panel/assets/MobileProjects-BMP6eLp1.js +0 -1
- package/src/assets/web-panel/assets/Tasks-BcVDAxdi.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CetO0WH0.js +0 -1
- package/src/assets/web-panel/assets/index-BZVz-WfV.js +0 -1
- package/src/assets/web-panel/assets/index-D0-bvFy3.js +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup update notice — Claude-Code-style "new version available" line.
|
|
3
|
+
*
|
|
4
|
+
* Zero startup cost by design:
|
|
5
|
+
* - the CLI entry only does ONE sync cache read (~/.chainlesschain/
|
|
6
|
+
* update-check.json) and prints a single gray stderr line when the cached
|
|
7
|
+
* latest version is newer than the running one (TTY only, never pollutes
|
|
8
|
+
* piped/JSON output);
|
|
9
|
+
* - when the cache is stale (>24h) it spawns a DETACHED, unref'd child that
|
|
10
|
+
* refreshes the cache from the npm registry for the NEXT run — the current
|
|
11
|
+
* invocation never waits on the network. The cache's checkedAt is touched
|
|
12
|
+
* optimistically before spawning so concurrent invocations don't stampede.
|
|
13
|
+
*
|
|
14
|
+
* Disable with CC_UPDATE_NOTICE=0. `cc update` remains the full interactive
|
|
15
|
+
* checker (GitHub releases + assets); this is just the passive nudge.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fsDefault from "fs";
|
|
19
|
+
import pathDefault from "path";
|
|
20
|
+
import osDefault from "os";
|
|
21
|
+
import { spawn as spawnDefault } from "child_process";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
import chalk from "chalk";
|
|
24
|
+
import semver from "semver";
|
|
25
|
+
import { VERSION } from "../constants.js";
|
|
26
|
+
|
|
27
|
+
export const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
28
|
+
export const NPM_LATEST_URL = "https://registry.npmjs.org/chainlesschain/latest";
|
|
29
|
+
|
|
30
|
+
export const _deps = {
|
|
31
|
+
fs: fsDefault,
|
|
32
|
+
path: pathDefault,
|
|
33
|
+
os: osDefault,
|
|
34
|
+
spawn: spawnDefault,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function cachePath(deps = _deps) {
|
|
38
|
+
return deps.path.join(
|
|
39
|
+
deps.os.homedir() || "",
|
|
40
|
+
".chainlesschain",
|
|
41
|
+
"update-check.json",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readCache(deps) {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(deps.fs.readFileSync(cachePath(deps), "utf-8"));
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeCache(deps, cache) {
|
|
54
|
+
try {
|
|
55
|
+
const p = cachePath(deps);
|
|
56
|
+
deps.fs.mkdirSync(deps.path.dirname(p), { recursive: true });
|
|
57
|
+
deps.fs.writeFileSync(p, JSON.stringify(cache), "utf-8");
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Entry-point hook. Cheap and fail-open — never throws, never blocks.
|
|
66
|
+
*
|
|
67
|
+
* @returns {{ printed: boolean, spawned: boolean }}
|
|
68
|
+
*/
|
|
69
|
+
export function maybeNotifyUpdate(opts = {}) {
|
|
70
|
+
const deps = { ..._deps, ...(opts.deps || {}) };
|
|
71
|
+
const env = opts.env || process.env;
|
|
72
|
+
const now = opts.now ?? Date.now();
|
|
73
|
+
const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);
|
|
74
|
+
const current = opts.currentVersion || VERSION;
|
|
75
|
+
const print =
|
|
76
|
+
opts.print || ((line) => process.stderr.write(chalk.gray(line) + "\n"));
|
|
77
|
+
|
|
78
|
+
const out = { printed: false, spawned: false };
|
|
79
|
+
try {
|
|
80
|
+
if (env.CC_UPDATE_NOTICE === "0") return out;
|
|
81
|
+
|
|
82
|
+
const cache = readCache(deps);
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
isTTY &&
|
|
86
|
+
cache?.latest &&
|
|
87
|
+
semver.valid(cache.latest) &&
|
|
88
|
+
semver.valid(current) &&
|
|
89
|
+
semver.gt(cache.latest, current)
|
|
90
|
+
) {
|
|
91
|
+
print(
|
|
92
|
+
`Update available: chainlesschain ${current} → ${cache.latest} (npm i -g chainlesschain · CC_UPDATE_NOTICE=0 to hide)`,
|
|
93
|
+
);
|
|
94
|
+
out.printed = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const stale = !cache?.checkedAt || now - cache.checkedAt > CACHE_TTL_MS;
|
|
98
|
+
if (stale) {
|
|
99
|
+
// Optimistic touch first: parallel `cc` invocations inside the stale
|
|
100
|
+
// window won't each spawn a refresher.
|
|
101
|
+
writeCache(deps, { ...(cache || {}), checkedAt: now });
|
|
102
|
+
const refresher = deps.path.join(
|
|
103
|
+
deps.path.dirname(fileURLToPath(import.meta.url)),
|
|
104
|
+
"update-notice-refresh.mjs",
|
|
105
|
+
);
|
|
106
|
+
const child = deps.spawn(
|
|
107
|
+
process.execPath,
|
|
108
|
+
[refresher, cachePath(deps)],
|
|
109
|
+
{ detached: true, stdio: "ignore", windowsHide: true },
|
|
110
|
+
);
|
|
111
|
+
if (child && typeof child.unref === "function") child.unref();
|
|
112
|
+
out.spawned = true;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
/* fail-open: a broken cache or spawn must never affect the CLI */
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* One cache refresh (used by the detached child; exported for tests).
|
|
122
|
+
* npm registry only — light, unauthenticated, no GitHub rate-limit risk.
|
|
123
|
+
*/
|
|
124
|
+
export async function refreshCacheOnce({
|
|
125
|
+
cacheFile,
|
|
126
|
+
fetchImpl = fetch,
|
|
127
|
+
deps = _deps,
|
|
128
|
+
now = Date.now(),
|
|
129
|
+
timeoutMs = 10_000,
|
|
130
|
+
} = {}) {
|
|
131
|
+
const ctrl = new AbortController();
|
|
132
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
133
|
+
try {
|
|
134
|
+
const res = await fetchImpl(NPM_LATEST_URL, {
|
|
135
|
+
headers: { Accept: "application/json" },
|
|
136
|
+
signal: ctrl.signal,
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
139
|
+
const data = await res.json();
|
|
140
|
+
if (!data?.version) throw new Error("no version field");
|
|
141
|
+
const file = cacheFile || cachePath(deps);
|
|
142
|
+
deps.fs.mkdirSync(deps.path.dirname(file), { recursive: true });
|
|
143
|
+
deps.fs.writeFileSync(
|
|
144
|
+
file,
|
|
145
|
+
JSON.stringify({ checkedAt: now, latest: data.version }),
|
|
146
|
+
"utf-8",
|
|
147
|
+
);
|
|
148
|
+
return { ok: true, latest: data.version };
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return { ok: false, error: err.message };
|
|
151
|
+
} finally {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -732,11 +732,59 @@ export async function startAgentRepl(options = {}) {
|
|
|
732
732
|
return chalk.green("> ");
|
|
733
733
|
};
|
|
734
734
|
|
|
735
|
+
// `@` tab-completion (Claude-Code @-mention parity): filesystem paths +
|
|
736
|
+
// (when the IDE bridge is connected) the editor's open tabs ranked first.
|
|
737
|
+
const { makeAtCompleter } = await import("../lib/repl-completer.js");
|
|
738
|
+
const atCompleter = makeAtCompleter({
|
|
739
|
+
cwd: process.cwd(),
|
|
740
|
+
// Keep in sync with the rl.on("line") handlers + /help below.
|
|
741
|
+
slashCommands: [
|
|
742
|
+
"/auto",
|
|
743
|
+
"/clear",
|
|
744
|
+
"/compact",
|
|
745
|
+
"/context",
|
|
746
|
+
"/cowork",
|
|
747
|
+
"/exit",
|
|
748
|
+
"/help",
|
|
749
|
+
"/mcp",
|
|
750
|
+
"/model",
|
|
751
|
+
"/output-style",
|
|
752
|
+
"/plan",
|
|
753
|
+
"/profile",
|
|
754
|
+
"/provider",
|
|
755
|
+
"/quit",
|
|
756
|
+
"/reindex",
|
|
757
|
+
"/search",
|
|
758
|
+
"/session",
|
|
759
|
+
"/stats",
|
|
760
|
+
"/statusline",
|
|
761
|
+
"/sub-agents",
|
|
762
|
+
"/task",
|
|
763
|
+
],
|
|
764
|
+
getIdeOpenFiles: async () => {
|
|
765
|
+
const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
|
|
766
|
+
if (!exec || exec.kind !== "mcp" || !_adhocMcp?.mcpClient?.callTool) {
|
|
767
|
+
return [];
|
|
768
|
+
}
|
|
769
|
+
const { parseToolResultJson } = await import("../lib/ide-context.js");
|
|
770
|
+
const res = await _adhocMcp.mcpClient.callTool(
|
|
771
|
+
exec.serverName,
|
|
772
|
+
exec.toolName,
|
|
773
|
+
{},
|
|
774
|
+
);
|
|
775
|
+
const data = parseToolResultJson(res);
|
|
776
|
+
return Array.isArray(data?.editors)
|
|
777
|
+
? data.editors.map((e) => e?.file).filter(Boolean)
|
|
778
|
+
: [];
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
|
|
735
782
|
const rl = readline.createInterface({
|
|
736
783
|
input: process.stdin,
|
|
737
784
|
output: process.stdout,
|
|
738
785
|
prompt: getPrompt(),
|
|
739
786
|
terminal: true,
|
|
787
|
+
completer: atCompleter,
|
|
740
788
|
});
|
|
741
789
|
|
|
742
790
|
logger.log(chalk.bold("\nChainlessChain Agent"));
|
|
@@ -830,6 +878,45 @@ export async function startAgentRepl(options = {}) {
|
|
|
830
878
|
return;
|
|
831
879
|
}
|
|
832
880
|
|
|
881
|
+
// `!` bash passthrough (Claude-Code parity): run the command right here —
|
|
882
|
+
// no LLM round-trip — and fold the output into the conversation context.
|
|
883
|
+
if (trimmed.startsWith("!") && trimmed.slice(1).trim()) {
|
|
884
|
+
try {
|
|
885
|
+
const { runBangCommand } = await import("../lib/repl-bang-memorize.js");
|
|
886
|
+
const res = runBangCommand(trimmed, { cwd: process.cwd() });
|
|
887
|
+
logger.log(chalk.gray(`$ ${res.cmd}`));
|
|
888
|
+
if (res.stdout) process.stdout.write(res.stdout.endsWith("\n") ? res.stdout : `${res.stdout}\n`);
|
|
889
|
+
if (res.stderr) process.stderr.write(chalk.red(res.stderr.endsWith("\n") ? res.stderr : `${res.stderr}\n`));
|
|
890
|
+
if (res.error) logger.error(`shell error: ${res.error.message}`);
|
|
891
|
+
logger.log(chalk.gray(`(exit ${res.exitCode})`));
|
|
892
|
+
messages.push(res.contextMessage);
|
|
893
|
+
} catch (err) {
|
|
894
|
+
logger.error(`! command failed: ${err.message}`);
|
|
895
|
+
}
|
|
896
|
+
prompt();
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// `#` quick-memorize (Claude-Code parity): append a note to the project
|
|
901
|
+
// cc.md (auto-loaded next session) and keep it active in this one.
|
|
902
|
+
if (trimmed.startsWith("#") && trimmed.slice(1).trim()) {
|
|
903
|
+
try {
|
|
904
|
+
const { appendMemoryNote } = await import("../lib/repl-bang-memorize.js");
|
|
905
|
+
const res = appendMemoryNote(trimmed, { cwd: process.cwd() });
|
|
906
|
+
messages.push({
|
|
907
|
+
role: "system",
|
|
908
|
+
content: `<memory-note source="${res.target}">${res.note}</memory-note>`,
|
|
909
|
+
});
|
|
910
|
+
logger.log(
|
|
911
|
+
chalk.green(`✔ remembered in ${res.target}${res.created ? " (created)" : ""}`),
|
|
912
|
+
);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
logger.error(`# memorize failed: ${err.message}`);
|
|
915
|
+
}
|
|
916
|
+
prompt();
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
833
920
|
// Slash commands
|
|
834
921
|
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
835
922
|
logger.log(chalk.gray("\nGoodbye!"));
|
|
@@ -839,6 +926,12 @@ export async function startAgentRepl(options = {}) {
|
|
|
839
926
|
|
|
840
927
|
if (trimmed === "/help") {
|
|
841
928
|
logger.log(chalk.bold("\nCommands:"));
|
|
929
|
+
logger.log(
|
|
930
|
+
` ${chalk.cyan("! <cmd>")} Run a shell command directly (output joins context)`,
|
|
931
|
+
);
|
|
932
|
+
logger.log(
|
|
933
|
+
` ${chalk.cyan("# <note>")} Remember a note in the project cc.md`,
|
|
934
|
+
);
|
|
842
935
|
logger.log(` ${chalk.cyan("/exit")} Exit the agent`);
|
|
843
936
|
logger.log(
|
|
844
937
|
` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
|
|
@@ -848,6 +941,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
848
941
|
logger.log(
|
|
849
942
|
` ${chalk.cyan("/statusline")} Context-usage line on/off (/statusline [on|off])`,
|
|
850
943
|
);
|
|
944
|
+
logger.log(
|
|
945
|
+
` ${chalk.cyan("/context")} Live context-window usage by role`,
|
|
946
|
+
);
|
|
851
947
|
logger.log(
|
|
852
948
|
` ${chalk.cyan("/compact")} Smart compact (importance-based)`,
|
|
853
949
|
);
|
|
@@ -1086,6 +1182,52 @@ export async function startAgentRepl(options = {}) {
|
|
|
1086
1182
|
return;
|
|
1087
1183
|
}
|
|
1088
1184
|
|
|
1185
|
+
if (trimmed === "/context") {
|
|
1186
|
+
// Live-session twin of `cc context` (Claude-Code /context parity):
|
|
1187
|
+
// bucket the CURRENT in-memory conversation by role against the model
|
|
1188
|
+
// window. Reuses the same categorizer + estimator as the archived view.
|
|
1189
|
+
try {
|
|
1190
|
+
const { categorizeContext } = await import("../commands/context.js");
|
|
1191
|
+
const { estimateTokens } = await import(
|
|
1192
|
+
"../harness/prompt-compressor.js"
|
|
1193
|
+
);
|
|
1194
|
+
const { buckets, counts, total } = categorizeContext(
|
|
1195
|
+
messages,
|
|
1196
|
+
estimateTokens,
|
|
1197
|
+
);
|
|
1198
|
+
const window = getContextWindow(model, provider) || 0;
|
|
1199
|
+
logger.log(chalk.bold("\nContext usage (live session):"));
|
|
1200
|
+
const rows = [
|
|
1201
|
+
["system", buckets.system, counts.system],
|
|
1202
|
+
["user", buckets.user, counts.user],
|
|
1203
|
+
["assistant", buckets.assistant, counts.assistant],
|
|
1204
|
+
["tool", buckets.tool, counts.tool],
|
|
1205
|
+
["tool_calls", buckets.toolCalls, null],
|
|
1206
|
+
];
|
|
1207
|
+
for (const [label, tok, n] of rows) {
|
|
1208
|
+
if (!tok) continue;
|
|
1209
|
+
const share = total ? Math.round((tok / total) * 100) : 0;
|
|
1210
|
+
logger.log(
|
|
1211
|
+
` ${label.padEnd(11)}${String(tok).padStart(9)} tok ${String(share).padStart(3)}%${
|
|
1212
|
+
n != null ? chalk.gray(` (${n} msgs)`) : ""
|
|
1213
|
+
}`,
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
const pct = window ? Math.round((total / window) * 100) : null;
|
|
1217
|
+
logger.log(
|
|
1218
|
+
` ${"total".padEnd(11)}${String(total).padStart(9)} tok${
|
|
1219
|
+
window
|
|
1220
|
+
? ` ${pct}% of ${window} (${Math.max(0, window - total)} left)`
|
|
1221
|
+
: ""
|
|
1222
|
+
}`,
|
|
1223
|
+
);
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
logger.error(`/context failed: ${err.message}`);
|
|
1226
|
+
}
|
|
1227
|
+
prompt();
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1089
1231
|
if (trimmed === "/compact") {
|
|
1090
1232
|
if (_compressor && messages.length > 3) {
|
|
1091
1233
|
const { messages: compacted, stats } = await _compressor.compress(
|
|
@@ -1922,6 +2064,18 @@ export async function startAgentRepl(options = {}) {
|
|
|
1922
2064
|
}
|
|
1923
2065
|
}
|
|
1924
2066
|
|
|
2067
|
+
// IDE live context (Claude-Code parity): re-shared on every prompt while
|
|
2068
|
+
// an IDE bridge is connected — the user's selection moves between turns.
|
|
2069
|
+
// Ephemeral: persistence stores effectivePrompt, not this snapshot.
|
|
2070
|
+
// Best-effort; CC_IDE_CONTEXT=0 disables.
|
|
2071
|
+
try {
|
|
2072
|
+
const { buildIdePromptContext } = await import("../lib/ide-context.js");
|
|
2073
|
+
const ideCtx = await buildIdePromptContext(_adhocMcp);
|
|
2074
|
+
if (ideCtx) userContent += `\n\n${ideCtx}`;
|
|
2075
|
+
} catch (_err) {
|
|
2076
|
+
// optional polish — never fail the turn over it
|
|
2077
|
+
}
|
|
2078
|
+
|
|
1925
2079
|
// Add user message
|
|
1926
2080
|
messages.push({ role: "user", content: userContent });
|
|
1927
2081
|
|
|
@@ -230,6 +230,25 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
|
|
|
230
230
|
return { blocked: true, reason: outcome.reason, hook: outcome.hook };
|
|
231
231
|
}
|
|
232
232
|
if (outcome.decision === "ask") {
|
|
233
|
+
// File edits in an interactive session with an IDE bridge: route the ask
|
|
234
|
+
// through the editor's openDiff review (same machinery as settings ask —
|
|
235
|
+
// accepted means the IDE wrote the file, so the caller must skip
|
|
236
|
+
// execution; see tryIdeDiffApprovalForEdit).
|
|
237
|
+
const ide = await tryIdeDiffApprovalForEdit(name, args, context, cwd, {
|
|
238
|
+
rule: `hook:${outcome.hook}`,
|
|
239
|
+
source: "PreToolUse hook",
|
|
240
|
+
});
|
|
241
|
+
if (ide?.outcome === "accepted") {
|
|
242
|
+
return { blocked: false, ideApplied: ide.result };
|
|
243
|
+
}
|
|
244
|
+
if (ide?.outcome === "rejected") {
|
|
245
|
+
return {
|
|
246
|
+
blocked: true,
|
|
247
|
+
reason: ide.result.error,
|
|
248
|
+
hook: outcome.hook,
|
|
249
|
+
ideResult: ide.result,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
233
252
|
const confirm = context.permissionConfirm || context.shellConfirm || null;
|
|
234
253
|
const ok =
|
|
235
254
|
typeof confirm === "function"
|
|
@@ -603,6 +622,133 @@ export function buildSystemPrompt(cwd, opts = {}) {
|
|
|
603
622
|
|
|
604
623
|
// ─── Tool execution ──────────────────────────────────────────────────────
|
|
605
624
|
|
|
625
|
+
/** The file-mutating tools whose `ask` can be reviewed as an IDE diff. */
|
|
626
|
+
const IDE_DIFF_EDIT_TOOLS = new Set([
|
|
627
|
+
"write_file",
|
|
628
|
+
"edit_file",
|
|
629
|
+
"edit_file_hashed",
|
|
630
|
+
]);
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Compute the content an edit tool WOULD write, without writing it — the
|
|
634
|
+
* left/right sides for an IDE diff review. Mirrors the corresponding
|
|
635
|
+
* executeToolInner cases exactly (write_file / edit_file / edit_file_hashed,
|
|
636
|
+
* the latter via the same pure replaceByHash). Returns
|
|
637
|
+
* `{ filePath, newContent, originalText|null }` or null when the edit cannot
|
|
638
|
+
* be computed (missing file, anchor/old_string miss, bad args) — the caller
|
|
639
|
+
* then falls back to the normal confirmation path so the tool can produce its
|
|
640
|
+
* own diagnostics.
|
|
641
|
+
*/
|
|
642
|
+
export function computeProposedEdit(name, args = {}, cwd = process.cwd()) {
|
|
643
|
+
try {
|
|
644
|
+
if (!args.path) return null;
|
|
645
|
+
const filePath = path.resolve(cwd, args.path);
|
|
646
|
+
if (name === "write_file") {
|
|
647
|
+
if (typeof args.content !== "string") return null;
|
|
648
|
+
const originalText = fs.existsSync(filePath)
|
|
649
|
+
? fs.readFileSync(filePath, "utf8")
|
|
650
|
+
: "";
|
|
651
|
+
return { filePath, newContent: args.content, originalText };
|
|
652
|
+
}
|
|
653
|
+
if (name === "edit_file") {
|
|
654
|
+
if (!fs.existsSync(filePath)) return null;
|
|
655
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
656
|
+
if (
|
|
657
|
+
typeof args.old_string !== "string" ||
|
|
658
|
+
!content.includes(args.old_string)
|
|
659
|
+
) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
filePath,
|
|
664
|
+
newContent: content.replace(args.old_string, args.new_string),
|
|
665
|
+
originalText: content,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (name === "edit_file_hashed") {
|
|
669
|
+
if (!fs.existsSync(filePath)) return null;
|
|
670
|
+
if (!args.anchor_hash || typeof args.new_line !== "string") return null;
|
|
671
|
+
const original = fs.readFileSync(filePath, "utf8");
|
|
672
|
+
const result = replaceByHash(original, {
|
|
673
|
+
anchorHash: args.anchor_hash,
|
|
674
|
+
expectedLine: args.expected_line,
|
|
675
|
+
newLine: args.new_line,
|
|
676
|
+
});
|
|
677
|
+
if (!result.success) return null;
|
|
678
|
+
return { filePath, newContent: result.content, originalText: original };
|
|
679
|
+
}
|
|
680
|
+
} catch {
|
|
681
|
+
// unreadable file etc. → no proposal, normal path handles it
|
|
682
|
+
}
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Shared IDE-diff approval routing for an `ask` decision about a file edit
|
|
688
|
+
* (used by BOTH the settings-rules ask and the PreToolUse-hook ask). Returns
|
|
689
|
+
* { outcome:"accepted", result } — the IDE wrote the file; the caller MUST
|
|
690
|
+
* return `result` and skip execution
|
|
691
|
+
* { outcome:"rejected", result } — deny with `result`, file untouched
|
|
692
|
+
* null — not applicable (non-edit tool, headless,
|
|
693
|
+
* no IDE, disabled, no proposal, IDE died)
|
|
694
|
+
* → caller falls back to its own confirm.
|
|
695
|
+
*/
|
|
696
|
+
async function tryIdeDiffApprovalForEdit(
|
|
697
|
+
name,
|
|
698
|
+
args,
|
|
699
|
+
context,
|
|
700
|
+
cwd,
|
|
701
|
+
{ rule, source } = {},
|
|
702
|
+
) {
|
|
703
|
+
if (!IDE_DIFF_EDIT_TOOLS.has(name)) return null;
|
|
704
|
+
if (typeof context.permissionConfirm !== "function") return null; // interactive only
|
|
705
|
+
if (!context.mcpClient || !context.externalToolExecutors) return null;
|
|
706
|
+
try {
|
|
707
|
+
const { ideDiffApprovalEnabled, hasIdeOpenDiff, requestIdeDiffApproval } =
|
|
708
|
+
await import("../lib/ide-context.js");
|
|
709
|
+
const mcpLike = {
|
|
710
|
+
mcpClient: context.mcpClient,
|
|
711
|
+
externalToolExecutors: context.externalToolExecutors,
|
|
712
|
+
};
|
|
713
|
+
if (!ideDiffApprovalEnabled() || !hasIdeOpenDiff(mcpLike)) return null;
|
|
714
|
+
const proposal = computeProposedEdit(name, args, cwd);
|
|
715
|
+
if (!proposal) return null;
|
|
716
|
+
const verdict = await requestIdeDiffApproval(mcpLike, {
|
|
717
|
+
path: proposal.filePath,
|
|
718
|
+
modifiedText: proposal.newContent,
|
|
719
|
+
originalText: proposal.originalText,
|
|
720
|
+
title: `cc agent: ${name} ${path.basename(proposal.filePath)}`,
|
|
721
|
+
});
|
|
722
|
+
if (verdict?.outcome === "accepted") {
|
|
723
|
+
return {
|
|
724
|
+
outcome: "accepted",
|
|
725
|
+
result: {
|
|
726
|
+
success: true,
|
|
727
|
+
path: proposal.filePath,
|
|
728
|
+
appliedVia: "ide-diff",
|
|
729
|
+
...(verdict.finalText != null &&
|
|
730
|
+
verdict.finalText !== proposal.newContent
|
|
731
|
+
? { userEdited: true }
|
|
732
|
+
: {}),
|
|
733
|
+
policy: { decision: "allow", rule, via: "ide-diff" },
|
|
734
|
+
},
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
if (verdict?.outcome === "rejected") {
|
|
738
|
+
return {
|
|
739
|
+
outcome: "rejected",
|
|
740
|
+
result: {
|
|
741
|
+
error: `[Permission] "${name}" was rejected in the IDE diff review (${source}: ${rule}).`,
|
|
742
|
+
policy: { decision: "deny", rule, via: "ide-diff" },
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
} catch (_err) {
|
|
747
|
+
// diff-approval routing is best-effort — fall back to the normal confirm
|
|
748
|
+
}
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
|
|
606
752
|
/**
|
|
607
753
|
* Execute a single tool call with plan-mode filtering and hook pipeline.
|
|
608
754
|
*
|
|
@@ -719,6 +865,17 @@ export async function executeTool(name, args, context = {}) {
|
|
|
719
865
|
// 3 + 4. settings ask / allow (only reached when neither layer denied)
|
|
720
866
|
let ruleAllowed = false;
|
|
721
867
|
if (settingsVerdict.decision === "ask") {
|
|
868
|
+
// IDE-native diff approval (Claude-Code parity): for file edits in an
|
|
869
|
+
// interactive session with an IDE bridge connected, review the edit in
|
|
870
|
+
// the editor instead of a terminal y/N. Accepted = the IDE wrote the
|
|
871
|
+
// file → return the synthetic result and SKIP execution; rejected =
|
|
872
|
+
// deny; null = fall through to the normal confirm below. Shared with the
|
|
873
|
+
// PreToolUse-hook ask path (tryIdeDiffApprovalForEdit).
|
|
874
|
+
const ide = await tryIdeDiffApprovalForEdit(name, args, context, cwd, {
|
|
875
|
+
rule: settingsVerdict.rule,
|
|
876
|
+
source: "settings rule",
|
|
877
|
+
});
|
|
878
|
+
if (ide) return ide.result;
|
|
722
879
|
const confirm = context.permissionConfirm || context.shellConfirm || null;
|
|
723
880
|
const ok =
|
|
724
881
|
typeof confirm === "function"
|
|
@@ -792,7 +949,12 @@ export async function executeTool(name, args, context = {}) {
|
|
|
792
949
|
}
|
|
793
950
|
if (context.settingsHooks) {
|
|
794
951
|
const pre = await runSettingsPreToolUseHooks(name, args, context, cwd);
|
|
952
|
+
// A hook `ask` resolved by the IDE diff review: accepted → the IDE
|
|
953
|
+
// already wrote the file, return the synthetic result and skip the tool;
|
|
954
|
+
// rejected → the ide-diff deny shape (via:"ide-diff", not via:"hook").
|
|
955
|
+
if (pre.ideApplied) return pre.ideApplied;
|
|
795
956
|
if (pre.blocked) {
|
|
957
|
+
if (pre.ideResult) return pre.ideResult;
|
|
796
958
|
return {
|
|
797
959
|
error: `[Hook] PreToolUse blocked "${name}"${pre.reason ? ": " + pre.reason : ""}`,
|
|
798
960
|
policy: { decision: "block", via: "hook", hook: pre.hook || null },
|
|
@@ -930,6 +1092,39 @@ export async function executeTool(name, args, context = {}) {
|
|
|
930
1092
|
}
|
|
931
1093
|
}
|
|
932
1094
|
|
|
1095
|
+
// IDE post-edit diagnostics (Claude-Code parity): after a successful file
|
|
1096
|
+
// mutation with an IDE bridge connected, pull the editor's fresh
|
|
1097
|
+
// error/warning diagnostics for that file into the tool result so the model
|
|
1098
|
+
// can fix what it just broke in the same loop. Best-effort, bounded;
|
|
1099
|
+
// CC_IDE_CONTEXT=0 disables alongside prompt-time context.
|
|
1100
|
+
if (
|
|
1101
|
+
(name === "write_file" ||
|
|
1102
|
+
name === "edit_file" ||
|
|
1103
|
+
name === "edit_file_hashed") &&
|
|
1104
|
+
toolResult &&
|
|
1105
|
+
typeof toolResult === "object" &&
|
|
1106
|
+
!toolResult.error &&
|
|
1107
|
+
args?.path &&
|
|
1108
|
+
context.mcpClient &&
|
|
1109
|
+
context.externalToolExecutors
|
|
1110
|
+
) {
|
|
1111
|
+
try {
|
|
1112
|
+
const { collectIdeDiagnostics, formatIdeDiagnostics } =
|
|
1113
|
+
await import("../lib/ide-context.js");
|
|
1114
|
+
const diags = await collectIdeDiagnostics(
|
|
1115
|
+
{
|
|
1116
|
+
mcpClient: context.mcpClient,
|
|
1117
|
+
externalToolExecutors: context.externalToolExecutors,
|
|
1118
|
+
},
|
|
1119
|
+
path.resolve(cwd, args.path),
|
|
1120
|
+
);
|
|
1121
|
+
const feedback = formatIdeDiagnostics(diags);
|
|
1122
|
+
if (feedback) toolResult.ideDiagnostics = feedback;
|
|
1123
|
+
} catch (_err) {
|
|
1124
|
+
// diagnostics feedback is optional polish — never fail the tool
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
933
1128
|
return toolResult;
|
|
934
1129
|
}
|
|
935
1130
|
|
|
@@ -546,6 +546,25 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
546
546
|
}
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
// IDE live context (Claude-Code parity): when an IDE bridge is connected,
|
|
550
|
+
// share the editor's selection/active file/open tabs with this turn. Appended
|
|
551
|
+
// to the in-flight user message only — AFTER persistence — so a resumed
|
|
552
|
+
// session replays the prompt, not a stale editor snapshot. Best-effort with
|
|
553
|
+
// a short timeout; CC_IDE_CONTEXT=0 disables.
|
|
554
|
+
try {
|
|
555
|
+
const { buildIdePromptContext, appendTextToContent } =
|
|
556
|
+
await import("../lib/ide-context.js");
|
|
557
|
+
const ideCtx = await (deps.buildIdePromptContext || buildIdePromptContext)(
|
|
558
|
+
mcp,
|
|
559
|
+
);
|
|
560
|
+
if (ideCtx) {
|
|
561
|
+
const last = messages[messages.length - 1];
|
|
562
|
+
last.content = appendTextToContent(last.content, ideCtx);
|
|
563
|
+
}
|
|
564
|
+
} catch {
|
|
565
|
+
// IDE context is optional polish — never fail the run over it.
|
|
566
|
+
}
|
|
567
|
+
|
|
549
568
|
// --permission-prompt-tool: route every CONFIRM-tier approval to an MCP tool
|
|
550
569
|
// (loaded via --mcp-config) instead of headless fail-closed. Overrides the
|
|
551
570
|
// permission-mode confirmer on the gate for this session.
|
|
@@ -245,9 +245,8 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
245
245
|
// settings.json SessionStart hooks → inject session context once (observe-only).
|
|
246
246
|
if (settingsHooks) {
|
|
247
247
|
try {
|
|
248
|
-
const { runSessionStartHooks } =
|
|
249
|
-
"../lib/settings-hook-events.cjs"
|
|
250
|
-
);
|
|
248
|
+
const { runSessionStartHooks } =
|
|
249
|
+
await import("../lib/settings-hook-events.cjs");
|
|
251
250
|
const ctx = runSessionStartHooks(settingsHooks, {
|
|
252
251
|
source: "startup",
|
|
253
252
|
cwd,
|
|
@@ -428,9 +427,8 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
428
427
|
// settings.json UserPromptSubmit hooks. block → skip this turn; context → inject.
|
|
429
428
|
if (settingsHooks) {
|
|
430
429
|
try {
|
|
431
|
-
const { runUserPromptSubmitHooks } =
|
|
432
|
-
"../lib/settings-hook-events.cjs"
|
|
433
|
-
);
|
|
430
|
+
const { runUserPromptSubmitHooks } =
|
|
431
|
+
await import("../lib/settings-hook-events.cjs");
|
|
434
432
|
const ups = runUserPromptSubmitHooks(settingsHooks, {
|
|
435
433
|
prompt: userContent,
|
|
436
434
|
cwd,
|
|
@@ -454,6 +452,19 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
454
452
|
}
|
|
455
453
|
}
|
|
456
454
|
|
|
455
|
+
// IDE live context (Claude-Code parity): re-shared on every turn while an
|
|
456
|
+
// IDE bridge is connected — the user's selection moves between prompts.
|
|
457
|
+
// Best-effort; CC_IDE_CONTEXT=0 disables.
|
|
458
|
+
try {
|
|
459
|
+
const { buildIdePromptContext } = await import("../lib/ide-context.js");
|
|
460
|
+
const ideCtx = await (
|
|
461
|
+
deps.buildIdePromptContext || buildIdePromptContext
|
|
462
|
+
)(mcp);
|
|
463
|
+
if (ideCtx) userContent += `\n\n${ideCtx}`;
|
|
464
|
+
} catch {
|
|
465
|
+
// optional polish — never fail the turn over it
|
|
466
|
+
}
|
|
467
|
+
|
|
457
468
|
messages.push({ role: "user", content: userContent });
|
|
458
469
|
turns += 1;
|
|
459
470
|
|
|
@@ -508,9 +519,8 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
508
519
|
// settings.json SessionEnd hooks (observe-only) when stdin closes.
|
|
509
520
|
if (settingsHooks) {
|
|
510
521
|
try {
|
|
511
|
-
const { runObserveHooks } =
|
|
512
|
-
"../lib/settings-hook-events.cjs"
|
|
513
|
-
);
|
|
522
|
+
const { runObserveHooks } =
|
|
523
|
+
await import("../lib/settings-hook-events.cjs");
|
|
514
524
|
runObserveHooks(
|
|
515
525
|
settingsHooks,
|
|
516
526
|
"SessionEnd",
|