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.
Files changed (166) hide show
  1. package/README.md +368 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-DV0Q9zKL.js → AIOps-CPmKv82o.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-C6vH8rhL.js → ActionButton-BNDYY7Qd.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BvPDc2ui.js → Analytics-BgCMCOsk.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CWnyqTqY.js → AppLayout-Dv4oJcqS.js} +5 -5
  7. package/src/assets/web-panel/assets/{Audit-BzenidV4.js → Audit-5iV3yrGa.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-CSl7bNwK.js → Backup-CHDhnbzF.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-DAY3iHIq.js → BaseInput-B6reFkra.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-Jyhm9fgk.js → Chat-DwS5YyE2.js} +6 -6
  11. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CwlAnVjy.js → ChatBubbleRenderer-CqXa87Hw.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-D4rwURAi.js → Checkbox-yiW0M4RE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-DYdjTEfC.js → Codegen-DoiVuD_g.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-DsVyZ_fS.js → Col-BVASLexk.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-CjCpl27Q.js → Community-D6KQ7JoU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-kt18dsjm.js → Compact-Bl9Uhb6v.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-BV5urquU.js → Compliance-MM31-dba.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-C4SovPWC.js → Cowork-PjU_1ieD.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-uuNs_xzA.js → Cron-DorNtPZL.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-DR5a65tR.js → Crosschain-Bm5ts2Kw.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-B1KTf2-5.js → DID-7Y3jlFdY.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-Dkj7XgED.js → Dashboard-1oE532bG.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-BhXCuJ19.js → Dropdown-hJlOPs0s.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-DG8365Iv.js → EmailListRenderer-BEqJxKaO.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BdHGPu39.js → FamilyGuardDashboard-BvCGwB6X.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-Dwvxl0zR.js → Federation-CsXI72e5.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-BVmhCVWU.js → FormItemContext-Dh9SMul-.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-DDPjvF2s.js → GenericCardRenderer-9edWzrtG.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-foK6WTSr.js → Git-ZYhNL8Xk.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-CfqMdu6Y.js → Governance-BwAdp8QA.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-BKrLO4GO.js → Inference-5C-M1XsH.js} +1 -1
  32. package/src/assets/web-panel/assets/{KnowledgeGraph-6o6Q-mmF.js → KnowledgeGraph-zFAi-zCi.js} +1 -1
  33. package/src/assets/web-panel/assets/Logs-BZsEdbgE.js +2 -0
  34. package/src/assets/web-panel/assets/{Marketplace-BWkfEocP.js → Marketplace-BP6gErRK.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-BPebQbWU.js → McpTools-CXVzoLrd.js} +4 -4
  36. package/src/assets/web-panel/assets/{Memory-C0Dq-X3C.js → Memory-BIpChb4-.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileBridge-DRBoutTY.js → MobileBridge-B4O7wDT8.js} +2 -2
  38. package/src/assets/web-panel/assets/MobileProjects-7VPMoHus.js +1 -0
  39. package/src/assets/web-panel/assets/{Mtc-Cj3QPM9p.js → Mtc-BTmEyTM5.js} +6 -6
  40. package/src/assets/web-panel/assets/{MtcAudit-rBQYbfQR.js → MtcAudit-CsbG9LlV.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig-Dbuy4OY4.js → Multisig-CL8yoGon.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-CMnt1se-.js → NLProgramming-C2cIlIp_.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-BX9tSCiF.js → Notes-7aBk_n_M.js} +4 -4
  44. package/src/assets/web-panel/assets/{NotificationSettings-BFeirVRq.js → NotificationSettings-BuhQk4rJ.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-ybiMlKQW.js → OrderTableRenderer-mqMFZu0x.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-kTfRxKqk.js → Organization-CAdq-170.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-CtuCAzwV.js → Overflow--Xn0E787.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-KfbciaP3.js → P2P-DYt3YAXI.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-bqEUFhgC.js → PdhVaultBrowser-Bgb_v8WN.js} +5 -5
  50. package/src/assets/web-panel/assets/{Permissions-BgMypz-z.js → Permissions-DoFlmoaW.js} +3 -3
  51. package/src/assets/web-panel/assets/{PersonalDataHub-C3zUE-1z.js → PersonalDataHub-C-FJB3a0.js} +2 -2
  52. package/src/assets/web-panel/assets/{Pipeline-iX-pYHpC.js → Pipeline-3bL2RzzL.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-B01uzeFM.js → Privacy-c4igYUCF.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-TsfbzJp7.js → ProjectInit-C0QS1UPR.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-iGvMp8sM.js → ProjectSettings-CkYC0xkE.js} +2 -2
  56. package/src/assets/web-panel/assets/{Projects-Be9k29iQ.js → Projects-Di17SYft.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-C9Pc8dqo.js → Providers-41NySsLt.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-DN_yFiVO.js → QuickAsk-DHq9pD7z.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-CvSNgl7H.js → Recommend-CLjgFPLv.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-S6BCz8xH.js → Reputation-EIrgErm3.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-CTRYCaqP.js → Row-GAvKzKH7.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-Cu8_P5ll.js → RssFeed-CYCNsVmD.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-rZ1Xza_U.js → Search-DWOE32k8.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-CF43IJHX.js → Security-Dgh8Jevn.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-BobNHzne.js → Services-BxdgP67N.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-DWJ2kfuI.js → Skeleton-D-xT4ZkA.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-AmEZgHYr.js → Skills-BKN4lfSa.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-DTS-fBiY.js → Sla--N1TudpS.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-DEr6MHRU.js → SpeechSettings-B0vfJpEh.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-CVs9alv_.js → SyncSettings-BuBAbPAh.js} +2 -2
  71. package/src/assets/web-panel/assets/Tasks-4XugjJ87.js +1 -0
  72. package/src/assets/web-panel/assets/{Templates-CTNjZRKA.js → Templates-DI2giLgc.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-DPbXg0Pg.js → Tenant-BiTWvm0g.js} +1 -1
  74. package/src/assets/web-panel/assets/{Terminal-DhKXcPw2.js → Terminal-vV6AWGDi.js} +2 -2
  75. package/src/assets/web-panel/assets/{TimelineRenderer-B0DMZOpk.js → TimelineRenderer-BmgzKdAp.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-RvWuBXgg.js → Tokens-Nvupdm6p.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-2O-BaTQG.js → Trigger-DRfR77WJ.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-6qY35L-C.js → Trust-De0Jal_6.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-DhV1wYtQ.js → UkeySign-Dzo4-VAM.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-DgqA5UZm.js → VideoEditing-hg2ytiJB.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-DJRYdUAK.js → Wallet--bU5-gRh.js} +4 -4
  82. package/src/assets/web-panel/assets/{WebAuthn-C2W-x0cg.js → WebAuthn-DZptt-PV.js} +5 -5
  83. package/src/assets/web-panel/assets/{WorkflowEditor-BP2tkDHe.js → WorkflowEditor-Dy9223bY.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-CGVfeoTn.js → chat-DaxGeI9w.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-BmjRolM1.js → colors-Cu2VEci3.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-BvJJkjZE.js → compact-item-CGolhyJq.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-DyhlvRYs.js → createContext-DY7EFhkD.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-DV2BNd59.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-BoBMR89s.js → hasIn-Bpc-NoFN.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-CSgbOGaP.js → index-1D4sfByw.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-C1t-r7yV.js → index-8h9y5S6X.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-FKFT-QTk.js → index-BP9P6chP.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CIaGw7vl.js → index-BQ2z6Ky5.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CQJVedQ3.js → index-BRAgl2J_.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BycpeGfj.js → index-BTvwiqJE.js} +1 -1
  96. package/src/assets/web-panel/assets/index-BZqtTmyG.js +1 -0
  97. package/src/assets/web-panel/assets/{index-D0YToIi_.js → index-BjfxHEmX.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BT1SQ9nj.js → index-BlHq81Ow.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DyS4I4L-.js → index-Bn5gM9Oy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-81tWFqfN.js → index-Bz83ngs0.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-xZdOioVg.js → index-C-Hkl_2G.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cbh-lCxq.js → index-C0_zeYnx.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-xPSzUoWT.js → index-C2RpsAiO.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-n-N19np-.js → index-CBSk_VrT.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-VXVukhBA.js → index-CFAnEzRW.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DtKdCXHW.js → index-CGqeHu_F.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Beh7jDbS.js → index-CJFYF8F9.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BvvNnWXe.js → index-CLNqZF55.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DeeLHcMY.js → index-CaKXhpEu.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BuQrONgf.js → index-Ciw5-X1B.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CDPMHKQi.js → index-D0GN5tdM.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Bm_MmdwP.js → index-D63ObMdQ.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DIPZ6hbJ.js → index-DAov-rJR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-39VDXdn6.js → index-DElatOQ0.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DwTgvhOL.js → index-DNX81oSR.js} +1 -1
  116. package/src/assets/web-panel/assets/index-DUpwdJt9.js +1 -0
  117. package/src/assets/web-panel/assets/{index-DgbWSwr5.js → index-DZ4Vm8dQ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-D-93XwJd.js → index-DexYD87j.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-C0xn6hOr.js → index-DfKmAEtE.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-ZNIms1nA.js → index-DldaToUA.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-wLAjVpmJ.js → index-DpRSzAFl.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Te0ruvY_.js → index-DxXkr-NS.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Czsbrn75.js → index-RumxOD0S.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-vF1pR00A.js → index-VBRPxZeE.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BqGNmoKy.js → index-eF9RV_4c.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CzDVBBcg.js → index-lfP8sdzB.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Y1b8i0NV.js → index-oJQgRCrR.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-ByWpNjTj.js → index-rkm7dHwG.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-BLKSE8he.js → initDefaultProps-CkJZfCo8.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-Bb59qqLK.js → motion-BerbusV1.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-CB3pYCk6.js → move-DyRzKPD4.js} +1 -1
  132. package/src/assets/web-panel/assets/{omit-iImQWuU7.js → omit-CCdrTUAs.js} +1 -1
  133. package/src/assets/web-panel/assets/{pickAttrs-DRP2Chqo.js → pickAttrs-mVDeZx2m.js} +1 -1
  134. package/src/assets/web-panel/assets/{placementArrow-BrlfD4tF.js → placementArrow-Bb_-Fs_o.js} +1 -1
  135. package/src/assets/web-panel/assets/{responsiveObserve-Cqxkuh5H.js → responsiveObserve-C6TMj1R_.js} +1 -1
  136. package/src/assets/web-panel/assets/{slide-nxKEuLMj.js → slide-CdCNsy1J.js} +1 -1
  137. package/src/assets/web-panel/assets/{statusUtils-30E47KSk.js → statusUtils-Ccxd1rFd.js} +1 -1
  138. package/src/assets/web-panel/assets/{styleChecker-Dn2_-5bn.js → styleChecker-3IL-yw1V.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFlexGapSupport-DkZ00X6F.js → useFlexGapSupport-CH8DjUHl.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFs-ByrwSCOr.js → useFs-Cn9nE2sp.js} +1 -1
  141. package/src/assets/web-panel/assets/{usePersonalDataHub-BDY6jtUD.js → usePersonalDataHub-BPyT0HO7.js} +1 -1
  142. package/src/assets/web-panel/assets/{vnode-BL2q5BLv.js → vnode-Mfm7vy07.js} +1 -1
  143. package/src/assets/web-panel/assets/{zoom-BSkPKE42.js → zoom-CTpAiAE9.js} +1 -1
  144. package/src/assets/web-panel/index.html +1 -1
  145. package/src/commands/init.js +84 -2
  146. package/src/commands/session.js +36 -12
  147. package/src/index.js +10 -0
  148. package/src/lib/agent-session-export.js +124 -0
  149. package/src/lib/ide-context.js +333 -0
  150. package/src/lib/project-instructions.js +275 -0
  151. package/src/lib/project-inventory.js +355 -0
  152. package/src/lib/repl-bang-memorize.js +142 -0
  153. package/src/lib/repl-completer.js +154 -0
  154. package/src/lib/update-notice-refresh.mjs +10 -0
  155. package/src/lib/update-notice.js +154 -0
  156. package/src/repl/agent-repl.js +154 -0
  157. package/src/runtime/agent-core.js +195 -0
  158. package/src/runtime/headless-runner.js +19 -0
  159. package/src/runtime/headless-stream.js +19 -9
  160. package/src/runtime/system-prompt.js +21 -1
  161. package/src/assets/web-panel/assets/Logs-L5ZIW0Dz.js +0 -2
  162. package/src/assets/web-panel/assets/MobileProjects-BMP6eLp1.js +0 -1
  163. package/src/assets/web-panel/assets/Tasks-BcVDAxdi.js +0 -1
  164. package/src/assets/web-panel/assets/devWarning-CetO0WH0.js +0 -1
  165. package/src/assets/web-panel/assets/index-BZVz-WfV.js +0 -1
  166. package/src/assets/web-panel/assets/index-D0-bvFy3.js +0 -1
