chainlesschain 0.162.32 → 0.162.34

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 (163) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-Cg_uWAVl.js → AIOps-BYfi9NYS.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DSFtQ1c2.js → ActionButton-BiS_tAN7.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BMxpkw8y.js → Analytics-jiWl_p-B.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-tgVxlmsx.js → AppLayout-m4sIzDot.js} +3 -3
  6. package/src/assets/web-panel/assets/{Audit-DwzGllcp.js → Audit-CPla3Erm.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BG28Y2MV.js → Backup-BGeQzTaB.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-TXthbazl.js → BaseInput-DTf7Z1iU.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-D096SxaD.js → Chat-DPTlQlD-.js} +4 -4
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-BgRXce4e.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-Czttw1JS.js → Checkbox-DY-XuQMu.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-DZtMgv4q.js → Codegen-B6oxPiZI.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-D3DnfExY.js → Col-Dqxb4wSE.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-Bj5AdwqY.js → Community-DCIX514p.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-BQ8Zszub.js → Compact-BGtCzDoJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-DXacb34n.js → Compliance-zcOYd55o.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-BgMUBTkw.js → Cowork-DVTtdIdM.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-fqBWOqlN.js → Cron-CPUaR69k.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-E4oa1MWy.js → Crosschain-DnjUS6QH.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-pwgfYZaV.js → DID-Dnz8VDmx.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-n8mdLFIR.js → Dashboard-CtWf27j7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown--6DYqxk7.js → Dropdown-B4GC1ZV4.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-CkjQluz3.js → EmailListRenderer-wjij3kzr.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-u-QTQ-OC.js → FamilyGuardDashboard-rS-2W4u5.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-D219M5Qc.js → Federation-90p5Tnoz.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-BBU_aopC.js → FormItemContext-Cnrw7gzq.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-pTMCIHcM.js → GenericCardRenderer-C85NsWa3.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-ClcCARWt.js → Git-BFAVM9F8.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-CvUi3I93.js → Governance-DBoRonpq.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DT-a4pVg.js → Inference-DHRyD66j.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-DHMs2LY8.js → KnowledgeGraph-CTvUKecD.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-D2s4eV1N.js → Logs-CB0dv_Ts.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-YC5-fx-6.js → Marketplace-CN7Hm5Uw.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-7JHTEC4T.js → McpTools-q5H25_8L.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BudotVLD.js → Memory-BCV3pZ1d.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-CAiRyLVU.js → MobileBridge-C04Mngt4.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-d0iY0CeK.js → Mtc-ByAMz2DN.js} +2 -2
  39. package/src/assets/web-panel/assets/{MtcAudit-aI2cG1UP.js → MtcAudit-B7V7byJq.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-4bF70khG.js → Multisig-DtKmcVQV.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CwLib1S7.js → NLProgramming-CaMbT5SC.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-Wt7AuFRU.js → Notes-DRjbSTCU.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-D081vV_7.js → NotificationSettings-B9YbJID5.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-DCPei1L9.js → OrderTableRenderer-BcI_-vGS.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-BNEsUNdP.js → Organization-oTask4BE.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-B_1iUXDD.js → Overflow-Bab06ey7.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-Dbc-kNwJ.js → P2P--wlBeU0N.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-D8Xh289k.js → PdhVaultBrowser-D4t77Pwc.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-C77mM6-n.js → Permissions-B3sf6CJ3.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-Dj0J3r_K.js → PersonalDataHub-BXOojk63.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-B6F0WQ2C.js → Pipeline-DReqtBFN.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-eDKOkyyq.js → Privacy-cT1GwKLx.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-DAWwhr5_.js → ProjectInit-BhTAzVhH.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DwdK8k6I.js → ProjectSettings-CK-D8Fyj.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-CbHiwen6.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers--DcYxQfN.js → Providers-B-ftiXa8.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-DU268niT.js → QuickAsk-CT5XPwTF.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-ChnflhV1.js → Recommend-CohhlBZ_.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-DSsY3bQG.js → Reputation-CrgbixFz.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-Zb-EjmgQ.js → Row-ClExmBn3.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-CGLiixZB.js → RssFeed-VV0qizCJ.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-Dhr_po-U.js → Search-CqJapSiL.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-GMYNhGsR.js → Security-DY66Zie6.js} +2 -2
  64. package/src/assets/web-panel/assets/{Services-DiOpnVY0.js → Services-RQwxat7-.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-DG3ez6ME.js → Skeleton-0v37UTU_.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-DZGptytP.js → Skills-B4Vm4DxN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CtGpE3xA.js → Sla-CggphTlo.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-DQFw6Cf9.js → SpeechSettings-BAOU08C7.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-C8X78RpX.js → SyncSettings-DmtC4J1w.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-CExqxzL6.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-SF9_ZWsV.js → Templates-C1QK0YoU.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-BbIQSVZz.js → Tenant-CieOfmqp.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DKr5zDwu.js → Terminal-DWdhrxRq.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-BtLaNaWr.js → TimelineRenderer-CjFVUUDU.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-CfYbk2NG.js → Tokens-Bwbk3id9.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-BLX_XDP0.js → Trigger-uJle_yj4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-BWxUv9PR.js → Trust-BcOuxAA5.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DRwTyQD4.js → UkeySign-DUu7Ufg6.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BsC4VOSo.js → VideoEditing-Ck8JtQ2n.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-CSsO1NJU.js → Wallet-B3jw43on.js} +2 -2
  81. package/src/assets/web-panel/assets/{WebAuthn-z1MxiFzS.js → WebAuthn-Baf9K0y7.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-B1vV7uuJ.js → WorkflowEditor-CTEDl_83.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-C0NJRaL2.js → chat-CKV51quV.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-CHRiteWF.js → colors-BO_RP_yz.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-2XmBBKPD.js → compact-item-BZsxw_ZG.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-DkedHC38.js → createContext-CAbvtzVL.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-Bpn9Xrlw.js → hasIn-QmHT8zDz.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-CyJpmSHZ.js → index-5hlO2-JQ.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DAFLFMXQ.js → index-8BMLlHCv.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-De5vOO9V.js → index-9IqJODII.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-DtU4qZRF.js → index-B2aiE8jk.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-NuBsCRaR.js → index-B3fwyCjJ.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BYUd69vM.js → index-B5zhcul9.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CdR7RfRP.js → index-B9Z83FTS.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-De49R7TX.js → index-BCsZiq4i.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BveL_4n3.js → index-BEJa1FiF.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BYmwEaIk.js → index-BL7gQAuB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DMbF-Euw.js → index-BNvTNZ1V.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-i4W_EAuh.js → index-BPZHeug4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BN068mCR.js → index-BRNYA0BV.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CJOoo72F.js → index-BnPBG3Tr.js} +1 -1
  103. package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +1 -0
  104. package/src/assets/web-panel/assets/{index-DUBsq_1G.js → index-C3K1eHDd.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BfY9U3X5.js → index-C6AA-xB2.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Dk7P-q3n.js → index-C6i3reUS.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cljnfuxu.js → index-CEh2Ry_A.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-cfSUlOfY.js → index-CSaI8R_7.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DM9JrnYi.js → index-CVoYeZ5Q.js} +1 -1
  110. package/src/assets/web-panel/assets/index-CZZnSJEX.js +1 -0
  111. package/src/assets/web-panel/assets/{index-alGjpoM1.js → index-CqiKnXtL.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Sk3-3tKa.js → index-CsBx0u5G.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BZ1gOoiG.js → index-D8CHQnPl.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-uHGxyZtQ.js → index-DBCYOypV.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D9mNfpxi.js → index-DC1CFfQU.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CCg6ZY4t.js → index-DKnngF_f.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BItcSqan.js → index-DKquNxL2.js} +3 -3
  118. package/src/assets/web-panel/assets/{index-D7U411hK.js → index-DRK0oAV5.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CToQxpWz.js → index-DeC7lehI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DryKGM_t.js → index-DjrDGJP2.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-B5NGWgHp.js → index-Dln_vjSY.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BOsIgPge.js → index-Dob6B6qS.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DAeHmElB.js → index-GPY0LjCu.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DDy_RDjs.js → index-Ha2_56mf.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CWgWrrWs.js → index-fnDgExTu.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CxvA72CP.js → index-jd2r-T4p.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DE5Qm9UI.js → index-qPafbZmr.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-DlDE-QgI.js → initDefaultProps-Bc2GWeWe.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-CodUbIRF.js → motion-BI-Rxw6o.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-DaLwsHeR.js → move-DRPdwDQB.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-DdVg-3rL.js → omit-B4XTl3jW.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-KLR1EVCo.js → pickAttrs-Do5d86Wr.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-ChV7HvNw.js → placementArrow-B8VGZ0ZF.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-BB_A8dBt.js → responsiveObserve-Cf0kI_vN.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Bc1tQnIK.js → slide-Cb0psjSL.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-CgrveSb0.js → statusUtils-Bjuo5Oal.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-vXAYhhjz.js → styleChecker-BLMhoHJ5.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-BCIMPfq9.js → useFlexGapSupport-BdCwAfNU.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DMZGdr6G.js → useFs-9Jhaz5gG.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-118tWI_Z.js → usePersonalDataHub-xYFyXKwD.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Z7O2Y7JP.js → vnode-CVhepE6Z.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BXym6zmD.js → zoom-IbbtJ4Zr.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +39 -0
  145. package/src/commands/command.js +187 -0
  146. package/src/commands/context.js +189 -0
  147. package/src/index.js +4 -0
  148. package/src/lib/goal-assess.js +228 -0
  149. package/src/lib/goal-store.js +33 -0
  150. package/src/lib/slash-commands.js +197 -0
  151. package/src/repl/agent-repl.js +42 -1
  152. package/src/runtime/agent-core.js +275 -0
  153. package/src/runtime/headless-runner.js +151 -0
  154. package/src/runtime/headless-stream.js +86 -0
  155. package/src/runtime/mcp-config.js +239 -0
  156. package/src/runtime/policies/agent-policy.js +1 -0
  157. package/src/assets/web-panel/assets/ChatBubbleRenderer-PIx0Eu9I.js +0 -1
  158. package/src/assets/web-panel/assets/MobileProjects-CrJJOCFw.js +0 -1
  159. package/src/assets/web-panel/assets/Projects-Cb3p5QAP.js +0 -1
  160. package/src/assets/web-panel/assets/Tasks-DtVkhWCV.js +0 -1
  161. package/src/assets/web-panel/assets/devWarning-DmNpkOdC.js +0 -1
  162. package/src/assets/web-panel/assets/index-7nAysteg.js +0 -1
  163. package/src/assets/web-panel/assets/index-BKWSQilQ.js +0 -1
@@ -0,0 +1,197 @@
1
+ /**
2
+ * slash-commands — user-defined command templates (Claude-Code parity).
3
+ *
4
+ * Markdown files under `.claude/commands/` (project) or `~/.claude/commands/`
5
+ * (personal) become reusable command macros. Distinct from skills (skills are
6
+ * AI-invoked capability bundles; these are user-authored prompt macros you run
7
+ * explicitly). A file `git/commit.md` is the command `git:commit`.
8
+ *
9
+ * Frontmatter (all optional): `description`, `argument-hint`, `allowed-tools`,
10
+ * `model`. Body is the prompt template, with substitutions applied at run time:
11
+ * $ARGUMENTS → all args joined by space
12
+ * $1 $2 … $9 → positional args (missing → empty string)
13
+ * !`<cmd>` → run <cmd> in a shell, splice in its stdout (bang exec)
14
+ * @path → splice in file/dir contents (via file-ref-expander)
15
+ *
16
+ * Project scope shadows personal scope on a name clash. Discovery + parse +
17
+ * expand are pure (inject fs/exec/cwd) so the whole thing is unit-testable.
18
+ */
19
+
20
+ import fsDefault from "node:fs";
21
+ import pathDefault from "node:path";
22
+ import { homedir } from "node:os";
23
+ import { execSync as execSyncDefault } from "node:child_process";
24
+ import yaml from "js-yaml";
25
+ import { expandFileRefs } from "../runtime/file-ref-expander.js";
26
+
27
+ const _deps = { fs: fsDefault, path: pathDefault, execSync: execSyncDefault };
28
+
29
+ /**
30
+ * Split `--- ... ---` YAML frontmatter from the body and camelCase the keys
31
+ * (so `argument-hint` → `argumentHint`). Self-contained (no skill-loader, whose
32
+ * import chain drags in heavy native deps). Returns `{ data, body }`.
33
+ */
34
+ function parseFrontmatter(content) {
35
+ const text = String(content || "");
36
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
37
+ if (!m) return { data: {}, body: text.trim() };
38
+ let raw = {};
39
+ try {
40
+ raw = yaml.load(m[1]) || {};
41
+ } catch {
42
+ raw = {};
43
+ }
44
+ const data = {};
45
+ for (const [k, v] of Object.entries(raw)) {
46
+ const camel = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
47
+ data[camel] = v;
48
+ }
49
+ return { data, body: (m[2] || "").trim() };
50
+ }
51
+
52
+ /** Default shell timeout for `!`cmd`` bang execution. */
53
+ export const BANG_TIMEOUT_MS = 10_000;
54
+
55
+ /**
56
+ * The directories scanned for command files, project first (it shadows personal
57
+ * on a name clash). `opts.home` overrides the personal root for tests.
58
+ */
59
+ export function commandDirs(cwd = process.cwd(), opts = {}) {
60
+ const path = opts.deps?.path || _deps.path;
61
+ const home = opts.home || homedir();
62
+ return [
63
+ { dir: path.join(cwd, ".claude", "commands"), scope: "project" },
64
+ { dir: path.join(home, ".claude", "commands"), scope: "personal" },
65
+ ];
66
+ }
67
+
68
+ /** Recursively collect `*.md` files under `dir` as `{file, rel}` (rel uses /). */
69
+ function walkMd(dir, { fs, path }, base = dir, acc = []) {
70
+ let entries;
71
+ try {
72
+ entries = fs.readdirSync(dir, { withFileTypes: true });
73
+ } catch {
74
+ return acc;
75
+ }
76
+ for (const e of entries) {
77
+ const full = path.join(dir, e.name);
78
+ if (e.isDirectory()) {
79
+ walkMd(full, { fs, path }, base, acc);
80
+ } else if (e.isFile() && e.name.endsWith(".md")) {
81
+ const rel = path.relative(base, full).replace(/\\/g, "/");
82
+ acc.push({ file: full, rel });
83
+ }
84
+ }
85
+ return acc;
86
+ }
87
+
88
+ /** Command name from a relative path: `git/commit.md` → `git:commit`. */
89
+ function nameFromRel(rel) {
90
+ return rel.replace(/\.md$/, "").replace(/\//g, ":");
91
+ }
92
+
93
+ /** Parse one command file into its metadata + body (no expansion yet). */
94
+ export function parseCommandFile(file, scope, opts = {}) {
95
+ const fs = opts.deps?.fs || _deps.fs;
96
+ let content;
97
+ try {
98
+ content = fs.readFileSync(file, "utf-8");
99
+ } catch {
100
+ return null;
101
+ }
102
+ const { data, body } = parseFrontmatter(content);
103
+ return {
104
+ file,
105
+ scope,
106
+ description: data.description || "",
107
+ argumentHint: data.argumentHint || "",
108
+ allowedTools: data.allowedTools || null,
109
+ model: data.model || null,
110
+ body: body || "",
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Discover all commands across both scopes. Project shadows personal by name.
116
+ * @returns {Array<{name, scope, file, description, argumentHint, allowedTools, model}>}
117
+ */
118
+ export function discoverCommands(cwd = process.cwd(), opts = {}) {
119
+ const fs = opts.deps?.fs || _deps.fs;
120
+ const path = opts.deps?.path || _deps.path;
121
+ const byName = new Map();
122
+ // Personal first, then project — so project overwrites on clash.
123
+ const dirs = commandDirs(cwd, opts).reverse();
124
+ for (const { dir, scope } of dirs) {
125
+ for (const { file, rel } of walkMd(dir, { fs, path })) {
126
+ const meta = parseCommandFile(file, scope, opts);
127
+ if (!meta) continue;
128
+ const name = nameFromRel(rel);
129
+ byName.set(name, { name, ...meta });
130
+ }
131
+ }
132
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
133
+ }
134
+
135
+ /** Look up one command by name (accepts `git:commit` or `git/commit`). */
136
+ export function getCommand(name, cwd = process.cwd(), opts = {}) {
137
+ const wanted = String(name || "")
138
+ .replace(/^\//, "")
139
+ .replace(/\//g, ":");
140
+ return discoverCommands(cwd, opts).find((c) => c.name === wanted) || null;
141
+ }
142
+
143
+ /** Substitute $ARGUMENTS and $1..$9 in `text`. */
144
+ export function substituteArgs(text, args = []) {
145
+ const list = Array.isArray(args) ? args : [];
146
+ let out = text.replace(/\$ARGUMENTS/g, list.join(" "));
147
+ out = out.replace(/\$([1-9])/g, (_, d) => list[Number(d) - 1] ?? "");
148
+ return out;
149
+ }
150
+
151
+ /** Run every `!`cmd`` and replace it with the command's stdout (best-effort). */
152
+ function runBangs(text, { cwd, execSync }) {
153
+ return text.replace(/!`([^`]+)`/g, (_, cmd) => {
154
+ try {
155
+ const out = execSync(cmd, {
156
+ cwd,
157
+ encoding: "utf-8",
158
+ timeout: BANG_TIMEOUT_MS,
159
+ stdio: ["ignore", "pipe", "pipe"],
160
+ });
161
+ return String(out).trim();
162
+ } catch (err) {
163
+ return `[command failed: ${cmd} — ${err.message?.split("\n")[0] || err}]`;
164
+ }
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Expand a command into a final prompt string.
170
+ * 1. $ARGUMENTS / $1..$9 substitution
171
+ * 2. !`cmd` bang execution (skipped when opts.allowBang === false)
172
+ * 3. @path file references (via file-ref-expander)
173
+ *
174
+ * @param {object} command output of getCommand/parseCommandFile (needs .body)
175
+ * @param {string[]} args
176
+ * @param {object} [opts] { cwd, allowBang, deps:{ execSync } }
177
+ * @returns {{ prompt:string, warnings:string[] }}
178
+ */
179
+ export function expandCommand(command, args = [], opts = {}) {
180
+ const cwd = opts.cwd || process.cwd();
181
+ const execSync = opts.deps?.execSync || _deps.execSync;
182
+ const warnings = [];
183
+
184
+ let text = substituteArgs(command.body || "", args);
185
+
186
+ if (opts.allowBang !== false) {
187
+ text = runBangs(text, { cwd, execSync });
188
+ }
189
+
190
+ // @file expansion appends a <referenced-files> block when anything resolves.
191
+ const expanded = expandFileRefs(text, { cwd });
192
+ for (const w of expanded.warnings) warnings.push(w);
193
+
194
+ return { prompt: expanded.prompt, warnings };
195
+ }
196
+
197
+ export { _deps };
@@ -58,6 +58,7 @@ import {
58
58
  } from "../lib/session-hooks.js";
59
59
  import { HookEvents } from "../lib/hook-manager.js";
60
60
  import { IterationBudget } from "../lib/iteration-budget.js";
61
+ import { loadMcpConfig } from "../runtime/mcp-config.js";
61
62
  import {
62
63
  AGENT_TOOLS,
63
64
  buildSystemPrompt,
@@ -354,6 +355,10 @@ export async function startAgentRepl(options = {}) {
354
355
  // and applies bundle manifest metadata (model/provider override, agentId).
355
356
  let _bundleResolved = null;
356
357
  let _bundleMcpClient = null;
358
+ // --mcp-config (interactive parity with headless): ad-hoc MCP servers loaded
359
+ // for this session via the shared mcp-config engine. Holds {mcpClient,
360
+ // extraToolDefinitions, externalToolExecutors, externalToolDescriptors}.
361
+ let _adhocMcp = null;
357
362
  if (options.bundlePath) {
358
363
  try {
359
364
  const { loadBundle } =
@@ -446,6 +451,28 @@ export async function startAgentRepl(options = {}) {
446
451
  }
447
452
  }
448
453
 
454
+ // --mcp-config: connect ad-hoc MCP servers for this interactive session and
455
+ // expose their tools to the LLM (Claude-Code parity with headless). Reuses the
456
+ // shared engine, so tools surface as mcp__<server>__<tool>. Best-effort: a bad
457
+ // config is reported but never aborts the REPL.
458
+ if (options.mcpConfig) {
459
+ try {
460
+ _adhocMcp = await loadMcpConfig(options.mcpConfig, {
461
+ writeErr: (s) => process.stderr.write(s),
462
+ });
463
+ const toolCount = _adhocMcp.extraToolDefinitions.length;
464
+ logger.log(
465
+ chalk.gray(
466
+ `MCP: ${_adhocMcp.connected.length} server(s), ${toolCount} tool(s) ` +
467
+ `(mcp__<server>__<tool>)`,
468
+ ),
469
+ );
470
+ } catch (mcpErr) {
471
+ logger.log(chalk.yellow(`MCP: --mcp-config failed — ${mcpErr.message}`));
472
+ _adhocMcp = null;
473
+ }
474
+ }
475
+
449
476
  // Apply bundle approval policy to this session (after both gate and sessionId are ready)
450
477
  if (_bundleResolved?.approvalPolicy?.default && _approvalGate && sessionId) {
451
478
  try {
@@ -1567,7 +1594,12 @@ export async function startAgentRepl(options = {}) {
1567
1594
  checkpointSession: sessionId,
1568
1595
  prepareCall,
1569
1596
  approvalGate: _approvalGate,
1570
- mcpClient: _bundleMcpClient || undefined,
1597
+ // MCP: --mcp-config (ad-hoc) wins; bundle MCP is the fallback. The 3
1598
+ // tool channels expose --mcp-config servers' tools to the LLM directly.
1599
+ mcpClient: _adhocMcp?.mcpClient || _bundleMcpClient || undefined,
1600
+ extraToolDefinitions: _adhocMcp?.extraToolDefinitions,
1601
+ externalToolExecutors: _adhocMcp?.externalToolExecutors,
1602
+ externalToolDescriptors: _adhocMcp?.externalToolDescriptors,
1571
1603
  chatFn: _fallbackChatFn,
1572
1604
  });
1573
1605
 
@@ -1778,6 +1810,15 @@ export async function startAgentRepl(options = {}) {
1778
1810
  }
1779
1811
  }
1780
1812
 
1813
+ // Disconnect ad-hoc (--mcp-config) MCP servers
1814
+ if (_adhocMcp?.mcpClient) {
1815
+ try {
1816
+ await _adhocMcp.mcpClient.disconnectAll();
1817
+ } catch (_e) {
1818
+ // Non-critical
1819
+ }
1820
+ }
1821
+
1781
1822
  // Shutdown runtime
1782
1823
  try {
1783
1824
  await shutdown();
@@ -1729,6 +1729,19 @@ export async function chatWithTools(rawMessages, options) {
1729
1729
  ? baseUrl
1730
1730
  : "https://api.anthropic.com/v1";
1731
1731
 
1732
+ // Real token streaming (--include-partial-messages): stream the SSE response
1733
+ // and forward text deltas live, assembling tool_use blocks back into the
1734
+ // same {message, usage} shape the non-streaming path returns.
1735
+ if (typeof options.onToken === "function") {
1736
+ return await _chatAnthropicStreaming(
1737
+ `${url}/messages`,
1738
+ { ...body, stream: true },
1739
+ { "x-api-key": key, "anthropic-version": "2023-06-01" },
1740
+ options.onToken,
1741
+ signal,
1742
+ );
1743
+ }
1744
+
1732
1745
  const response = await fetch(`${url}/messages`, {
1733
1746
  method: "POST",
1734
1747
  headers: {
@@ -1798,6 +1811,27 @@ export async function chatWithTools(rawMessages, options) {
1798
1811
  volcengine: "doubao-seed-1-6-251015",
1799
1812
  };
1800
1813
 
1814
+ // Real token streaming (--include-partial-messages) for every OpenAI-compatible
1815
+ // provider (openai / deepseek / dashscope / mistral / gemini / volcengine):
1816
+ // stream the SSE response, forward content deltas live, and reassemble the
1817
+ // delta-fragmented tool_calls into the standard {message, usage} shape.
1818
+ if (typeof options.onToken === "function") {
1819
+ return await _chatOpenAIStreaming(
1820
+ `${url}/chat/completions`,
1821
+ {
1822
+ model: model || defaultModels[provider] || "gpt-4o-mini",
1823
+ messages,
1824
+ tools,
1825
+ stream: true,
1826
+ stream_options: { include_usage: true },
1827
+ },
1828
+ key,
1829
+ options.onToken,
1830
+ signal,
1831
+ provider,
1832
+ );
1833
+ }
1834
+
1801
1835
  const response = await fetch(`${url}/chat/completions`, {
1802
1836
  method: "POST",
1803
1837
  headers: {
@@ -1934,6 +1968,225 @@ async function _chatOllamaStreaming(apiUrl, body, onToken, signal) {
1934
1968
  return _ollamaFinalize(state);
1935
1969
  }
1936
1970
 
1971
+ // ─── Anthropic streaming (SSE → {message, usage}, tool_use reassembled) ──────
1972
+ //
1973
+ // Anthropic /messages with stream:true emits SSE: message_start (input usage),
1974
+ // content_block_start (text or tool_use header), content_block_delta
1975
+ // (text_delta → onToken, or input_json_delta accumulating a tool's JSON args),
1976
+ // message_delta (output usage). We reduce per `data:` line and finalize into
1977
+ // the same shape chatWithTools returns non-streamed.
1978
+
1979
+ function _anthropicInitState() {
1980
+ return { text: "", blocks: {}, inputTokens: 0, outputTokens: 0 };
1981
+ }
1982
+
1983
+ function _anthropicReduceLine(state, raw, onToken) {
1984
+ const line = (raw || "").trim();
1985
+ if (!line.startsWith("data:")) return state;
1986
+ const payload = line.slice(5).trim();
1987
+ if (!payload) return state;
1988
+ let obj;
1989
+ try {
1990
+ obj = JSON.parse(payload);
1991
+ } catch {
1992
+ return state;
1993
+ }
1994
+ if (obj.type === "message_start") {
1995
+ state.inputTokens =
1996
+ Number(obj.message?.usage?.input_tokens) || state.inputTokens;
1997
+ } else if (obj.type === "content_block_start") {
1998
+ const cb = obj.content_block || {};
1999
+ state.blocks[obj.index] =
2000
+ cb.type === "tool_use"
2001
+ ? { type: "tool_use", id: cb.id, name: cb.name, json: "" }
2002
+ : { type: "text" };
2003
+ } else if (obj.type === "content_block_delta") {
2004
+ const d = obj.delta || {};
2005
+ if (d.type === "text_delta" && d.text) {
2006
+ state.text += d.text;
2007
+ if (typeof onToken === "function") {
2008
+ try {
2009
+ onToken(d.text);
2010
+ } catch {
2011
+ // a failing UI hook must never break the run
2012
+ }
2013
+ }
2014
+ } else if (d.type === "input_json_delta" && state.blocks[obj.index]) {
2015
+ state.blocks[obj.index].json += d.partial_json || "";
2016
+ }
2017
+ } else if (obj.type === "message_delta") {
2018
+ state.outputTokens = Number(obj.usage?.output_tokens) || state.outputTokens;
2019
+ }
2020
+ return state;
2021
+ }
2022
+
2023
+ function _anthropicFinalize(state) {
2024
+ const toolCalls = [];
2025
+ for (const k of Object.keys(state.blocks)) {
2026
+ const b = state.blocks[k];
2027
+ if (b.type === "tool_use") {
2028
+ let input = {};
2029
+ try {
2030
+ input = b.json ? JSON.parse(b.json) : {};
2031
+ } catch {
2032
+ input = {};
2033
+ }
2034
+ toolCalls.push({
2035
+ id: b.id,
2036
+ type: "function",
2037
+ function: { name: b.name, arguments: JSON.stringify(input) },
2038
+ });
2039
+ }
2040
+ }
2041
+ const message = { role: "assistant", content: state.text };
2042
+ if (toolCalls.length) message.tool_calls = toolCalls;
2043
+ const out = { message };
2044
+ if (state.inputTokens || state.outputTokens) {
2045
+ out.usage = {
2046
+ input_tokens: state.inputTokens,
2047
+ output_tokens: state.outputTokens,
2048
+ };
2049
+ }
2050
+ return out;
2051
+ }
2052
+
2053
+ /** Pure reducer over Anthropic SSE lines — exported for tests (no HTTP). */
2054
+ export function _accumulateAnthropicStream(lines, onToken) {
2055
+ const state = _anthropicInitState();
2056
+ for (const line of lines) _anthropicReduceLine(state, line, onToken);
2057
+ return _anthropicFinalize(state);
2058
+ }
2059
+
2060
+ async function _chatAnthropicStreaming(apiUrl, body, extraHeaders, onToken, signal) {
2061
+ const response = await fetch(apiUrl, {
2062
+ method: "POST",
2063
+ headers: { "Content-Type": "application/json", ...extraHeaders },
2064
+ signal,
2065
+ body: JSON.stringify(body),
2066
+ });
2067
+ if (!response.ok) {
2068
+ throw new Error(`Anthropic error: ${response.status}`);
2069
+ }
2070
+ const state = _anthropicInitState();
2071
+ const reader = response.body.getReader();
2072
+ const decoder = new TextDecoder();
2073
+ let buf = "";
2074
+ for (;;) {
2075
+ const { done, value } = await reader.read();
2076
+ if (done) break;
2077
+ buf += decoder.decode(value, { stream: true });
2078
+ const lines = buf.split("\n");
2079
+ buf = lines.pop() || "";
2080
+ for (const line of lines) _anthropicReduceLine(state, line, onToken);
2081
+ }
2082
+ if (buf.trim()) _anthropicReduceLine(state, buf, onToken);
2083
+ return _anthropicFinalize(state);
2084
+ }
2085
+
2086
+ // ─── OpenAI-compatible streaming (SSE → {message, usage}) ────────────────────
2087
+ //
2088
+ // `data:` lines carry choices[0].delta.{content, tool_calls[]}; tool_calls
2089
+ // arrive fragmented and keyed by `index` (name in the first chunk, arguments
2090
+ // concatenated across chunks). usage rides the terminal chunk when
2091
+ // stream_options.include_usage was requested. Terminator: `data: [DONE]`.
2092
+
2093
+ function _openaiInitState() {
2094
+ return { text: "", tools: [], inputTokens: 0, outputTokens: 0 };
2095
+ }
2096
+
2097
+ function _openaiReduceLine(state, raw, onToken) {
2098
+ const line = (raw || "").trim();
2099
+ if (!line.startsWith("data:")) return state;
2100
+ const payload = line.slice(5).trim();
2101
+ if (!payload || payload === "[DONE]") return state;
2102
+ let obj;
2103
+ try {
2104
+ obj = JSON.parse(payload);
2105
+ } catch {
2106
+ return state;
2107
+ }
2108
+ const delta = obj.choices?.[0]?.delta;
2109
+ if (delta?.content) {
2110
+ state.text += delta.content;
2111
+ if (typeof onToken === "function") {
2112
+ try {
2113
+ onToken(delta.content);
2114
+ } catch {
2115
+ // a failing UI hook must never break the run
2116
+ }
2117
+ }
2118
+ }
2119
+ if (Array.isArray(delta?.tool_calls)) {
2120
+ for (const tc of delta.tool_calls) {
2121
+ const idx = tc.index ?? 0;
2122
+ if (!state.tools[idx]) state.tools[idx] = { id: undefined, name: "", args: "" };
2123
+ if (tc.id) state.tools[idx].id = tc.id;
2124
+ if (tc.function?.name) state.tools[idx].name = tc.function.name;
2125
+ if (tc.function?.arguments) state.tools[idx].args += tc.function.arguments;
2126
+ }
2127
+ }
2128
+ if (obj.usage) {
2129
+ state.inputTokens = Number(obj.usage.prompt_tokens) || state.inputTokens;
2130
+ state.outputTokens =
2131
+ Number(obj.usage.completion_tokens) || state.outputTokens;
2132
+ }
2133
+ return state;
2134
+ }
2135
+
2136
+ function _openaiFinalize(state) {
2137
+ const toolCalls = state.tools.filter(Boolean).map((t) => ({
2138
+ id: t.id || `call_${t.name || "tool"}`,
2139
+ type: "function",
2140
+ function: { name: t.name, arguments: t.args || "{}" },
2141
+ }));
2142
+ const message = { role: "assistant", content: state.text };
2143
+ if (toolCalls.length) message.tool_calls = toolCalls;
2144
+ const out = { message };
2145
+ if (state.inputTokens || state.outputTokens) {
2146
+ out.usage = {
2147
+ input_tokens: state.inputTokens,
2148
+ output_tokens: state.outputTokens,
2149
+ };
2150
+ }
2151
+ return out;
2152
+ }
2153
+
2154
+ /** Pure reducer over OpenAI-compatible SSE lines — exported for tests. */
2155
+ export function _accumulateOpenAIStream(lines, onToken) {
2156
+ const state = _openaiInitState();
2157
+ for (const line of lines) _openaiReduceLine(state, line, onToken);
2158
+ return _openaiFinalize(state);
2159
+ }
2160
+
2161
+ async function _chatOpenAIStreaming(apiUrl, body, apiKey, onToken, signal, provider) {
2162
+ const response = await fetch(apiUrl, {
2163
+ method: "POST",
2164
+ headers: {
2165
+ "Content-Type": "application/json",
2166
+ Authorization: `Bearer ${apiKey}`,
2167
+ },
2168
+ signal,
2169
+ body: JSON.stringify(body),
2170
+ });
2171
+ if (!response.ok) {
2172
+ throw new Error(`${provider} API error: ${response.status}`);
2173
+ }
2174
+ const state = _openaiInitState();
2175
+ const reader = response.body.getReader();
2176
+ const decoder = new TextDecoder();
2177
+ let buf = "";
2178
+ for (;;) {
2179
+ const { done, value } = await reader.read();
2180
+ if (done) break;
2181
+ buf += decoder.decode(value, { stream: true });
2182
+ const lines = buf.split("\n");
2183
+ buf = lines.pop() || "";
2184
+ for (const line of lines) _openaiReduceLine(state, line, onToken);
2185
+ }
2186
+ if (buf.trim()) _openaiReduceLine(state, buf, onToken);
2187
+ return _openaiFinalize(state);
2188
+ }
2189
+
1937
2190
  function _normalizeAnthropicResponse(data) {
1938
2191
  const content = data.content || [];
1939
2192
  const textBlocks = content.filter((b) => b.type === "text");
@@ -2198,12 +2451,34 @@ export async function* agentLoop(messages, options) {
2198
2451
  );
2199
2452
  if (stats.saved > 0 && compacted.length < messages.length) {
2200
2453
  messages.splice(0, messages.length, ...compacted);
2454
+ // Persist the compaction so a later --resume rebuilds from the
2455
+ // shortened history. An explicit `onCompaction` hook (if a caller
2456
+ // provides one) takes precedence; otherwise self-persist a `compact`
2457
+ // event — but only when the session is already being persisted to
2458
+ // disk (the JSONL file exists), so a one-shot `cc agent -p` (which
2459
+ // never creates a session file) writes nothing. Opt out with
2460
+ // `persistCompaction: false`. Best-effort throughout.
2201
2461
  if (typeof options.onCompaction === "function") {
2202
2462
  try {
2203
2463
  options.onCompaction(stats, compacted);
2204
2464
  } catch {
2205
2465
  // persistence is best-effort
2206
2466
  }
2467
+ } else if (
2468
+ options.sessionId &&
2469
+ options.persistCompaction !== false
2470
+ ) {
2471
+ try {
2472
+ const store = await import("../harness/jsonl-session-store.js");
2473
+ if (store.sessionExists(options.sessionId)) {
2474
+ store.appendCompactEvent(options.sessionId, {
2475
+ ...stats,
2476
+ messages: compacted,
2477
+ });
2478
+ }
2479
+ } catch {
2480
+ // self-persist is best-effort
2481
+ }
2207
2482
  }
2208
2483
  yield { type: "compaction", stats, runId };
2209
2484
  }