chainlesschain 0.162.107 → 0.162.109

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 (176) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-B7Hz-sMT.js → AIOps-DOMx_gkg.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-tIqg_ue9.js → ActionButton-C3V6-KN7.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-B6DbVy_b.js → Analytics-DhcH00zB.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-Cm_hOXV8.js → AppLayout-DxaARGVc.js} +4 -4
  6. package/src/assets/web-panel/assets/{Audit-Crc94mKO.js → Audit-B5v_yPs_.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-DIZVZXve.js → Backup-y0trXy2c.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BZWHuJyu.js → BaseInput-BKmOrsWP.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-BOCsYs8k.js → Chat-YxvzoXfA.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BK6OX-J4.js → ChatBubbleRenderer-BT502wFg.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-_bsZN_Td.js → Checkbox-CWlobcH7.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Cu-YIxg5.js → Codegen-DyDnpB9x.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-BMM77pN6.js → Col-vSk5m8e6.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-B4uw3_sc.js → Community-ms1KDyit.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-RxrnL4Ql.js → Compact-CgOlMunA.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-CKEfGRFH.js → Compliance-BtmnlvkF.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-jS0AVJjh.js → Cowork-oEgcjamv.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-DNyVTsuS.js → Cron-C45dOmbZ.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-DRaSmfQP.js → Crosschain-COCW4N4S.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-B5UTlNum.js → DID-D6L3jdjB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-OVoPMtcA.js → Dashboard-CZO_nWR9.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-5OA2sbK9.js → Dropdown-BHogrwxg.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DUJPUE_n.js → EmailListRenderer-DVU5KA6Y.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BKFSlqTB.js → FamilyGuardDashboard-BpJuZRlS.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-BAhgrKK6.js → Federation-C8XUIu9h.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CZ8wh8ZQ.js → FormItemContext-Dkv__07L.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-Cnsg9gY4.js → GenericCardRenderer-PI4Hv6J7.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-EY6xhhH-.js → Git-DwoiOK7x.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-CKGWi2hF.js → Governance-CZMrVnKF.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-Bibz4dcl.js → Inference-DGMCR5fY.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-B1GhH4Rh.js → KnowledgeGraph-CHnyTP7N.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-Ce6yjdqa.js → Logs-aHwf1vTL.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-0dFzmO9J.js → Marketplace-DrrJWlYy.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-crttKADW.js → McpTools-FsvEQItl.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BjtmouRu.js → Memory-BaLEbZZU.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-Djm8NKL7.js → MobileBridge-Cbpe-UlH.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-WpHJ6m0I.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-UvDcNKPe.js → Mtc-B2-MRjt7.js} +2 -2
  39. package/src/assets/web-panel/assets/{MtcAudit-CCUvaVsw.js → MtcAudit-BPKg4478.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-CltVDZII.js → Multisig-DL-OMvg9.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-nVqmxQ1N.js → NLProgramming-BFbo7QJF.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-gyIOnFru.js → Notes-DxlawFPj.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-C7VByx8y.js → NotificationSettings-mXTgzIC1.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-DopvBK5x.js → OrderTableRenderer-BUcf5CYv.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-VxnADtjw.js → Organization-FeID8_Wf.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Du67ZgzE.js → Overflow-DTFS4Vaz.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-CXK6FzZK.js → P2P-BkylEhA8.js} +2 -2
  48. package/src/assets/web-panel/assets/PdhVaultBrowser-D8bxEEcj.js +7 -0
  49. package/src/assets/web-panel/assets/{Permissions-B23k4uT8.js → Permissions-BSQ09sRI.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-CjiMy8De.js → PersonalDataHub-Ba5iRuIi.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-Z1KmgqP5.js → Pipeline-DmL3Gpxg.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CmxIvTR1.js → Privacy-DAvd_DSh.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-Bts__Qz3.js → ProjectInit-AxBuFwBR.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-Bd_lhQgg.js → ProjectSettings-BZ40fAt5.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-DR-bmuRQ.js → Projects-BHNbHuBF.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-kaD-Hp-Y.js → Providers-CDkzXPon.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-qy3iXhzr.js → QuickAsk-CzKbOUkP.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-DONp9kRc.js → Recommend-D82aHoFt.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-DDyL2Aeq.js → Reputation-zpuCi7ft.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-D8YcGPf4.js → Row-BDq7p96-.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-DXUODZiL.js → RssFeed-pAo6EJPy.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-DEdHYWFg.js → Search-BXv5RRMM.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-H-o6s5e0.js → Security-Gn-HnKp5.js} +3 -3
  64. package/src/assets/web-panel/assets/{Services-UuIWNe9h.js → Services-BlfMJ4ZL.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-DYtxZPtS.js → Skeleton-Q8cKUNTJ.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-DDifBlN4.js → Skills-BjOLODy1.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-D1ZAkzmU.js → Sla-TbklgbfG.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-CKjGGj0q.js → SpeechSettings-DzKKyV6J.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-BGg86vP1.js → SyncSettings-Bq-76qhL.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-VxIkYJmg.js → Tasks-DWBUnqic.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-DFRL3OpQ.js → Templates-CS77urAR.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-CmYigIMz.js → Tenant-B1hx8tjz.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-CNX2SAIP.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-B75R5MPw.js → TimelineRenderer-CS_iGXXB.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-BQjcUYi2.js → Tokens-CPfe_Ndx.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-B87osPjr.js → Trigger-DARLZ6w5.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-BMNsPMGf.js → Trust-R_mlmd7H.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-Dh8aq9A5.js → UkeySign-CysEd--z.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BrW9UYwd.js → VideoEditing-eQ46o05K.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-rVZaz6JO.js → Wallet-C5788NxS.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-BaQ7s6Rr.js → WebAuthn-CK_CPM4k.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-DFo5Ff-W.js → WorkflowEditor-DhvdLkE6.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-BhGtd3vF.js → chat-CcKQhy1e.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-C_Hs-0Q_.js → colors-B4d3_MCE.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-Cxku8C7o.js → compact-item-DC-Xf92z.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-v6jUmogl.js → createContext-KBHDmwRY.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-Da3Hdd0q.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-BKhLdwbQ.js → hasIn-DEuYFeVL.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-DfD0hdcw.js → index-2Csz0FGD.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-C2rsPZun.js → index-8BCBAwoA.js} +3 -3
  91. package/src/assets/web-panel/assets/{index-Bgtv4PNS.js → index-B4RknAfS.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CARZxqzf.js → index-B5cOdkHy.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-B2IZIcKV.js → index-BB9f50Gt.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DMiUs7VJ.js → index-BCbc993k.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-GoxGMHvm.js → index-BJbBr6_l.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BDW_6hBP.js → index-BK8vM7es.js} +1 -1
  97. package/src/assets/web-panel/assets/index-BMeXnsEn.js +1 -0
  98. package/src/assets/web-panel/assets/{index-B3pkWuB9.js → index-BZjC0VTy.js} +1 -1
  99. package/src/assets/web-panel/assets/index-BdqPeWvV.js +1 -0
  100. package/src/assets/web-panel/assets/{index-CBL2DZwT.js → index-Bm39r2a4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DpO44b89.js → index-BwD3FKsC.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BolB2BAg.js → index-BxlEfskb.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DfjaeiC1.js → index-C4Ose5wC.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DaDs425R.js → index-CCnk_-ug.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BMCT14Fw.js → index-CTgs-ddr.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CVAVApRf.js → index-CdabmsmR.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CwKGHmaT.js → index-ChJ_B7b9.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BElj4Od7.js → index-Cl0As6V5.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-C2PkAyeB.js → index-CxtHBFqA.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Ha3xv10h.js → index-Cyb-NAiR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Mloutg2P.js → index-DE0-JQay.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CmhCbfio.js → index-DQMDCc8g.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DkFQKhAE.js → index-DQXNz0yD.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DgpKinbP.js → index-DSXxbnHU.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CZDVxpam.js → index-DTudiivg.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CfTpPsv6.js → index-DZN3yAE2.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BnYbnWuX.js → index-DhGYNT5Y.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dfnrfw8C.js → index-DkoDAyug.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CYz9sJYb.js → index-EnBI6ILp.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BLJ91vZT.js → index-JXP0mbdx.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-D8-PVIyz.js → index-N4xZOl6m.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ITysWrpU.js → index-Tcf8b4Dr.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-gjdcCPuc.js → index-VkeJvcgx.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D_vs8HEm.js → index-ZbZ9FKyc.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D7aePQoD.js → index-gOIec6-U.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-51MhKb2G.js → index-p5OuYAoC.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Hmuq7HiU.js → index-tW1RyKbL.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-3JYk3XrB.js → initDefaultProps-BSjGxzmn.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-DnWevrJa.js → motion-B5_Cszlm.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-9bW2GZpH.js → move-Zi4ZApRv.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-D8ydPKdj.js → omit-BNHsjZx4.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-l5iG1eRA.js → pickAttrs-GzJRqTuY.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-Bp1b6Ucy.js → placementArrow-CXfzbh2d.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-ChTuOCcn.js → responsiveObserve-lhENMtmd.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Ck7EHmbe.js → slide-BaUzfEHc.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Beih7s__.js → statusUtils-ZYc611Fn.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-Dddg8IJR.js → styleChecker-CaymUbb1.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-DU6cxKQF.js → useFlexGapSupport-9EAy_UPY.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-BISbGmgC.js → useFs-CUqinWaA.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-CuO7bBz3.js → usePersonalDataHub-eOXrcae-.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Cq3NWQzC.js → vnode-gBQTchDv.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BTh_EbRh.js → zoom-CCC5-LNi.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/__tests__/hub-query-events.test.js +134 -0
  145. package/src/commands/agent.js +6 -0
  146. package/src/commands/hub.js +39 -10
  147. package/src/commands/mcp.js +42 -10
  148. package/src/commands/pdh.js +191 -1
  149. package/src/constants.js +2 -2
  150. package/src/gateways/ws/video-protocol.js +7 -0
  151. package/src/harness/feature-flags.js +19 -6
  152. package/src/harness/prompt-compressor.js +1 -0
  153. package/src/lib/agent-core.js +2 -0
  154. package/src/lib/config-manager.js +42 -1
  155. package/src/lib/git-integration.js +1 -2
  156. package/src/lib/learning/learning-hooks.js +6 -0
  157. package/src/lib/llm-providers.js +2 -0
  158. package/src/lib/mcp-auth-target.js +53 -0
  159. package/src/lib/repl-completer.js +48 -9
  160. package/src/lib/shell-approval.js +7 -12
  161. package/src/lib/skill-loader.js +6 -2
  162. package/src/lib/suggest-name.js +91 -0
  163. package/src/lib/task-model-selector.js +5 -5
  164. package/src/repl/agent-repl.js +21 -1
  165. package/src/repl/slash-macro.js +7 -1
  166. package/src/runtime/agent-core.js +62 -3
  167. package/src/runtime/coding-agent-contract-shared.cjs +11 -1
  168. package/src/runtime/headless-runner.js +57 -4
  169. package/src/runtime/headless-stream.js +50 -2
  170. package/src/session/index.js +6 -2
  171. package/src/assets/web-panel/assets/MobileProjects-CuWSdENU.js +0 -1
  172. package/src/assets/web-panel/assets/PdhVaultBrowser-BH6PhZwi.js +0 -7
  173. package/src/assets/web-panel/assets/Terminal-DjjQrZtn.js +0 -3
  174. package/src/assets/web-panel/assets/devWarning-BnANFH7b.js +0 -1
  175. package/src/assets/web-panel/assets/index-CEJN-vG4.js +0 -1
  176. package/src/assets/web-panel/assets/index-CeJCYDPn.js +0 -1
@@ -0,0 +1,53 @@
1
+ /**
2
+ * mcp-auth-target — resolve a `cc mcp login|logout <target>` argument.
3
+ *
4
+ * Claude-Code 2.1.186 parity: `mcp login <name>` / `mcp logout <name>` accept
5
+ * the NAME of a configured MCP server, not only a raw URL. A `target` that
6
+ * already looks like a `scheme://…` URL is used verbatim (fast path — no DB
7
+ * needed); otherwise it is treated as a configured-server name and resolved to
8
+ * its stored url via the injected `lookupServer(name)` callback.
9
+ *
10
+ * Pure (no DB / IO of its own) so it is unit-testable; the caller supplies the
11
+ * `lookupServer` resolver (DB-backed in the command, a stub in tests).
12
+ */
13
+
14
+ /** A `scheme://` URL (http, https, …) — not a bare host or a server name. */
15
+ export function isUrlLike(s) {
16
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(String(s || "").trim());
17
+ }
18
+
19
+ /**
20
+ * Resolve a login/logout target to a concrete server URL.
21
+ *
22
+ * @param {string} target a URL or a configured MCP server name
23
+ * @param {(name: string) => ({ name?: string, url?: string|null,
24
+ * transport?: string }|null)} [lookupServer] name → server config (or null)
25
+ * @returns {{ url: string, name: string|null, source: "url"|"config" }}
26
+ * @throws {Error} when target is empty, names an unknown server, or names a
27
+ * server that has no http(s) url (stdio servers cannot do OAuth login)
28
+ */
29
+ export function resolveMcpAuthTarget(target, lookupServer) {
30
+ const t = typeof target === "string" ? target.trim() : "";
31
+ if (!t) {
32
+ throw new Error("a server URL or configured server name is required");
33
+ }
34
+ if (isUrlLike(t)) {
35
+ return { url: t, name: null, source: "url" };
36
+ }
37
+ const server = typeof lookupServer === "function" ? lookupServer(t) : null;
38
+ if (!server) {
39
+ throw new Error(
40
+ `no configured MCP server named "${t}" (and it is not a URL) — ` +
41
+ `run "cc mcp servers" to list, or pass the full https:// URL`,
42
+ );
43
+ }
44
+ const url = server.url || null;
45
+ if (!url || !isUrlLike(url)) {
46
+ throw new Error(
47
+ `configured server "${t}" has no http(s) URL (transport: ` +
48
+ `${server.transport || "stdio"}); OAuth login only applies to remote ` +
49
+ `http/sse MCP servers`,
50
+ );
51
+ }
52
+ return { url, name: server.name || t, source: "config" };
53
+ }
@@ -136,23 +136,62 @@ export function makeAtCompleter(opts = {}) {
136
136
  ? [...new Set(opts.slashCommands)].sort()
137
137
  : [];
138
138
 
139
+ // User/project custom slash commands (.claude/commands/*.md → `/git:commit`),
140
+ // surfaced in TAB completion the same as built-ins (Claude-Code parity — its
141
+ // slash autocomplete includes user-authored commands too). Pulled from an
142
+ // injected SYNC source so newly authored command files become completable
143
+ // without a restart and `/cd` follows whichever project's commands apply.
144
+ // Cached with the same short TTL as the IDE source so a burst of TABs never
145
+ // re-walks the filesystem.
146
+ const getDynamic =
147
+ typeof opts.getDynamicSlashCommands === "function"
148
+ ? opts.getDynamicSlashCommands
149
+ : null;
150
+ let dynCache = [];
151
+ let dynFetchedAt = -Infinity;
152
+ const dynamicSlashCommands = () => {
153
+ if (!getDynamic) return [];
154
+ if (now() - dynFetchedAt < IDE_CACHE_TTL_MS) return dynCache;
155
+ try {
156
+ const list = getDynamic();
157
+ dynCache = Array.isArray(list)
158
+ ? list.filter((c) => typeof c === "string" && c)
159
+ : [];
160
+ } catch {
161
+ dynCache = []; // a broken command source never breaks completion
162
+ }
163
+ dynFetchedAt = now();
164
+ return dynCache;
165
+ };
166
+ const mergedSlashCommands = () => {
167
+ const dyn = dynamicSlashCommands();
168
+ if (!dyn.length) return slashCommands; // common case: built-ins only
169
+ return [...new Set([...slashCommands, ...dyn])].sort();
170
+ };
171
+
139
172
  const completer = (line) => {
140
173
  // `/command` completion (Claude-Code parity): only while typing the
141
- // command token itself — once a space follows, args are the user's.
142
- const slash = /^\/([A-Za-z_-]*)$/.exec(line);
143
- if (slash && slashCommands.length) {
144
- const pref = `/${slash[1].toLowerCase()}`;
145
- const hits = slashCommands.filter((c) =>
146
- c.toLowerCase().startsWith(pref),
147
- );
148
- return [hits, line];
174
+ // command token itself — once a space follows, args are the user's. The
175
+ // token charset includes `:` / `.` / digits so namespaced custom commands
176
+ // (`/git:commit` from commands/git/commit.md) complete too.
177
+ const slash = /^\/([A-Za-z0-9:._-]*)$/.exec(line);
178
+ if (slash) {
179
+ const all = mergedSlashCommands();
180
+ if (all.length) {
181
+ const pref = `/${slash[1].toLowerCase()}`;
182
+ const hits = all.filter((c) => c.toLowerCase().startsWith(pref));
183
+ return [hits, line];
184
+ }
149
185
  }
150
186
  const at = extractAtPrefix(line);
151
187
  if (!at) return [[], line];
152
188
  refreshIde(); // async top-up for the NEXT tab; this one uses the cache
153
189
  const norm = fwd(at.prefix).toLowerCase();
154
190
  const fromIde = ideFiles.filter((f) => f.toLowerCase().startsWith(norm));
155
- const fromFs = fileCandidates(at.prefix, { cwd: getCwd(), deps: opts.deps });
191
+ const fromFs = fileCandidates(at.prefix, {
192
+ cwd: getCwd(),
193
+ deps: opts.deps,
194
+ });
156
195
  const merged = [...new Set([...fromIde, ...fromFs])].slice(
157
196
  0,
158
197
  MAX_CANDIDATES,
@@ -19,20 +19,20 @@
19
19
  * riskLevel, policy }` so callers don't have to juggle two decision types.
20
20
  */
21
21
 
22
- const sharedShellPolicy = require("../runtime/coding-agent-shell-policy.cjs");
23
- const {
24
- APPROVAL_RISK: RISK,
25
- APPROVAL_DECISION: DECISION,
26
- } = require("@chainlesschain/session-core");
22
+ import sharedShellPolicy from "../runtime/coding-agent-shell-policy.cjs";
23
+ import {
24
+ APPROVAL_RISK as RISK,
25
+ APPROVAL_DECISION as DECISION,
26
+ } from "@chainlesschain/session-core";
27
27
 
28
- const SHELL_TO_RISK = {
28
+ export const SHELL_TO_RISK = {
29
29
  allow: RISK.LOW,
30
30
  warn: RISK.MEDIUM,
31
31
  deny: RISK.HIGH,
32
32
  reroute: RISK.HIGH,
33
33
  };
34
34
 
35
- async function evaluateShellCommandWithApproval({
35
+ export async function evaluateShellCommandWithApproval({
36
36
  command,
37
37
  sessionId = null,
38
38
  approvalGate = null,
@@ -89,8 +89,3 @@ async function evaluateShellCommandWithApproval({
89
89
  policy: gateResult.policy,
90
90
  };
91
91
  }
92
-
93
- module.exports = {
94
- evaluateShellCommandWithApproval,
95
- SHELL_TO_RISK,
96
- };
@@ -129,8 +129,12 @@ export function parseSkillMd(content) {
129
129
  const key = trimmed.slice(0, colonIndex).trim();
130
130
  let value = trimmed.slice(colonIndex + 1).trim();
131
131
 
132
- // Convert kebab-case to camelCase
133
- const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
132
+ // Normalize frontmatter key case to camelCase. Claude-Code skills
133
+ // (auto-loaded from .claude/skills) may author keys in kebab-case,
134
+ // snake_case, or camelCase interchangeably (Claude Code 2.1.186), e.g.
135
+ // `display-name` / `display_name` / `displayName`. Treat all three the
136
+ // same so the field reads below (data.displayName, …) pick them up.
137
+ const camelKey = key.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
134
138
 
135
139
  if (value === "") {
136
140
  currentKey = camelKey;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * suggest-name — "did you mean?" suggestions for a mistyped identifier.
3
+ *
4
+ * Combines substring containment (a partial name) with bounded Levenshtein edit
5
+ * distance (single-char typos / transpositions) so `cc mcp remove cotnext7`
6
+ * suggests `context7`. Pure + dependency-free; reusable by any command that
7
+ * resolves a user-supplied name against a known set (MCP servers, skills, …).
8
+ *
9
+ * Claude-Code 2.1.186 parity: `mcp get/remove` typo suggestions.
10
+ */
11
+
12
+ /** Levenshtein edit distance between two strings (iterative two-row, O(n*m)). */
13
+ export function editDistance(a, b) {
14
+ a = String(a ?? "");
15
+ b = String(b ?? "");
16
+ if (a === b) return 0;
17
+ if (!a.length) return b.length;
18
+ if (!b.length) return a.length;
19
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
20
+ let curr = new Array(b.length + 1);
21
+ for (let i = 1; i <= a.length; i++) {
22
+ curr[0] = i;
23
+ for (let j = 1; j <= b.length; j++) {
24
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
25
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
26
+ }
27
+ const tmp = prev;
28
+ prev = curr;
29
+ curr = tmp;
30
+ }
31
+ return prev[b.length];
32
+ }
33
+
34
+ /**
35
+ * Rank candidates by closeness to `target` and return up to `limit` names.
36
+ * A candidate qualifies if it contains `target` (or vice-versa) as a
37
+ * case-insensitive substring, OR is within `maxDistance` edits (default scales
38
+ * with target length, capped at 3). Exact matches are excluded (not a typo).
39
+ * Output is ordered: substring matches first, then by edit distance, then
40
+ * alphabetically (stable).
41
+ *
42
+ * @param {string} target
43
+ * @param {string[]} candidates
44
+ * @param {{ limit?: number, maxDistance?: number }} [opts]
45
+ * @returns {string[]}
46
+ */
47
+ export function suggestNames(target, candidates, opts = {}) {
48
+ const t = String(target ?? "")
49
+ .trim()
50
+ .toLowerCase();
51
+ if (!t || !Array.isArray(candidates)) return [];
52
+ const limit = opts.limit ?? 3;
53
+ const maxDistance =
54
+ opts.maxDistance ?? Math.min(3, Math.max(1, Math.floor(t.length / 3)));
55
+ const scored = [];
56
+ for (const raw of candidates) {
57
+ const name = String(raw ?? "");
58
+ if (!name) continue;
59
+ const lc = name.toLowerCase();
60
+ if (lc === t) continue; // exact match isn't a "did you mean"
61
+ const contains = lc.includes(t) || t.includes(lc);
62
+ const dist = editDistance(t, lc);
63
+ if (contains || dist <= maxDistance) {
64
+ scored.push({ name, contains: contains ? 0 : 1, dist });
65
+ }
66
+ }
67
+ scored.sort((a, b) => {
68
+ if (a.contains !== b.contains) return a.contains - b.contains;
69
+ if (a.dist !== b.dist) return a.dist - b.dist;
70
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
71
+ });
72
+ return scored.slice(0, limit).map((s) => s.name);
73
+ }
74
+
75
+ /**
76
+ * Build a `<noun> "<target>" not found` message, appending a `— did you mean
77
+ * "x", "y"?` hint when there are close candidates. Returns just the base when
78
+ * there are none.
79
+ *
80
+ * @param {string} target
81
+ * @param {string[]} candidates
82
+ * @param {{ noun?: string, limit?: number, maxDistance?: number }} [opts]
83
+ * @returns {string}
84
+ */
85
+ export function notFoundWithSuggestion(target, candidates, opts = {}) {
86
+ const noun = opts.noun || "item";
87
+ const base = `${noun} "${target}" not found`;
88
+ const hints = suggestNames(target, candidates, opts);
89
+ if (!hints.length) return base;
90
+ return `${base} — did you mean ${hints.map((h) => `"${h}"`).join(", ")}?`;
91
+ }
@@ -24,7 +24,7 @@ export const TaskType = {
24
24
  */
25
25
  const TASK_MODEL_MAP = {
26
26
  [TaskType.CHAT]: {
27
- volcengine: "doubao-seed-1-6-251015",
27
+ volcengine: "doubao-seed-2-1-pro-260628",
28
28
  openai: "gpt-4o-mini",
29
29
  anthropic: "claude-sonnet-4-6",
30
30
  deepseek: "deepseek-chat",
@@ -36,7 +36,7 @@ const TASK_MODEL_MAP = {
36
36
  ollama: "qwen2.5:7b",
37
37
  },
38
38
  [TaskType.CODE]: {
39
- volcengine: "doubao-seed-1-6-251015",
39
+ volcengine: "doubao-seed-2-1-pro-260628",
40
40
  openai: "gpt-4o",
41
41
  anthropic: "claude-sonnet-4-6",
42
42
  deepseek: "deepseek-coder",
@@ -48,7 +48,7 @@ const TASK_MODEL_MAP = {
48
48
  ollama: "qwen2.5-coder:14b",
49
49
  },
50
50
  [TaskType.REASONING]: {
51
- volcengine: "doubao-seed-1-6-251015",
51
+ volcengine: "doubao-seed-2-1-pro-260628",
52
52
  openai: "o1",
53
53
  anthropic: "claude-opus-4-6",
54
54
  deepseek: "deepseek-reasoner",
@@ -72,7 +72,7 @@ const TASK_MODEL_MAP = {
72
72
  ollama: "qwen2:7b",
73
73
  },
74
74
  [TaskType.TRANSLATE]: {
75
- volcengine: "doubao-seed-1-6-251015",
75
+ volcengine: "doubao-seed-2-1-pro-260628",
76
76
  openai: "gpt-4o",
77
77
  anthropic: "claude-sonnet-4-6",
78
78
  deepseek: "deepseek-chat",
@@ -84,7 +84,7 @@ const TASK_MODEL_MAP = {
84
84
  ollama: "qwen2:7b",
85
85
  },
86
86
  [TaskType.CREATIVE]: {
87
- volcengine: "doubao-seed-1-6-251015",
87
+ volcengine: "doubao-seed-2-1-pro-260628",
88
88
  openai: "gpt-4o",
89
89
  anthropic: "claude-opus-4-6",
90
90
  deepseek: "deepseek-chat",
@@ -894,6 +894,7 @@ export async function startAgentRepl(options = {}) {
894
894
  // `@` tab-completion (Claude-Code @-mention parity): filesystem paths +
895
895
  // (when the IDE bridge is connected) the editor's open tabs ranked first.
896
896
  const { makeAtCompleter } = await import("../lib/repl-completer.js");
897
+ const { discoverCommands } = await import("../lib/slash-commands.js");
897
898
  const atCompleter = makeAtCompleter({
898
899
  // cwd left unset on purpose: the completer resolves process.cwd() lazily
899
900
  // so it follows `/cd` mid-session.
@@ -941,6 +942,17 @@ export async function startAgentRepl(options = {}) {
941
942
  "/ultrathink",
942
943
  "/vim",
943
944
  ],
945
+ // User/project custom commands (.claude/commands/*.md) join TAB completion
946
+ // alongside the built-ins above. Sync + best-effort; the completer
947
+ // TTL-caches the result so this filesystem walk runs at most once per few
948
+ // seconds, and process.cwd() makes it follow `/cd` mid-session.
949
+ getDynamicSlashCommands: () => {
950
+ try {
951
+ return discoverCommands(process.cwd()).map((cmd) => `/${cmd.name}`);
952
+ } catch {
953
+ return [];
954
+ }
955
+ },
944
956
  getIdeOpenFiles: async () => {
945
957
  const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
946
958
  if (!exec || exec.kind !== "mcp" || !_adhocMcp?.mcpClient?.callTool) {
@@ -1305,7 +1317,9 @@ export async function startAgentRepl(options = {}) {
1305
1317
  logger.log(
1306
1318
  ` ${chalk.cyan("… \\")} End a line with \\ to continue input onto the next line`,
1307
1319
  );
1308
- logger.log(` ${chalk.cyan("/exit")} Exit the agent`);
1320
+ logger.log(
1321
+ ` ${chalk.cyan("/exit")} Exit the agent (alias: /quit)`,
1322
+ );
1309
1323
  logger.log(
1310
1324
  ` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
1311
1325
  );
@@ -1326,6 +1340,9 @@ export async function startAgentRepl(options = {}) {
1326
1340
  logger.log(
1327
1341
  ` ${chalk.cyan("/theme")} Color theme (/theme <auto|dark|light|mono>; mono = no color)`,
1328
1342
  );
1343
+ logger.log(
1344
+ ` ${chalk.cyan("/output-style")} Response persona (/output-style <name|list>; explanatory/learning built-in)`,
1345
+ );
1329
1346
  logger.log(
1330
1347
  ` ${chalk.cyan("/config")} Show config; ${chalk.cyan("/config <key>")} read, ${chalk.cyan("/config <key>=<val>")} set, ${chalk.cyan("/config --help")} keys`,
1331
1348
  );
@@ -1411,6 +1428,9 @@ export async function startAgentRepl(options = {}) {
1411
1428
  logger.log(
1412
1429
  ` ${chalk.cyan("/ide")} IDE bridge status (connected editor, tools, or why not)`,
1413
1430
  );
1431
+ logger.log(
1432
+ ` ${chalk.cyan("/mcp")} MCP server status + tools (/mcp <name> for one server)`,
1433
+ );
1414
1434
  logger.log(chalk.bold("\nCapabilities:"));
1415
1435
  logger.log(" Read, write, and edit files");
1416
1436
  logger.log(" Run shell commands (git, npm, etc.)");
@@ -15,7 +15,11 @@
15
15
  * @param {string} input raw REPL line
16
16
  * @param {object} [opts] { cwd, deps:{ getCommand, expandCommand } }
17
17
  * @returns {Promise<{matched:boolean, promptText:string, warnings:string[],
18
- * name?:string, scope?:string}>}
18
+ * name?:string, scope?:string, model?:string|null,
19
+ * allowedTools?:string|null}>}
20
+ * `model` / `allowedTools` are the matched command's frontmatter (or null),
21
+ * so a caller running the macro as a whole turn can scope the run the same
22
+ * way `cc command run` does.
19
23
  */
20
24
  export async function resolveSlashMacro(input, opts = {}) {
21
25
  const cwd = opts.cwd || process.cwd();
@@ -41,5 +45,7 @@ export async function resolveSlashMacro(input, opts = {}) {
41
45
  warnings,
42
46
  name: macro.name,
43
47
  scope: macro.scope,
48
+ model: macro.model || null,
49
+ allowedTools: macro.allowedTools || null,
44
50
  };
45
51
  }
@@ -1546,17 +1546,43 @@ async function executeToolInner(
1546
1546
  }
1547
1547
  // Hashline mode: prefix each line with a 6-char content hash tag
1548
1548
  // so downstream edit_file_hashed calls can anchor by hash.
1549
- const rendered = args.hashed === true ? annotateLines(content) : content;
1549
+ let rendered = args.hashed === true ? annotateLines(content) : content;
1550
+
1551
+ // Line-range slice (Claude-Code Read offset/limit parity): `offset` is the
1552
+ // 1-based first line, `limit` the max line count — so a file larger than
1553
+ // the size cap can be paged through instead of being stuck at its head.
1554
+ // Coerces numeric strings the model may emit ("10" → 10).
1555
+ const toPos = (v) => {
1556
+ const n = typeof v === "number" ? v : parseInt(v, 10);
1557
+ return Number.isInteger(n) && n > 0 ? n : null;
1558
+ };
1559
+ const offset = toPos(args.offset);
1560
+ const limit = toPos(args.limit);
1561
+ let range = null;
1562
+ if (offset || limit) {
1563
+ const lines = rendered.split("\n");
1564
+ const start = offset ? offset - 1 : 0;
1565
+ const end = limit != null ? start + limit : lines.length;
1566
+ rendered = lines.slice(start, end).join("\n");
1567
+ range = {
1568
+ startLine: Math.min(start + 1, lines.length),
1569
+ endLine: Math.min(end, lines.length),
1570
+ totalLines: lines.length,
1571
+ };
1572
+ }
1573
+
1550
1574
  if (rendered.length > 50000) {
1551
1575
  return attachDescriptor({
1552
1576
  content: rendered.substring(0, 50000) + "\n...(truncated)",
1553
1577
  size: rendered.length,
1554
1578
  hashed: args.hashed === true,
1579
+ ...(range ? { range } : {}),
1555
1580
  });
1556
1581
  }
1557
1582
  return attachDescriptor({
1558
1583
  content: rendered,
1559
1584
  hashed: args.hashed === true,
1585
+ ...(range ? { range } : {}),
1560
1586
  });
1561
1587
  }
1562
1588
 
@@ -2633,6 +2659,36 @@ async function _executeRunCode(args, cwd) {
2633
2659
  */
2634
2660
  export const MAX_SUB_AGENT_DEPTH = 5;
2635
2661
 
2662
+ /**
2663
+ * Per-tool-result character cap fed back to the model. One giant tool output (a
2664
+ * huge file, a verbose command, an MCP blob) must not blow the context window —
2665
+ * but the model is TOLD when output is cut, instead of the old silent
2666
+ * `substring(0, 5000)` that sliced mid-content with no marker and undercut even
2667
+ * read_file's own 50k self-limit. Tools that self-limit (read_file, notebook
2668
+ * render) stay below this; it is the final safety net for the ones that don't
2669
+ * (run_shell, search_files, run_code, MCP). Override with CC_MAX_TOOL_RESULT_CHARS.
2670
+ */
2671
+ export const MAX_TOOL_RESULT_CHARS = (() => {
2672
+ const n = Number(process.env.CC_MAX_TOOL_RESULT_CHARS);
2673
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 50000;
2674
+ })();
2675
+
2676
+ /**
2677
+ * Cap a serialized tool result to `max` chars, appending a visible truncation
2678
+ * marker (with the original length + how to get the rest) when it overflows —
2679
+ * so the model never silently receives a mid-content slice. Pure; exported for
2680
+ * tests.
2681
+ */
2682
+ export function capToolResultString(serialized, max = MAX_TOOL_RESULT_CHARS) {
2683
+ const s = String(serialized ?? "");
2684
+ if (s.length <= max) return s;
2685
+ return (
2686
+ s.slice(0, max) +
2687
+ `\n…[tool output truncated: showing the first ${max} of ${s.length} chars` +
2688
+ ` — narrow the request (read a line range, grep, or paginate) to see the rest]`
2689
+ );
2690
+ }
2691
+
2636
2692
  /**
2637
2693
  * Execute a spawn_sub_agent tool call.
2638
2694
  * Creates an isolated SubAgentContext, runs it, and returns only the summary.
@@ -3112,7 +3168,7 @@ export async function chatWithTools(rawMessages, options) {
3112
3168
  dashscope: "qwen-turbo",
3113
3169
  mistral: "mistral-large-latest",
3114
3170
  gemini: "gemini-2.0-flash",
3115
- volcengine: "doubao-seed-1-6-251015",
3171
+ volcengine: "doubao-seed-2-1-pro-260628",
3116
3172
  };
3117
3173
 
3118
3174
  // Real token streaming (--include-partial-messages) for every OpenAI-compatible
@@ -4586,7 +4642,10 @@ export async function* agentLoop(messages, options) {
4586
4642
 
4587
4643
  // Append budget warning to tool result so the LLM sees it
4588
4644
  const warningMsg = budget.toWarningMessage();
4589
- const resultStr = JSON.stringify(toolResult).substring(0, 5000);
4645
+ // Cap an individual tool result so one giant output can't blow the
4646
+ // context — but tell the model when we cut it (no more silent
4647
+ // mid-content slice). See MAX_TOOL_RESULT_CHARS / capToolResultString.
4648
+ const resultStr = capToolResultString(JSON.stringify(toolResult));
4590
4649
  const toolContent = warningMsg
4591
4650
  ? `${resultStr}\n\n${warningMsg}`
4592
4651
  : resultStr;
@@ -28,11 +28,21 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
28
28
  kind: "filesystem",
29
29
  tier: "mvp",
30
30
  description:
31
- "Read a file's content. Jupyter notebooks (.ipynb) are rendered as a compact cell listing (index/id/type/source, outputs hidden) for use with notebook_edit — pass raw:true for the underlying JSON.",
31
+ "Read a file's content. For a large file, page through it with offset+limit (line range) instead of re-reading the head. Jupyter notebooks (.ipynb) are rendered as a compact cell listing (index/id/type/source, outputs hidden) for use with notebook_edit — pass raw:true for the underlying JSON.",
32
32
  inputSchema: {
33
33
  type: "object",
34
34
  properties: {
35
35
  path: { type: "string", description: "File path to read" },
36
+ offset: {
37
+ type: "integer",
38
+ description:
39
+ "1-based line number to start reading from (omit to read from the top). Use with limit to page a large file.",
40
+ },
41
+ limit: {
42
+ type: "integer",
43
+ description:
44
+ "Maximum number of lines to return starting at offset (omit to read to end, subject to the size cap).",
45
+ },
36
46
  hashed: {
37
47
  type: "boolean",
38
48
  description:
@@ -250,7 +250,10 @@ export async function runAgentHeadless(options = {}, deps = {}) {
250
250
  );
251
251
  }
252
252
 
253
- const model = options.model || "qwen2.5:7b";
253
+ // `let` (not const): a custom-command macro's `model:` frontmatter may
254
+ // override it below (when the user passed no explicit --model), mirroring
255
+ // `cc command run`.
256
+ let model = options.model || "qwen2.5:7b";
254
257
  const provider = options.provider || "ollama";
255
258
  const baseUrl = options.baseUrl || "http://localhost:11434";
256
259
  const apiKey = options.apiKey || null;
@@ -369,12 +372,60 @@ export async function runAgentHeadless(options = {}, deps = {}) {
369
372
  return { exitCode: isError ? 1 : 0, result: text, isError };
370
373
  }
371
374
 
375
+ // ── Custom slash-command macros (Claude-Code parity: a .claude/commands/*
376
+ // command runs in `-p` mode too, not just the interactive REPL). A leading
377
+ // `/name …` that resolves to a user/project command is expanded into its
378
+ // prompt template ($ARGUMENTS / $1.. + !`bang` + @file) before the turn; an
379
+ // unknown `/...` (or plain text) is left untouched so it reaches the LLM
380
+ // verbatim. expandCommand already runs @file expansion, so the @-ref pass
381
+ // below is skipped when a macro matched. Opt out with options.slashMacros:false.
382
+ let userContent = prompt;
383
+ let slashExpanded = false;
384
+ // A matched command's `allowed-tools:` frontmatter scopes the run (parsed
385
+ // below into enabledToolNames) the same way `cc command run` does.
386
+ let macroAllowedTools = null;
387
+ if (options.slashMacros !== false && prompt.startsWith("/")) {
388
+ try {
389
+ const doMacro =
390
+ deps.resolveSlashMacro ||
391
+ (await import("../repl/slash-macro.js")).resolveSlashMacro;
392
+ const macro = await doMacro(prompt, { cwd });
393
+ if (macro && macro.matched) {
394
+ userContent = macro.promptText;
395
+ slashExpanded = true;
396
+ for (const w of macro.warnings || []) {
397
+ writeErr(` /${macro.name}: ${w}\n`);
398
+ }
399
+ writeErr(` command: /${macro.name} [${macro.scope}]\n`);
400
+ // Frontmatter `model:` / `allowed-tools:` scope the run exactly like
401
+ // `cc command run` — but an explicit --model / --allowed-tools still
402
+ // wins (those arrive as a set options.model / options.allowedTools).
403
+ if (macro.model && !options.model) {
404
+ model = macro.model;
405
+ writeErr(` command: model → ${model}\n`);
406
+ try {
407
+ const { maybeWarnDeprecatedModel } =
408
+ await import("../lib/model-deprecation.js");
409
+ maybeWarnDeprecatedModel({ model });
410
+ } catch {
411
+ // deprecation notice is best-effort
412
+ }
413
+ }
414
+ if (macro.allowedTools && !options.allowedTools) {
415
+ macroAllowedTools = parseToolList(macro.allowedTools);
416
+ }
417
+ }
418
+ } catch {
419
+ // macro resolution is best-effort — fall back to the literal prompt
420
+ }
421
+ }
422
+
372
423
  // ── Expand @file references in the prompt (Claude-Code parity) ─────────
373
424
  // `@path/to/file` tokens are augmented with the referenced file contents (or
374
425
  // a dir listing) so `cc agent -p "review @src/x.js"` works without a manual
375
426
  // cat-pipe. Opt out with `--no-file-refs` (options.expandFileRefs === false).
376
- let userContent = prompt;
377
- if (options.expandFileRefs !== false) {
427
+ // Skipped when a slash macro already expanded (expandCommand ran @refs).
428
+ if (!slashExpanded && options.expandFileRefs !== false) {
378
429
  const doExpand = deps.expandFileRefs || expandFileRefsAsync;
379
430
  const expanded = await doExpand(prompt, { cwd });
380
431
  userContent = expanded.prompt;
@@ -388,7 +439,9 @@ export async function runAgentHeadless(options = {}, deps = {}) {
388
439
  // ── Permission + tool resolution ──────────────────────────────────────
389
440
  const perm = resolvePermissionMode(options.permissionMode);
390
441
  const enabledToolNames = resolveEnabledTools({
391
- allowedTools: options.allowedTools,
442
+ // An explicit --allowed-tools wins; otherwise a matched command's
443
+ // `allowed-tools:` frontmatter scopes the run (null when neither applies).
444
+ allowedTools: options.allowedTools || macroAllowedTools,
392
445
  readOnly: perm.readOnly,
393
446
  });
394
447
  const disabledTools = options.disallowedTools || [];