@@ -0,0 +1,333 @@
1
+ /**
2
+ * IDE live prompt context (Claude-Code parity) — when an IDE bridge is
3
+ * connected (lib/ide-bridge.js → mcp-config.js `loadIdeMcp`, server `ide`),
4
+ * automatically share the editor's state at the moment a prompt is submitted:
5
+ * the active file, the open editor tabs, and the current selection. The agent
6
+ * no longer has to *choose* to call mcp__ide__getSelection — the context rides
7
+ * along with every user turn, exactly like Claude Code's at-submit selection
8
+ * sharing.
9
+ *
10
+ * The context is EPHEMERAL by design: entry points append it to the in-flight
11
+ * user content only, after session persistence, so a resumed session replays
12
+ * the user's words, not a stale editor snapshot.
13
+ *
14
+ * Everything here is best-effort and bounded: a missing/slow IDE server can
15
+ * never block or fail a turn (short timeout, all errors → null), and the
16
+ * injected block is capped. `CC_IDE_CONTEXT=0` disables the feature without
17
+ * disconnecting the IDE tools themselves.
18
+ */
19
+
20
+ /** Hard cap on the selected text we inline into the prompt. */
21
+ const SELECTION_TEXT_CAP = 2000;
22
+ /** At most this many open-editor entries are listed. */
23
+ const OPEN_EDITORS_CAP = 10;
24
+ /** Per-tool-call budget; the IDE answers from memory, so this is generous. */
25
+ const DEFAULT_TIMEOUT_MS = 1500;
26
+
27
+ /** Env kill-switch: CC_IDE_CONTEXT=0|false|off disables injection. */
28
+ export function ideContextEnabled(env = process.env) {
29
+ const v = String(env?.CC_IDE_CONTEXT ?? "").toLowerCase();
30
+ return !(v === "0" || v === "false" || v === "off");
31
+ }
32
+
33
+ /**
34
+ * Does this resolved MCP bundle expose the IDE bridge's selection tool?
35
+ * (`resolveAgentMcp` connects the bridge as server `ide`, so its tools land in
36
+ * `externalToolExecutors` as mcp__ide__*.)
37
+ */
38
+ export function hasIdeContextTools(mcp) {
39
+ return !!(
40
+ mcp?.mcpClient?.callTool &&
41
+ mcp.externalToolExecutors?.mcp__ide__getSelection?.kind === "mcp"
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Read an MCP tools/call result's first text block as JSON. The IDE bridge
47
+ * servers always wrap handler data as
48
+ * `{content:[{type:"text",text:JSON.stringify(data)}]}`. Returns null for
49
+ * isError results, non-text content, or unparsable text.
50
+ */
51
+ export function parseToolResultJson(result) {
52
+ if (!result || result.isError) return null;
53
+ const block = Array.isArray(result.content)
54
+ ? result.content.find((b) => b && b.type === "text")
55
+ : null;
56
+ if (!block || typeof block.text !== "string") return null;
57
+ try {
58
+ return JSON.parse(block.text);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /** Resolve to the promise's value, or null after `ms` / on rejection. */
65
+ function withTimeout(promise, ms) {
66
+ return new Promise((resolve) => {
67
+ const timer = setTimeout(() => resolve(null), ms);
68
+ promise.then(
69
+ (v) => {
70
+ clearTimeout(timer);
71
+ resolve(v);
72
+ },
73
+ () => {
74
+ clearTimeout(timer);
75
+ resolve(null);
76
+ },
77
+ );
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Query the connected IDE for its live state. Returns
83
+ * `{ selection, openEditors }` (either field may be null) or null when the
84
+ * feature is disabled, no IDE tools are connected, or nothing useful came
85
+ * back. Never throws.
86
+ *
87
+ * @param {object} mcp resolved bundle from resolveAgentMcp
88
+ * @param {object} opts { env?, timeoutMs? }
89
+ */
90
+ export async function collectIdeContext(mcp, opts = {}) {
91
+ if (!ideContextEnabled(opts.env || process.env)) return null;
92
+ if (!hasIdeContextTools(mcp)) return null;
93
+ const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
94
+ const executors = mcp.externalToolExecutors;
95
+ const call = (name) => {
96
+ const exec = executors[name];
97
+ if (!exec || exec.kind !== "mcp") return Promise.resolve(null);
98
+ let p;
99
+ try {
100
+ p = mcp.mcpClient.callTool(exec.serverName, exec.toolName, {});
101
+ } catch {
102
+ return Promise.resolve(null);
103
+ }
104
+ return withTimeout(p.then(parseToolResultJson), timeoutMs);
105
+ };
106
+ const [selection, editors] = await Promise.all([
107
+ call("mcp__ide__getSelection"),
108
+ call("mcp__ide__getOpenEditors"),
109
+ ]);
110
+ const openEditors = Array.isArray(editors?.editors) ? editors.editors : null;
111
+ if (!selection && !(openEditors && openEditors.length > 0)) return null;
112
+ return { selection: selection || null, openEditors };
113
+ }
114
+
115
+ /**
116
+ * Render collected IDE state as a compact tagged block for the user turn.
117
+ * Returns null when there is nothing worth saying.
118
+ */
119
+ export function formatIdeContext(ctx) {
120
+ if (!ctx) return null;
121
+ const lines = [];
122
+ const editors = Array.isArray(ctx.openEditors) ? ctx.openEditors : [];
123
+ const active = editors.find((e) => e && e.active);
124
+ if (active?.file) lines.push(`Active file: ${active.file}`);
125
+ if (editors.length > 0) {
126
+ const names = editors
127
+ .filter((e) => e && e.file)
128
+ .slice(0, OPEN_EDITORS_CAP)
129
+ .map((e) => (e.active ? `${e.file} (active)` : e.file));
130
+ const more = editors.length - names.length;
131
+ lines.push(
132
+ `Open editors: ${names.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`,
133
+ );
134
+ }
135
+ const sel = ctx.selection;
136
+ if (sel && typeof sel.text === "string" && sel.text.length > 0) {
137
+ const start = sel.selection?.start?.line;
138
+ const end = sel.selection?.end?.line;
139
+ const range =
140
+ Number.isInteger(start) && Number.isInteger(end)
141
+ ? `:${start + 1}-${end + 1}` // editor lines are 0-based
142
+ : "";
143
+ const text =
144
+ sel.text.length > SELECTION_TEXT_CAP
145
+ ? sel.text.slice(0, SELECTION_TEXT_CAP) + "\n...(selection truncated)"
146
+ : sel.text;
147
+ lines.push(`Selected text in ${sel.file || "the active editor"}${range}:`);
148
+ lines.push(text);
149
+ } else if (sel?.file && !active) {
150
+ lines.push(`Active file: ${sel.file}`);
151
+ }
152
+ if (lines.length === 0) return null;
153
+ return (
154
+ "<ide-context>\n" +
155
+ "Live editor state, shared automatically at prompt time (an IDE is " +
156
+ "connected). This reflects what the user is looking at NOW:\n" +
157
+ lines.join("\n") +
158
+ "\n</ide-context>"
159
+ );
160
+ }
161
+
162
+ /**
163
+ * Append extra text to user-turn content, preserving multimodal arrays
164
+ * (OpenAI-style content parts from --image runs).
165
+ */
166
+ export function appendTextToContent(content, extra) {
167
+ if (!extra) return content;
168
+ if (typeof content === "string") {
169
+ return content.length > 0 ? `${content}\n\n${extra}` : extra;
170
+ }
171
+ if (Array.isArray(content)) {
172
+ return [...content, { type: "text", text: extra }];
173
+ }
174
+ return content;
175
+ }
176
+
177
+ /**
178
+ * One-call convenience for entry points: collect + format. Returns the tagged
179
+ * block string or null. Never throws.
180
+ */
181
+ export async function buildIdePromptContext(mcp, opts = {}) {
182
+ try {
183
+ const ctx = await collectIdeContext(mcp, opts);
184
+ return ctx ? formatIdeContext(ctx) : null;
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+
190
+ // ─── Post-edit diagnostics feedback (Claude-Code parity) ────────────────────
191
+ //
192
+ // After the agent mutates a file, the connected IDE's language servers see the
193
+ // change and update their diagnostics. Pulling them right back into the tool
194
+ // result lets the model fix what it just broke in the SAME loop instead of
195
+ // discovering it turns later. The IDE needs a beat to re-lint, hence the
196
+ // settle delay (CC_IDE_DIAG_SETTLE_MS overrides; 0 skips the wait).
197
+
198
+ /** Give language servers this long to notice the disk change before asking. */
199
+ const DIAG_SETTLE_MS = 600;
200
+ /** At most this many diagnostics are surfaced per edit. */
201
+ const DIAG_CAP = 10;
202
+ /** Only these severities are worth interrupting the model for. */
203
+ const DIAG_SEVERITIES = new Set(["error", "warning"]);
204
+
205
+ /** Does this MCP surface expose the IDE bridge's diagnostics tool? */
206
+ export function hasIdeDiagnosticsTool(mcp) {
207
+ return !!(
208
+ mcp?.mcpClient?.callTool &&
209
+ mcp.externalToolExecutors?.mcp__ide__getDiagnostics?.kind === "mcp"
210
+ );
211
+ }
212
+
213
+ /**
214
+ * Pull the IDE's current error/warning diagnostics for one file. `mcp` accepts
215
+ * either a resolveAgentMcp bundle or agent-core's tool context (both carry
216
+ * `mcpClient` + `externalToolExecutors`). Returns a non-empty array or null.
217
+ * Never throws.
218
+ *
219
+ * @param {object} mcp { mcpClient, externalToolExecutors }
220
+ * @param {string} filePath absolute path of the just-edited file
221
+ * @param {object} opts { env?, settleMs?, timeoutMs? }
222
+ */
223
+ export async function collectIdeDiagnostics(mcp, filePath, opts = {}) {
224
+ const env = opts.env || process.env;
225
+ if (!ideContextEnabled(env)) return null;
226
+ if (!filePath || !hasIdeDiagnosticsTool(mcp)) return null;
227
+ const settle =
228
+ opts.settleMs ??
229
+ (Number.isFinite(Number(env.CC_IDE_DIAG_SETTLE_MS))
230
+ ? Number(env.CC_IDE_DIAG_SETTLE_MS)
231
+ : DIAG_SETTLE_MS);
232
+ if (settle > 0) await new Promise((r) => setTimeout(r, settle));
233
+ const exec = mcp.externalToolExecutors.mcp__ide__getDiagnostics;
234
+ let p;
235
+ try {
236
+ p = mcp.mcpClient.callTool(exec.serverName, exec.toolName, {
237
+ path: filePath,
238
+ });
239
+ } catch {
240
+ return null;
241
+ }
242
+ const data = await withTimeout(
243
+ p.then(parseToolResultJson),
244
+ opts.timeoutMs || DEFAULT_TIMEOUT_MS,
245
+ );
246
+ const all = Array.isArray(data?.diagnostics) ? data.diagnostics : null;
247
+ if (!all) return null;
248
+ const relevant = all.filter(
249
+ (d) => d && DIAG_SEVERITIES.has(String(d.severity).toLowerCase()),
250
+ );
251
+ return relevant.length > 0 ? relevant : null;
252
+ }
253
+
254
+ // ─── IDE-native diff approval (Claude-Code parity) ──────────────────────────
255
+ //
256
+ // When a permission `ask` fires for a file edit and an IDE bridge is up, the
257
+ // confirmation can be the editor's own openDiff review instead of a terminal
258
+ // y/N. The openDiff contract (extension P2): it BLOCKS until the user decides;
259
+ // on Accept the IDE itself writes the (possibly user-edited) right-hand text
260
+ // to the file — so an accepted review REPLACES the tool's own write, it does
261
+ // not precede it. The caller must skip normal execution on "accepted".
262
+
263
+ /** Env kill-switch for diff-approval routing: CC_IDE_DIFF_APPROVAL=0 disables. */
264
+ export function ideDiffApprovalEnabled(env = process.env) {
265
+ const v = String(env?.CC_IDE_DIFF_APPROVAL ?? "").toLowerCase();
266
+ return !(v === "0" || v === "false" || v === "off");
267
+ }
268
+
269
+ /** Does this MCP surface expose the IDE bridge's openDiff tool? */
270
+ export function hasIdeOpenDiff(mcp) {
271
+ return !!(
272
+ mcp?.mcpClient?.callTool &&
273
+ mcp.externalToolExecutors?.mcp__ide__openDiff?.kind === "mcp"
274
+ );
275
+ }
276
+
277
+ /**
278
+ * Run one blocking openDiff review in the connected IDE. Returns
279
+ * { outcome:"accepted", finalText|null } — the IDE wrote the file itself
280
+ * { outcome:"rejected" } — nothing was written
281
+ * null — IDE unavailable / transport
282
+ * error / malformed reply → the
283
+ * caller falls back to its normal
284
+ * confirmation path.
285
+ * Deliberately NO timeout: a review takes as long as the user takes (the MCP
286
+ * HTTP client has no request timeout; the extension holds the response open).
287
+ */
288
+ export async function requestIdeDiffApproval(mcp, req = {}) {
289
+ if (!hasIdeOpenDiff(mcp)) return null;
290
+ if (!req.path || typeof req.modifiedText !== "string") return null;
291
+ const exec = mcp.externalToolExecutors.mcp__ide__openDiff;
292
+ let result;
293
+ try {
294
+ result = await mcp.mcpClient.callTool(exec.serverName, exec.toolName, {
295
+ path: req.path,
296
+ modifiedText: req.modifiedText,
297
+ ...(typeof req.originalText === "string"
298
+ ? { originalText: req.originalText }
299
+ : {}),
300
+ ...(req.title ? { title: req.title } : {}),
301
+ });
302
+ } catch {
303
+ return null;
304
+ }
305
+ const data = parseToolResultJson(result);
306
+ if (data?.outcome === "accepted") {
307
+ return {
308
+ outcome: "accepted",
309
+ finalText: typeof data.finalText === "string" ? data.finalText : null,
310
+ };
311
+ }
312
+ if (data?.outcome === "rejected") return { outcome: "rejected" };
313
+ return null; // anything else is not a verdict — fail safe to fallback
314
+ }
315
+
316
+ /**
317
+ * Render pulled diagnostics as a compact feedback string for the tool result.
318
+ * Returns null when there is nothing to report.
319
+ */
320
+ export function formatIdeDiagnostics(diags, { cap = DIAG_CAP } = {}) {
321
+ if (!Array.isArray(diags) || diags.length === 0) return null;
322
+ const shown = diags.slice(0, cap).map((d) => {
323
+ const loc = Number.isInteger(d.line) ? `:${d.line + 1}` : "";
324
+ const src = d.source ? ` (${d.source})` : "";
325
+ return ` [${d.severity}] ${d.file || ""}${loc} ${d.message || ""}${src}`.trimEnd();
326
+ });
327
+ const more = diags.length - shown.length;
328
+ return (
329
+ `IDE diagnostics after this edit (${diags.length} problem${diags.length === 1 ? "" : "s"}):\n` +
330
+ shown.join("\n") +
331
+ (more > 0 ? `\n (+${more} more)` : "")
332
+ );
333
+ }
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Project-memory loader — file-based project instructions for `cc agent`
3
+ * (Claude-Code CLAUDE.md-hierarchy parity, with our own file name).
4
+ *
5
+ * The primary instruction file is **`cc.md`** (ChainlessChain branding);
6
+ * `CLAUDE.md` and `AGENTS.md` are accepted as compatibility fallbacks so any
7
+ * repo that already carries Claude-Code/agent memory works with zero setup.
8
+ *
9
+ * Discovery order (first existing name wins per location):
10
+ *
11
+ * 1. user scope : `~/.chainlesschain/cc.md`, else `~/.claude/CLAUDE.md`
12
+ * 2. project scope: per directory from <git-root> down to <cwd> —
13
+ * `cc.md` → `CLAUDE.md` → `AGENTS.md`
14
+ * (root-first, so deeper files refine shallower ones)
15
+ * 3. local scope : `cc.local.md` → `CLAUDE.local.md` next to each project
16
+ * file (gitignored personal notes)
17
+ *
18
+ * `@path` import lines inside an instruction file pull in the referenced file
19
+ * (resolved relative to the importing file; `~/` works too), recursively up to
20
+ * MAX_IMPORT_DEPTH with cycle protection. Tokens inside fenced code blocks and
21
+ * tokens that don't resolve to a real file (npm scopes like `@scope/pkg`,
22
+ * emails) are ignored silently.
23
+ *
24
+ * Loading is fail-open: any I/O error yields an empty block — composing the
25
+ * system prompt must never crash because of a bad memory file. All fs access
26
+ * goes through an injectable `deps` seam (project `_deps` philosophy) and all
27
+ * reads are explicit UTF-8 (encoding.md rule).
28
+ *
29
+ * Disable globally with `CC_PROJECT_MEMORY=0`, per-call with
30
+ * `projectMemory: false` on composeSystemPrompt.
31
+ */
32
+
33
+ import fsDefault from "fs";
34
+ import pathDefault from "path";
35
+ import osDefault from "os";
36
+
37
+ export const DEFAULT_MAX_FILE_BYTES = 48 * 1024; // per instruction/import file
38
+ export const DEFAULT_MAX_TOTAL_BYTES = 192 * 1024; // whole block budget
39
+ export const MAX_IMPORT_DEPTH = 5;
40
+
41
+ /** Per-directory project file names, first match wins. */
42
+ export const PROJECT_FILE_NAMES = ["cc.md", "CLAUDE.md", "AGENTS.md"];
43
+ /** Local (gitignored) companion names, first match wins. */
44
+ export const LOCAL_FILE_NAMES = ["cc.local.md", "CLAUDE.local.md"];
45
+
46
+ // Same boundary rule as file-ref-expander: `@` at start / after whitespace or
47
+ // an opening bracket-quote, so emails and decorative @ never match.
48
+ const IMPORT_TOKEN_RE = /(^|[\s("'`[{])@([^\s"'`)\]}]+)/g;
49
+
50
+ function resolveDeps(opts) {
51
+ return {
52
+ fs: opts.deps?.fs || fsDefault,
53
+ path: opts.deps?.path || pathDefault,
54
+ os: opts.deps?.os || osDefault,
55
+ };
56
+ }
57
+
58
+ function isFile(fs, p) {
59
+ try {
60
+ return fs.statSync(p).isFile();
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /** First existing candidate among `names` inside `dir`, or null. */
67
+ function firstExisting(fs, path, dir, names) {
68
+ for (const name of names) {
69
+ const p = path.join(dir, name);
70
+ if (isFile(fs, p)) return p;
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /** Walk up from cwd looking for a `.git` marker; null when not in a repo. */
76
+ export function findProjectRoot(cwd, opts = {}) {
77
+ const { fs, path } = resolveDeps(opts);
78
+ let dir = path.resolve(cwd);
79
+ for (;;) {
80
+ try {
81
+ if (fs.existsSync(path.join(dir, ".git"))) return dir;
82
+ } catch {
83
+ /* keep walking */
84
+ }
85
+ const parent = path.dirname(dir);
86
+ if (parent === dir) return null;
87
+ dir = parent;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Ordered instruction-file discovery (user → project root → … → cwd, with the
93
+ * local companion right after its project file). Only existing files are
94
+ * returned. Deduped by absolute path (covers cwd == home corner cases).
95
+ *
96
+ * @returns {Array<{path:string, scope:"user"|"project"|"local"|"rules"}>}
97
+ */
98
+ export function findInstructionFiles(opts = {}) {
99
+ const { fs, path, os } = resolveDeps(opts);
100
+ const cwd = path.resolve(opts.cwd || process.cwd());
101
+ const home = opts.home || os.homedir() || "";
102
+
103
+ const seen = new Set();
104
+ const out = [];
105
+ const push = (p, scope) => {
106
+ if (!p) return;
107
+ const abs = path.resolve(p);
108
+ if (seen.has(abs) || !isFile(fs, abs)) return;
109
+ seen.add(abs);
110
+ out.push({ path: abs, scope });
111
+ };
112
+
113
+ if (home) {
114
+ push(
115
+ firstExisting(fs, path, home, [
116
+ path.join(".chainlesschain", "cc.md"),
117
+ path.join(".claude", "CLAUDE.md"),
118
+ ]),
119
+ "user",
120
+ );
121
+ }
122
+
123
+ const root = findProjectRoot(cwd, opts) || cwd;
124
+ const chain = [];
125
+ let dir = cwd;
126
+ for (;;) {
127
+ chain.unshift(dir);
128
+ if (dir === root) break;
129
+ const parent = path.dirname(dir);
130
+ if (parent === dir) break;
131
+ dir = parent;
132
+ }
133
+ for (const d of chain) {
134
+ push(firstExisting(fs, path, d, PROJECT_FILE_NAMES), "project");
135
+ push(firstExisting(fs, path, d, LOCAL_FILE_NAMES), "local");
136
+ // Template-scaffolded project rules (`cc init -t` writes these) join the
137
+ // chain too, so scaffold-flow and memory-flow projects both feed the agent.
138
+ push(path.join(d, ".chainlesschain", "rules.md"), "rules");
139
+ }
140
+ return out;
141
+ }
142
+
143
+ /**
144
+ * Collect `@path` import tokens from instruction text, skipping fenced code
145
+ * blocks (``` / ~~~). Line-level scanning is good enough for memory files,
146
+ * which use imports on their own prose lines.
147
+ */
148
+ export function collectImportTokens(text) {
149
+ const found = [];
150
+ let inFence = false;
151
+ for (const line of String(text).split(/\r?\n/)) {
152
+ if (/^\s*(```|~~~)/.test(line)) {
153
+ inFence = !inFence;
154
+ continue;
155
+ }
156
+ if (inFence) continue;
157
+ IMPORT_TOKEN_RE.lastIndex = 0;
158
+ let m;
159
+ while ((m = IMPORT_TOKEN_RE.exec(line)) !== null) {
160
+ if (m[2]) found.push(m[2]);
161
+ }
162
+ }
163
+ return found;
164
+ }
165
+
166
+ function readCapped(fs, abs, maxFileBytes) {
167
+ const buf = fs.readFileSync(abs);
168
+ const truncated = buf.length > maxFileBytes;
169
+ const content = (truncated ? buf.slice(0, maxFileBytes) : buf).toString(
170
+ "utf-8",
171
+ );
172
+ return { content, bytes: buf.length, truncated };
173
+ }
174
+
175
+ /**
176
+ * Load the full instruction set: hierarchy files + recursive imports.
177
+ *
178
+ * @param {object} [opts] { cwd, home, deps, maxFileBytes, maxTotalBytes }
179
+ * @returns {{ files: Array<{path,scope,bytes,truncated,content}>, warnings: string[] }}
180
+ */
181
+ export function loadProjectInstructions(opts = {}) {
182
+ const { fs, path, os } = resolveDeps(opts);
183
+ const home = opts.home || os.homedir() || "";
184
+ const maxFileBytes = Number.isFinite(opts.maxFileBytes)
185
+ ? opts.maxFileBytes
186
+ : DEFAULT_MAX_FILE_BYTES;
187
+ const maxTotalBytes = Number.isFinite(opts.maxTotalBytes)
188
+ ? opts.maxTotalBytes
189
+ : DEFAULT_MAX_TOTAL_BYTES;
190
+
191
+ const roots = findInstructionFiles(opts);
192
+ const visited = new Set(roots.map((r) => r.path));
193
+ const out = [];
194
+ const warnings = [];
195
+ let total = 0;
196
+
197
+ // Queue of { abs, scope, depth } — imports inherit "import" scope.
198
+ const queue = roots.map((r) => ({ abs: r.path, scope: r.scope, depth: 0 }));
199
+
200
+ while (queue.length) {
201
+ const { abs, scope, depth } = queue.shift();
202
+ if (total >= maxTotalBytes) {
203
+ warnings.push(
204
+ `project-memory budget (${maxTotalBytes} bytes) exhausted — remaining files skipped`,
205
+ );
206
+ break;
207
+ }
208
+ let entry;
209
+ try {
210
+ entry = readCapped(fs, abs, maxFileBytes);
211
+ } catch (err) {
212
+ warnings.push(`${abs} — cannot read: ${err.message}`);
213
+ continue;
214
+ }
215
+ total += Math.min(entry.bytes, maxFileBytes);
216
+ out.push({ path: abs, scope, ...entry });
217
+
218
+ if (depth >= MAX_IMPORT_DEPTH) continue;
219
+ const baseDir = path.dirname(abs);
220
+ for (const raw of collectImportTokens(entry.content)) {
221
+ let target = raw;
222
+ if (target.startsWith("~/") || target === "~") {
223
+ if (!home) continue;
224
+ target = path.join(home, target.slice(1));
225
+ }
226
+ const resolved = path.resolve(baseDir, target);
227
+ if (visited.has(resolved) || !isFile(fs, resolved)) continue; // silent:
228
+ // non-files are decorative @tokens (npm scopes, emails), not imports.
229
+ visited.add(resolved);
230
+ queue.push({ abs: resolved, scope: "import", depth: depth + 1 });
231
+ }
232
+ }
233
+ return { files: out, warnings };
234
+ }
235
+
236
+ function escapeAttr(s) {
237
+ return String(s).replace(/"/g, "&quot;");
238
+ }
239
+
240
+ /** Render loaded instructions as a single system-prompt block ("" if none). */
241
+ export function renderProjectInstructionsBlock(loaded) {
242
+ const files = loaded?.files || [];
243
+ if (!files.length) return "";
244
+ const parts = [
245
+ '<project-instructions note="project memory auto-loaded from cc.md / CLAUDE.md / AGENTS.md; follow these as authoritative project conventions">',
246
+ ];
247
+ for (const f of files) {
248
+ const attrs =
249
+ `path="${escapeAttr(f.path)}" scope="${f.scope}"` +
250
+ (f.truncated ? ` truncated="true" total-bytes="${f.bytes}"` : "");
251
+ parts.push(`<file ${attrs}>`);
252
+ parts.push(f.content.trimEnd());
253
+ if (f.truncated) {
254
+ parts.push(`… [truncated — file is ${f.bytes} bytes]`);
255
+ }
256
+ parts.push(`</file>`);
257
+ }
258
+ parts.push("</project-instructions>");
259
+ return parts.join("\n");
260
+ }
261
+
262
+ /**
263
+ * One-call convenience for composeSystemPrompt: returns the rendered block or
264
+ * "" — and never throws (fail-open by design).
265
+ */
266
+ export function loadProjectInstructionsBlock(opts = {}) {
267
+ try {
268
+ const loaded = loadProjectInstructions(opts);
269
+ return renderProjectInstructionsBlock(loaded);
270
+ } catch {
271
+ return "";
272
+ }
273
+ }
274
+
275
+ export const _deps = { fs: fsDefault, path: pathDefault, os: osDefault };