chainlesschain 0.162.37 → 0.162.39

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 (162) hide show
  1. package/package.json +3 -2
  2. package/src/assets/web-panel/assets/{AIOps-_oxz4VHy.js → AIOps-DCjoAX_u.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-uaeqFuDj.js → ActionButton-XHoOmsbP.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BPVV0OUf.js → Analytics--xaFkDnL.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-ppCYKm3I.js → AppLayout-CSa3FBn8.js} +4 -4
  6. package/src/assets/web-panel/assets/{Audit-DFAY6umk.js → Audit-ONWXiAwG.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-pAPBFDyP.js → Backup-CKOPNdgy.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BbBl0uT2.js → BaseInput-PNj4uVqg.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-Ct22JUnT.js → Chat-CZCulyXV.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DPlsLl22.js → ChatBubbleRenderer-CjuJpfpV.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-DEkCollc.js → Checkbox-jvy668lD.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Tor-de39.js → Codegen-DhUebOQD.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-ojNrLQU7.js → Col-BiBvHfdT.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-CLOGhqMF.js → Community-CmEdEti-.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-CYKNlSZ4.js → Compact-CtxpF4R5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-C5E6ABuA.js → Compliance-CvPTrTAJ.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CHeEsZ3W.js → Cowork-BMafGHjy.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-B4e1n2e7.js → Cron-mdg_4TR1.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-DbNV8P9R.js → Crosschain--dGxsUvn.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-C5_Tk3nC.js → DID-C9oKaCml.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-BhdV_c4N.js → Dashboard-CoGxKMvy.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-CEi5AMtM.js → Dropdown-CDDu3ZZ3.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DOhPiYng.js → EmailListRenderer-Dy7_r9Ag.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-fu4NRP3X.js → FamilyGuardDashboard-CNg6vImJ.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-B7BtIWKL.js → Federation-CT61bf3u.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-BmPWZVLP.js → FormItemContext-CSLRnXhg.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-hsOPNJq8.js → GenericCardRenderer-CZ4NE5N3.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-Bi_EFBUH.js → Git-DBuOma3L.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-emf2ubDK.js → Governance-BTU_SEef.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-B7KjKzkI.js → Inference-47SAmLC_.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-uAaBK0F3.js → KnowledgeGraph-DCrK5vP4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-utK7hNpj.js → Logs-BqiDxdav.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CzQe6n3z.js → Marketplace-CReUjsDt.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-CuAaJr51.js → McpTools-agZBV3p8.js} +6 -6
  35. package/src/assets/web-panel/assets/{Memory-CRuZZJ75.js → Memory-C_YvUtyS.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-Cp06wunh.js → MobileBridge-41fP1Tui.js} +3 -3
  37. package/src/assets/web-panel/assets/{MobileProjects-DJEdUwhr.js → MobileProjects-BkqLvGfL.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-8YY4dR7g.js → Mtc-JFJCXUnk.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-BmPJYHar.js → MtcAudit-BHNpPZC9.js} +6 -6
  40. package/src/assets/web-panel/assets/{Multisig-d-ydyVdq.js → Multisig-DuCRumiz.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-DA_ikw_n.js → NLProgramming-DK-g0fKY.js} +1 -1
  42. package/src/assets/web-panel/assets/Notes-BSMcjsPf.js +7 -0
  43. package/src/assets/web-panel/assets/{NotificationSettings-CzPZXEtK.js → NotificationSettings-9ouC118H.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-LG2nUO5y.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-DdDZ_Ap6.js → Organization-DSV7oRnR.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-BnMBkttv.js → Overflow-DVkkORc3.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-Es1050f-.js → P2P-BXXjkkQD.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-CKkRmyn9.js → PdhVaultBrowser-O5hNnLTP.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-zU9n9cAD.js → Permissions-D_s0H5Av.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BZi5Xwas.js → PersonalDataHub-CzMDrwUi.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-CRfeGiFc.js → Pipeline-i9krLVTL.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CQA_IgLA.js → Privacy-cMQcj9I8.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-C9hmEvoT.js → ProjectInit-Ca_l7avo.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-yXA72ws4.js → ProjectSettings-BkaIhd6b.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-BpWS-qam.js → Projects-Dy9yNmDg.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-Cxe55dRD.js → Providers-D0nzYiqz.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-Do0aUTQr.js → QuickAsk-Bzzr9d0f.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend--ysZHjyA.js → Recommend-C-UFbQnX.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-BOBU8JrH.js → Reputation-BKMIKO5F.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-C6X7bRKE.js → Row-Bs7htK1T.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-D8AwqlkQ.js → RssFeed-v6MdULUh.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-Bi3rCZD4.js → Search-DlRWYzvz.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DxUDVrtY.js → Security-DXWO37xX.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-BXXN7yC1.js → Services-C2tWA-O0.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-B3BR34tZ.js → Skeleton-Q8pIYY4a.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-BjYu8OQ1.js → Skills-D7XBlErj.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-DDkCtD8w.js → Sla-CiyMVPJ1.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-CGhYzP7V.js → SpeechSettings-CadCeeiR.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-CYNKVAHA.js → SyncSettings-DzNAUhQq.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-BjdHjZeb.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-CQuYFf2C.js → Templates-DfgEpUa4.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-DdzZh8vE.js → Tenant-C8ajkuYi.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-B9rHwQQx.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-DKOARnc_.js → TimelineRenderer-D1ZVNezX.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-D7QRNG8y.js → Tokens-CAkED4mx.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-BCsqLZl4.js → Trigger-CJSrm6X0.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-BarGUa6p.js → Trust-B-TeorSk.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-pHrg5a8E.js → UkeySign-Di7Ymofy.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-Dug3m1py.js → VideoEditing-DM1eYNZe.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-BfK3Z_Ez.js → Wallet-DvRWkbmR.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-CYRdl9td.js → WebAuthn-CeZ3Y622.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-DTW5AcqM.js → WorkflowEditor-Cq8c4h5j.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CCXz4j38.js → chat-7-WfML6Q.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-BJBOhAqa.js → colors-D6FgCmB-.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-E9M6BQcM.js → compact-item-ClYV25qi.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-Cg9CAws4.js → createContext-CDhtjdkV.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-O0FVFeZg.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-DhVtqv5L.js → hasIn-DZSH5LQd.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-_PNqQ5mE.js → index---azBCXl.js} +1 -1
  90. package/src/assets/web-panel/assets/index--ANIKvhL.js +1 -0
  91. package/src/assets/web-panel/assets/{index-kz1oXl1a.js → index-B13QnrnE.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-C-2dUIli.js → index-B4PMzmOx.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-Ca8BYV1g.js → index-B6VWGnwq.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BFZPRd0T.js → index-B78X5S22.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dwvewrul.js → index-BHeK8I5A.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-MdXEhfdJ.js → index-BJ7mrOaB.js} +1 -1
  97. package/src/assets/web-panel/assets/{index--7o5YdL6.js → index-BUOPjAUM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CFp-wdrQ.js → index-B_mMFQ4S.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DdhnGez0.js → index-Bj8hZiyL.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CUp_c8Le.js → index-BlBF_l8m.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-6-04M2Nx.js → index-BpzOUiSb.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-ByazO4Q9.js → index-BqOIoEo6.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-ComyTKz-.js → index-C7pQa2is.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Cm1m7BJh.js → index-C7sC56w8.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B4NBF4Sa.js → index-CDX4QU3k.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DSQazU6J.js → index-CKgS8E_X.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BAB0nGP7.js → index-CSjoWPxB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-B8bjEHrQ.js → index-CWOkL-8O.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CznfPnOx.js → index-CmU631Je.js} +3 -3
  110. package/src/assets/web-panel/assets/{index-D7DXdf7x.js → index-CrTmxbL8.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-wkt-o5q5.js → index-CxwfFZ1u.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BxSzyly9.js → index-D0YzTJJO.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-4N5lNXGP.js → index-DGJK8D0l.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DaFe1aqY.js → index-DGj1orXm.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Di5LBXcE.js → index-DL6GFJAd.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B_SMPD4L.js → index-DLizxxId.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CSiyjCYi.js → index-DPEYvNvq.js} +1 -1
  118. package/src/assets/web-panel/assets/index-DUfp4rnQ.js +1 -0
  119. package/src/assets/web-panel/assets/{index-D5yC2Ps8.js → index-DWRoh3_3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DDcJO27F.js → index-DZ4zuoCP.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CJ8nNT8h.js → index-DgaF1F0W.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ChsSljaN.js → index-Di9pFrHV.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CFarAlXj.js → index-DjG82V0v.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B111fZ21.js → index-Or_McYjX.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CVR_s-pT.js → index-rCs9VJJp.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CkTeBHI9.js → index-tU6pZ1TP.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CeRlLp3F.js → index-z-R0KaJS.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-iyBaePF-.js → initDefaultProps-CSdsIGy3.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-RWtj4rgu.js → motion-Do-AcZV4.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-CqPRVzpH.js → move-BmgOoMsi.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-DsvJze25.js → omit-D4Tm7-s9.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-B4tfZBhc.js → pickAttrs-CuWA8-lj.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-KvHUwXMA.js → placementArrow-BSbEF5op.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-DGdJ-b7W.js → responsiveObserve-GIMJwB_9.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Cd6ebRmw.js → slide-DlZxpIBe.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bg9GcIAn.js → statusUtils-BZ26LPlh.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-MQjKsG84.js → styleChecker-Yn_3FZ0l.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-C241WujP.js → useFlexGapSupport-O_LOE1AB.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-CMpy7RS4.js → useFs-VFMyQqtl.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BLHtapKb.js → usePersonalDataHub-B_hyrGB-.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-DmcTV67c.js → vnode-D4LttGy7.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-DHL8_0Y8.js → zoom-KnTK1fjj.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +31 -0
  145. package/src/commands/mcp.js +236 -6
  146. package/src/harness/mcp-client.js +70 -1
  147. package/src/lib/ide-context.js +271 -0
  148. package/src/lib/repl-completer.js +139 -0
  149. package/src/lib/settings-hooks.cjs +1 -0
  150. package/src/repl/agent-repl.js +88 -20
  151. package/src/repl/mcp-prompt.js +122 -0
  152. package/src/runtime/agent-core.js +156 -17
  153. package/src/runtime/headless-runner.js +53 -9
  154. package/src/runtime/headless-stream.js +19 -9
  155. package/src/runtime/mcp-config.js +118 -9
  156. package/src/assets/web-panel/assets/Notes-DIyF-fRe.js +0 -7
  157. package/src/assets/web-panel/assets/OrderTableRenderer-BiLtg-LY.js +0 -1
  158. package/src/assets/web-panel/assets/Tasks-DavmlJpd.js +0 -1
  159. package/src/assets/web-panel/assets/Terminal-D75WeG9d.js +0 -3
  160. package/src/assets/web-panel/assets/devWarning-BrsbTJUv.js +0 -1
  161. package/src/assets/web-panel/assets/index-DSTQDO-Y.js +0 -1
  162. package/src/assets/web-panel/assets/index-c2U6LV3Q.js +0 -1
@@ -0,0 +1,271 @@
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
+ /**
255
+ * Render pulled diagnostics as a compact feedback string for the tool result.
256
+ * Returns null when there is nothing to report.
257
+ */
258
+ export function formatIdeDiagnostics(diags, { cap = DIAG_CAP } = {}) {
259
+ if (!Array.isArray(diags) || diags.length === 0) return null;
260
+ const shown = diags.slice(0, cap).map((d) => {
261
+ const loc = Number.isInteger(d.line) ? `:${d.line + 1}` : "";
262
+ const src = d.source ? ` (${d.source})` : "";
263
+ return ` [${d.severity}] ${d.file || ""}${loc} ${d.message || ""}${src}`.trimEnd();
264
+ });
265
+ const more = diags.length - shown.length;
266
+ return (
267
+ `IDE diagnostics after this edit (${diags.length} problem${diags.length === 1 ? "" : "s"}):\n` +
268
+ shown.join("\n") +
269
+ (more > 0 ? `\n (+${more} more)` : "")
270
+ );
271
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * REPL `@` file-reference tab-completion (Claude-Code @-mention parity).
3
+ *
4
+ * Typing `@src/fo<TAB>` in the agent REPL completes file/dir paths the same
5
+ * way the @-token will later be expanded by file-ref-expander. Two candidate
6
+ * sources, merged:
7
+ * 1. the filesystem under cwd (dirs get a trailing `/` so TAB can descend)
8
+ * 2. the connected IDE's OPEN EDITOR TABS (when the IDE bridge is up) —
9
+ * the files you are working on rank first, before any fs listing.
10
+ *
11
+ * IDE tabs come from an injected async `getIdeOpenFiles()`; the completer
12
+ * caches the last list (TTL) and refreshes in the background, so completion
13
+ * stays synchronous-fast and a slow/dead IDE costs nothing (first TAB simply
14
+ * has no IDE entries yet).
15
+ */
16
+ import fs from "fs";
17
+ import path from "path";
18
+
19
+ /** Refresh the IDE open-editors list at most this often. */
20
+ const IDE_CACHE_TTL_MS = 5000;
21
+ /** Hard cap on candidates shown per TAB. */
22
+ const MAX_CANDIDATES = 30;
23
+
24
+ /**
25
+ * Find a trailing `@token` being typed at the end of the line. Returns
26
+ * `{ prefix }` (token without `@`, may be "") or null when the cursor is not
27
+ * on an @-token. An @ must start the line or follow whitespace, mirroring
28
+ * file-ref-expander's token rules.
29
+ */
30
+ export function extractAtPrefix(line) {
31
+ const m = /(^|\s)@([^\s@]*)$/.exec(line || "");
32
+ return m ? { prefix: m[2] } : null;
33
+ }
34
+
35
+ /** Normalize to forward slashes (what @refs use on every platform). */
36
+ function fwd(p) {
37
+ return String(p).replace(/\\/g, "/");
38
+ }
39
+
40
+ /**
41
+ * Filesystem candidates for an @-prefix, relative to cwd. Directories get a
42
+ * trailing `/`. Missing dirs / unreadable entries → empty (best-effort).
43
+ */
44
+ export function fileCandidates(prefix, { cwd = process.cwd(), deps } = {}) {
45
+ const readdir = deps?.readdir || ((d) => fs.readdirSync(d));
46
+ const isDir =
47
+ deps?.isDir ||
48
+ ((p) => {
49
+ try {
50
+ return fs.statSync(p).isDirectory();
51
+ } catch {
52
+ return false;
53
+ }
54
+ });
55
+ const norm = fwd(prefix);
56
+ const slash = norm.lastIndexOf("/");
57
+ const dirPart = slash >= 0 ? norm.slice(0, slash + 1) : "";
58
+ const basePart = slash >= 0 ? norm.slice(slash + 1) : norm;
59
+ const absDir = path.resolve(cwd, dirPart || ".");
60
+ let names;
61
+ try {
62
+ names = readdir(absDir);
63
+ } catch {
64
+ return [];
65
+ }
66
+ const out = [];
67
+ for (const name of names) {
68
+ if (basePart && !name.toLowerCase().startsWith(basePart.toLowerCase())) {
69
+ continue;
70
+ }
71
+ if (name === "node_modules" || name === ".git") continue;
72
+ const rel = dirPart + name;
73
+ out.push(isDir(path.join(absDir, name)) ? `${rel}/` : rel);
74
+ if (out.length >= MAX_CANDIDATES) break;
75
+ }
76
+ return out;
77
+ }
78
+
79
+ /**
80
+ * Build a readline completer. Returns `completer(line)` → `[hits, replaced]`
81
+ * per the readline contract; non-@ lines complete to nothing.
82
+ *
83
+ * @param {object} opts { cwd?, getIdeOpenFiles?: () => Promise<string[]>, deps? }
84
+ */
85
+ export function makeAtCompleter(opts = {}) {
86
+ const cwd = opts.cwd || process.cwd();
87
+ const getIde = opts.getIdeOpenFiles || null;
88
+ const now = opts.deps?.now || Date.now;
89
+ let ideFiles = [];
90
+ let ideFetchedAt = -Infinity; // "never" — the first @ must always fetch
91
+ let ideInFlight = false;
92
+
93
+ const refreshIde = () => {
94
+ if (!getIde || ideInFlight || now() - ideFetchedAt < IDE_CACHE_TTL_MS) {
95
+ return;
96
+ }
97
+ ideInFlight = true;
98
+ Promise.resolve()
99
+ .then(() => getIde())
100
+ .then((files) => {
101
+ ideFiles = Array.isArray(files)
102
+ ? files
103
+ .filter((f) => typeof f === "string" && f.length > 0)
104
+ .map((f) => {
105
+ const rel = path.relative(cwd, f);
106
+ // Keep workspace files relative (the natural @ref form);
107
+ // out-of-workspace files keep their absolute path.
108
+ return rel && !rel.startsWith("..") ? fwd(rel) : fwd(f);
109
+ })
110
+ : [];
111
+ ideFetchedAt = now();
112
+ })
113
+ .catch(() => {
114
+ ideFetchedAt = now(); // don't hammer a dead IDE
115
+ })
116
+ .finally(() => {
117
+ ideInFlight = false;
118
+ });
119
+ };
120
+
121
+ const completer = (line) => {
122
+ const at = extractAtPrefix(line);
123
+ if (!at) return [[], line];
124
+ refreshIde(); // async top-up for the NEXT tab; this one uses the cache
125
+ const norm = fwd(at.prefix).toLowerCase();
126
+ const fromIde = ideFiles.filter((f) => f.toLowerCase().startsWith(norm));
127
+ const fromFs = fileCandidates(at.prefix, { cwd, deps: opts.deps });
128
+ const merged = [...new Set([...fromIde, ...fromFs])].slice(
129
+ 0,
130
+ MAX_CANDIDATES,
131
+ );
132
+ // readline replaces `replaced` with the chosen hit — keep the `@`.
133
+ return [merged.map((m) => `@${m}`), `@${at.prefix}`];
134
+ };
135
+ // test seam: expose the cache refresher state
136
+ completer._refreshIde = refreshIde;
137
+ completer._ideState = () => ({ ideFiles, ideFetchedAt });
138
+ return completer;
139
+ }
@@ -37,6 +37,7 @@ const HOOK_EVENTS = Object.freeze([
37
37
  "PostToolUse",
38
38
  "UserPromptSubmit",
39
39
  "Stop",
40
+ "SubagentStop",
40
41
  "SessionStart",
41
42
  "SessionEnd",
42
43
  "PreCompact",
@@ -74,6 +74,7 @@ import { expandFileRefs } from "../runtime/file-ref-expander.js";
74
74
  import { composeSystemPrompt } from "../runtime/system-prompt.js";
75
75
  import { makeFallbackChatFn } from "../runtime/fallback-model.js";
76
76
  import { resolveSlashMacro } from "./slash-macro.js";
77
+ import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
77
78
 
78
79
  /**
79
80
  * Reference to the runtime DB for hook execution (set during startAgentRepl)
@@ -129,7 +130,8 @@ async function _persistAlwaysAllow(tool, args) {
129
130
  scope: "local",
130
131
  });
131
132
  if (!_permissionRules) _permissionRules = { allow: [], ask: [], deny: [] };
132
- if (!_permissionRules.allow.includes(rule)) _permissionRules.allow.push(rule);
133
+ if (!_permissionRules.allow.includes(rule))
134
+ _permissionRules.allow.push(rule);
133
135
  return { rule, file };
134
136
  } catch (err) {
135
137
  process.stderr.write(` always-allow persist failed: ${err.message}\n`);
@@ -504,9 +506,8 @@ export async function startAgentRepl(options = {}) {
504
506
  // settings.json SessionStart hooks → inject session context (observe-only).
505
507
  if (_settingsHooks) {
506
508
  try {
507
- const { runSessionStartHooks } = await import(
508
- "../lib/settings-hook-events.cjs"
509
- );
509
+ const { runSessionStartHooks } =
510
+ await import("../lib/settings-hook-events.cjs");
510
511
  const ctx = runSessionStartHooks(_settingsHooks, {
511
512
  source: "startup",
512
513
  cwd: process.cwd(),
@@ -731,11 +732,35 @@ export async function startAgentRepl(options = {}) {
731
732
  return chalk.green("> ");
732
733
  };
733
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
+ getIdeOpenFiles: async () => {
741
+ const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
742
+ if (!exec || exec.kind !== "mcp" || !_adhocMcp?.mcpClient?.callTool) {
743
+ return [];
744
+ }
745
+ const { parseToolResultJson } = await import("../lib/ide-context.js");
746
+ const res = await _adhocMcp.mcpClient.callTool(
747
+ exec.serverName,
748
+ exec.toolName,
749
+ {},
750
+ );
751
+ const data = parseToolResultJson(res);
752
+ return Array.isArray(data?.editors)
753
+ ? data.editors.map((e) => e?.file).filter(Boolean)
754
+ : [];
755
+ },
756
+ });
757
+
734
758
  const rl = readline.createInterface({
735
759
  input: process.stdin,
736
760
  output: process.stdout,
737
761
  prompt: getPrompt(),
738
762
  terminal: true,
763
+ completer: atCompleter,
739
764
  });
740
765
 
741
766
  logger.log(chalk.bold("\nChainlessChain Agent"));
@@ -805,7 +830,8 @@ export async function startAgentRepl(options = {}) {
805
830
  if (_statusLineEnabled && _renderStatus) {
806
831
  const line = _renderStatus();
807
832
  // Built-in line is dimmed; a custom command may carry its own ANSI.
808
- if (line) process.stdout.write((_customStatus ? line : chalk.dim(line)) + "\n");
833
+ if (line)
834
+ process.stdout.write((_customStatus ? line : chalk.dim(line)) + "\n");
809
835
  }
810
836
  rl.setPrompt(getPrompt());
811
837
  rl.prompt();
@@ -1024,15 +1050,16 @@ export async function startAgentRepl(options = {}) {
1024
1050
  logger.info("Status line: on");
1025
1051
  } else {
1026
1052
  // bare / "show" → report state + a one-off render
1027
- const line = _statusLineEnabled && _renderStatus ? _renderStatus() : null;
1053
+ const line =
1054
+ _statusLineEnabled && _renderStatus ? _renderStatus() : null;
1028
1055
  if (line) {
1029
- logger.info(
1030
- `Status line: ${_customStatus ? line : chalk.dim(line)}`,
1031
- );
1056
+ logger.info(`Status line: ${_customStatus ? line : chalk.dim(line)}`);
1032
1057
  } else {
1033
1058
  logger.info(
1034
1059
  `Status line: ${_statusLineEnabled ? "on (no content yet)" : "off"}` +
1035
- (_statusLineEnabled ? "" : ` — enable with ${chalk.cyan("/statusline on")}`),
1060
+ (_statusLineEnabled
1061
+ ? ""
1062
+ : ` — enable with ${chalk.cyan("/statusline on")}`),
1036
1063
  );
1037
1064
  }
1038
1065
  if (_customStatus) {
@@ -1046,9 +1073,8 @@ export async function startAgentRepl(options = {}) {
1046
1073
  if (trimmed === "/output-style" || trimmed.startsWith("/output-style ")) {
1047
1074
  const arg = trimmed.slice("/output-style".length).trim();
1048
1075
  try {
1049
- const { discoverOutputStyles, getOutputStyle } = await import(
1050
- "../lib/output-styles.js"
1051
- );
1076
+ const { discoverOutputStyles, getOutputStyle } =
1077
+ await import("../lib/output-styles.js");
1052
1078
  if (!arg) {
1053
1079
  logger.log(chalk.bold("Output styles:"));
1054
1080
  for (const s of discoverOutputStyles(process.cwd())) {
@@ -1801,15 +1827,47 @@ export async function startAgentRepl(options = {}) {
1801
1827
  return;
1802
1828
  }
1803
1829
 
1830
+ // `/mcp` — overview of connected MCP servers' resources + prompts.
1831
+ if (trimmed === "/mcp" || trimmed === "/mcp ") {
1832
+ const mcpClient = _adhocMcp?.mcpClient || _bundleMcpClient;
1833
+ logger.log(renderMcpSurface(mcpClient));
1834
+ prompt();
1835
+ return;
1836
+ }
1837
+
1804
1838
  // User-defined slash-command macros (.claude/commands/*.md), Claude-Code
1805
1839
  // parity. resolveSlashMacro maps a leading /name to a command macro and
1806
1840
  // expands its template; a non-match returns the line unchanged so a literal
1807
1841
  // prompt like "/etc/hosts" still reaches the LLM. Wire is unit-tested.
1808
1842
  let promptText = trimmed;
1843
+
1844
+ // MCP server-provided prompts (Claude-Code parity): `/mcp__<server>__<name>
1845
+ // [json-args]` fetches a rendered prompt template from the connected MCP
1846
+ // server and uses its text as this turn's input. Falls through unchanged
1847
+ // when the line isn't an MCP prompt command.
1848
+ if (promptText.startsWith("/mcp__")) {
1849
+ try {
1850
+ const expanded = await expandMcpPrompt(
1851
+ promptText,
1852
+ _adhocMcp?.mcpClient || _bundleMcpClient,
1853
+ );
1854
+ if (expanded != null) {
1855
+ promptText = expanded;
1856
+ logger.log(chalk.gray(`[mcp] prompt expanded`));
1857
+ }
1858
+ } catch (err) {
1859
+ logger.info(
1860
+ chalk.yellow(`[mcp] prompt expansion failed: ${err.message}`),
1861
+ );
1862
+ prompt();
1863
+ return;
1864
+ }
1865
+ }
1809
1866
  try {
1810
1867
  const macro = await resolveSlashMacro(trimmed, { cwd: process.cwd() });
1811
1868
  if (macro.matched) {
1812
- for (const w of macro.warnings) logger.info(chalk.yellow(`[@ref] ${w}`));
1869
+ for (const w of macro.warnings)
1870
+ logger.info(chalk.yellow(`[@ref] ${w}`));
1813
1871
  promptText = macro.promptText;
1814
1872
  logger.log(
1815
1873
  chalk.gray(`[/${macro.name}] macro expanded (${macro.scope})`),
@@ -1864,9 +1922,8 @@ export async function startAgentRepl(options = {}) {
1864
1922
  // is observe-only). block → abort the turn; context → inject before the turn.
1865
1923
  if (_settingsHooks) {
1866
1924
  try {
1867
- const { runUserPromptSubmitHooks } = await import(
1868
- "../lib/settings-hook-events.cjs"
1869
- );
1925
+ const { runUserPromptSubmitHooks } =
1926
+ await import("../lib/settings-hook-events.cjs");
1870
1927
  const ups = runUserPromptSubmitHooks(_settingsHooks, {
1871
1928
  prompt: userContent,
1872
1929
  cwd: process.cwd(),
@@ -1889,6 +1946,18 @@ export async function startAgentRepl(options = {}) {
1889
1946
  }
1890
1947
  }
1891
1948
 
1949
+ // IDE live context (Claude-Code parity): re-shared on every prompt while
1950
+ // an IDE bridge is connected — the user's selection moves between turns.
1951
+ // Ephemeral: persistence stores effectivePrompt, not this snapshot.
1952
+ // Best-effort; CC_IDE_CONTEXT=0 disables.
1953
+ try {
1954
+ const { buildIdePromptContext } = await import("../lib/ide-context.js");
1955
+ const ideCtx = await buildIdePromptContext(_adhocMcp);
1956
+ if (ideCtx) userContent += `\n\n${ideCtx}`;
1957
+ } catch (_err) {
1958
+ // optional polish — never fail the turn over it
1959
+ }
1960
+
1892
1961
  // Add user message
1893
1962
  messages.push({ role: "user", content: userContent });
1894
1963
 
@@ -2136,9 +2205,8 @@ export async function startAgentRepl(options = {}) {
2136
2205
  // settings.json SessionEnd hooks (observe-only) when the REPL exits.
2137
2206
  if (_settingsHooks) {
2138
2207
  try {
2139
- const { runObserveHooks } = await import(
2140
- "../lib/settings-hook-events.cjs"
2141
- );
2208
+ const { runObserveHooks } =
2209
+ await import("../lib/settings-hook-events.cjs");
2142
2210
  runObserveHooks(
2143
2211
  _settingsHooks,
2144
2212
  "SessionEnd",