chainlesschain 0.162.71 → 0.162.72

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 (167) hide show
  1. package/package.json +3 -2
  2. package/src/assets/web-panel/assets/{AIOps-pes7_jGr.js → AIOps-1bl50Cen.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DiHWdeH1.js → ActionButton-Bw4kAx-1.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C51UX-V_.js → Analytics-junq7r42.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BE6LJLw9.js → AppLayout-vf1TVGeu.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-CtTkLTe2.js → Audit-CBzpgcL9.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-lmpVwPxc.js → Backup-ChwGPeP4.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-Dr7DR1E1.js → BaseInput-2G2KGE6M.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DXph6klA.js → Chat-Cr9NwfDA.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-RefrXxqp.js → ChatBubbleRenderer-CJvlo_ay.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-BF0aS5R9.js → Checkbox-DN_5PBT7.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B-oHWNTV.js → Codegen-D4bluAgT.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-B6KdyRTE.js → Col-DBP3UjYW.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-D5ac0KyO.js → Community-ge-SvR3c.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-Dc61O2eQ.js → Compact-DGXvlGNv.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-a48qvNmd.js → Compliance-Cn7nBsFQ.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CIF9lSRj.js → Cowork-DgHEO2de.js} +3 -3
  18. package/src/assets/web-panel/assets/{Cron-C_gkf4xd.js → Cron-DUbYxPs0.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-CJt15PzW.js → Crosschain-3J0MWPQq.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-D8I_okEj.js → DID-BR_Jpkh4.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-O4q-qCF2.js → Dashboard-ffykLkAJ.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-flRziiaw.js → Dropdown-DaI38dXT.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-CnnkfGPw.js → EmailListRenderer-CnIzkUVG.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CYAJpGqd.js → FamilyGuardDashboard-BxrgAAZg.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-Dm3WqzQ_.js → Federation-BuS_FRh3.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CpAIter6.js → FormItemContext-DBG-8g1w.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-ChzXcyuN.js → GenericCardRenderer-CmEc8RgH.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-DElmMsL4.js → Git-CK10BYwY.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-EWmgn9io.js → Governance-C8cABM_I.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-VtOR_sef.js → Inference-Dr4_DIw3.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-OMqlGkMR.js → KnowledgeGraph-0_bLuvvb.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CTgYDsTZ.js → Logs-PVGZxt9z.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-DZIUTbn8.js → Marketplace-BtVnS4HW.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-DWeOErCA.js → McpTools-BVulusCC.js} +3 -3
  35. package/src/assets/web-panel/assets/{Memory-DwFBp-ev.js → Memory-DEMtNbJ9.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-D42xBfE3.js → MobileBridge-5QL654FQ.js} +3 -3
  37. package/src/assets/web-panel/assets/{MobileProjects-BHCHauUl.js → MobileProjects-G5Du9Thr.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-CabnNsyS.js → Mtc-CNFPJej0.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-Ddx9Awu5.js → MtcAudit-DAJ81Cej.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-CJpYBZ8f.js → Multisig-Cwj8tXfT.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-BvPJskVG.js → NLProgramming-Di0ptaL6.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-92ELvJM2.js → Notes-Cse_4Ox6.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-CXz0SUzP.js → NotificationSettings-ntpoi5ec.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-4iWRkMr-.js → OrderTableRenderer-CbKsOM0D.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-DblqJPeY.js → Organization-Bhjya5h9.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CHfS3HwD.js → Overflow-C0u3aive.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DIg0pLIG.js → P2P-BWrTwf_a.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-BhD3DGjk.js → PdhVaultBrowser-pld2Qlqk.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-Ct7bwrz8.js → Permissions-CW5FG3mE.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-DG9c1eas.js → PersonalDataHub-BuBmqfDa.js} +2 -2
  51. package/src/assets/web-panel/assets/{Pipeline-BwQnEqG0.js → Pipeline-nZr06BNO.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-BvWaf-Ba.js → Privacy-B_XvMHYv.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-BFzMHtiW.js → ProjectInit-CbRHJy85.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DDpokSpH.js → ProjectSettings-CCH8SpEa.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-3IL8lyPl.js → Projects-D5Min2Fu.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-C6BsEzSR.js → Providers-DgCY5A8A.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-Mh6690Ey.js → QuickAsk-DEcKTiTV.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CJbZ6wgA.js → Recommend-BRaq2oKc.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-GDcyl6iQ.js → Reputation-DzdbBdVv.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-6p4DKFSi.js → Row-Ds2JNh7c.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-BLhrkoDq.js → RssFeed-Bk1-t4Ie.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-Ckiyt35t.js → Search-DaVhRwoq.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-qhh-B5Qt.js → Security-D9mXjPSu.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-D7a2OwKm.js → Services-D8sukL97.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-CSMxupKd.js → Skeleton-COCJt30X.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CGpMng8w.js → Skills-DtFPSPSc.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CYtaU3Ar.js → Sla-DIab-wjR.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BTCsu1uM.js → SpeechSettings-xxIH9hgB.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-Cnzx2L5I.js → SyncSettings-DrpxzJOH.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-BhnGtqaZ.js → Tasks-maiJ3jAT.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-Ccdx_C3w.js → Templates-COE-dSks.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-ZN8c86j3.js → Tenant-CLGObu4M.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-Bw8WBvW3.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-CjgirE9d.js → TimelineRenderer-D4gdk3Qb.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-QoRpi6Wf.js → Tokens-CSoL_uAw.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-CEQKJkhs.js → Trigger-BCRcowFi.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-UTuAzV1q.js → Trust-D5QPZ198.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-JDs9I7fl.js → UkeySign-C7SsMqfZ.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BACYQQSd.js → VideoEditing-CDQ4HNZL.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-C-SKluhH.js → Wallet-VmG4P_Fm.js} +3 -3
  81. package/src/assets/web-panel/assets/{WebAuthn-9chizTFy.js → WebAuthn-qtC0dZxA.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-0mF-LAUo.js → WorkflowEditor-C_CSdk3l.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-Di7QINiP.js → chat-BGm8YCIo.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-SBmhHMM9.js → colors-BScOmAwk.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BmbETbNj.js → compact-item-HDwaT31n.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-C7hFnsJ7.js → createContext-CheSjPm8.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-CjO6o19j.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CFtthKDD.js → hasIn-C1_NbX0O.js} +1 -1
  89. package/src/assets/web-panel/assets/index--jhpSxS1.js +1 -0
  90. package/src/assets/web-panel/assets/{index-D6vNUp6c.js → index-6C8mxO8j.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-ClIp4Vin.js → index-B94_DZF-.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-D134XFWR.js → index-BGROuzQG.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-Cib-RTws.js → index-BKQordyU.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CWDk3nDZ.js → index-BMxPL1oG.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Bpo7UVJe.js → index-Bd2zS3jK.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Dww6gCzI.js → index-ByoUjX8f.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-PN02VlUB.js → index-C4FSaMAu.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CovTrPpI.js → index-C6WXnaG4.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C6tZzsb9.js → index-C71Z__li.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-sz0w-D-C.js → index-CSaztnyZ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-sA4vr5WV.js → index-CYkezxqK.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-lRVjtXeH.js → index-Clqj2JTi.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CNpb8m5a.js → index-Cm55PcYG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C2fhXmhW.js → index-CxoOEb-l.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C2CN7coN.js → index-D2gy4IX0.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-WjZVyLn7.js → index-D8xpncJ0.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-_hO8-EnW.js → index-DB6gbgU2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CgXQwqXr.js → index-DDk0s3nO.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CcBhsVYn.js → index-DQ01dBAS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D8CeUlN0.js → index-DSuWcpsv.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C07kpleB.js → index-DVyL9jbT.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Dps9xdE8.js → index-DaATJtzu.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DRBc1Ewn.js → index-DbfAaBxL.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Dk4ez5rf.js → index-DhTELUKN.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DI2xH1fU.js → index-Dk2N6hxE.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BcawAm_i.js → index-DkiIxTIS.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-B4etIqbf.js → index-DoZwBGUX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CSnTUPQx.js → index-DppSbT8L.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-BCcCs7LJ.js → index-DzE-95XH.js} +1 -1
  120. package/src/assets/web-panel/assets/index-J4Rzclyc.js +1 -0
  121. package/src/assets/web-panel/assets/{index-NxxQTlPJ.js → index-KG9xVANC.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-_a2NG3iP.js → index-KXrUKw1h.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DWfObC0n.js → index-eSBoP6E_.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D0yO3vWh.js → index-ggl9cj35.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BT2uOwmA.js → index-i-xYmSsZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DEYnWiq1.js → index-lB5kN9Yc.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BcRTX_WO.js → index-yKmudyK7.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-B1zCwkvT.js → initDefaultProps-D7Q7zDzP.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BDGIV9KA.js → motion-B4DcaqPb.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-BITqgluK.js → move-CYxmYrFY.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-uD97QKBF.js → omit-ze_9dKw3.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-CZpXhSIE.js → pickAttrs-D24HrRSv.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-ByUvT_08.js → placementArrow-BWJZVYyS.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-BQG9LlBs.js → responsiveObserve-CseJia_L.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Bx9TyeEE.js → slide-CBWNEpey.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-XWaMqYM8.js → statusUtils-f4e6p7yd.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BZ-nMyDB.js → styleChecker-DZmfWZ8G.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-D_iwA3vx.js → useFlexGapSupport-4RBaBnVI.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DuvW_aRX.js → useFs-Cv6bNoEA.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BO7orS0d.js → usePersonalDataHub-DOTETJIA.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Cc_fqAL4.js → vnode-DqS1K6-J.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BGkBp90M.js → zoom-C1MYzIJG.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/cowork.js +8 -0
  145. package/src/commands/crosschain.js +32 -4
  146. package/src/commands/init.js +10 -10
  147. package/src/commands/loop.js +9 -3
  148. package/src/commands/memory.js +6 -4
  149. package/src/commands/orchestrate.js +5 -2
  150. package/src/commands/video.js +5 -1
  151. package/src/lib/cowork-workflow.js +357 -136
  152. package/src/lib/micro-compact.js +52 -0
  153. package/src/lib/permission-rules.cjs +39 -0
  154. package/src/lib/skill-loader.js +62 -43
  155. package/src/repl/agent-repl.js +228 -20
  156. package/src/repl/chat-repl.js +4 -2
  157. package/src/repl/permission-tier.js +60 -0
  158. package/src/repl/stream-decision.js +16 -0
  159. package/src/repl/think-command.js +36 -0
  160. package/src/runtime/agent-core.js +42 -6
  161. package/src/runtime/file-ref-expander.js +209 -18
  162. package/src/runtime/headless-runner.js +3 -3
  163. package/src/runtime/headless-stream.js +16 -3
  164. package/src/assets/web-panel/assets/Terminal-BfgM0oyN.js +0 -3
  165. package/src/assets/web-panel/assets/devWarning-gNvbyy4j.js +0 -1
  166. package/src/assets/web-panel/assets/index-BNXpMnIY.js +0 -1
  167. package/src/assets/web-panel/assets/index-gfHxqT1Q.js +0 -1
@@ -78,6 +78,10 @@ export const LAYER_NAMES = [
78
78
  "workspace",
79
79
  ];
80
80
 
81
+ /** Max directory depth to descend when scanning a skill layer for nested
82
+ * `<group>/<skill>/SKILL.md` layouts (infinite-recursion / deep-tree guard). */
83
+ export const MAX_SKILL_NEST_DEPTH = 5;
84
+
81
85
  /**
82
86
  * Simple YAML frontmatter parser (no dependencies)
83
87
  * Shared utility extracted from skill.js
@@ -348,54 +352,69 @@ export class CLISkillLoader {
348
352
  */
349
353
  _loadFromDir(dir, layer) {
350
354
  const skills = [];
351
- if (!dir || !fs.existsSync(dir)) return skills;
355
+ this._collectSkills(dir, layer, skills, 0);
356
+ return skills;
357
+ }
352
358
 
359
+ /**
360
+ * Recursively collect skills under `dir`. A directory containing a SKILL.md
361
+ * is a skill (a leaf — we do NOT descend into its asset/script subdirs); a
362
+ * directory WITHOUT one is treated as a grouping/category folder and is
363
+ * descended into, so nested layouts like `.claude/skills/<group>/<skill>/`
364
+ * load too (Claude-Code 2.1.178 nested-skills parity). Depth-capped.
365
+ */
366
+ _collectSkills(dir, layer, out, depth) {
367
+ if (!dir || !fs.existsSync(dir)) return;
368
+ let entries;
353
369
  try {
354
- const dirs = fs.readdirSync(dir, { withFileTypes: true });
355
- for (const entry of dirs) {
356
- if (!entry.isDirectory()) continue;
357
-
358
- const skillMd = path.join(dir, entry.name, "SKILL.md");
359
- if (!fs.existsSync(skillMd)) continue;
360
-
361
- try {
362
- const content = fs.readFileSync(skillMd, "utf-8");
363
- const { data, body } = parseSkillMd(content);
364
-
365
- skills.push({
366
- id: data.name || entry.name,
367
- displayName: data.displayName || entry.name,
368
- description: data.description || "",
369
- version: data.version || "1.0.0",
370
- category: data.category || "uncategorized",
371
- activation: data.activation || "manual",
372
- tags: data.tags || [],
373
- userInvocable: data.userInvocable !== false,
374
- handler: data.handler || null,
375
- capabilities: data.capabilities || [],
376
- os: data.os || [],
377
- // CLI pack extended fields
378
- executionMode: data.executionMode || null,
379
- cliDomain: data.cliDomain || null,
380
- cliVersionHash: data.cliVersionHash || null,
381
- dirName: entry.name,
382
- hasHandler: fs.existsSync(path.join(dir, entry.name, "handler.js")),
383
- body,
384
- // Skill-Embedded MCP: inline server declarations in a
385
- // ```mcp-servers fenced code block. Empty array if absent.
386
- mcpServers: parseSkillMcpServers(body),
387
- source: layer,
388
- skillDir: path.join(dir, entry.name),
389
- });
390
- } catch {
391
- // Skip malformed skill files
370
+ entries = fs.readdirSync(dir, { withFileTypes: true });
371
+ } catch {
372
+ return; // Directory unreadable
373
+ }
374
+ for (const entry of entries) {
375
+ if (!entry.isDirectory()) continue;
376
+ const skillDir = path.join(dir, entry.name);
377
+ const skillMd = path.join(skillDir, "SKILL.md");
378
+ if (!fs.existsSync(skillMd)) {
379
+ // Grouping folder descend (bounded) to find nested skills.
380
+ if (depth < MAX_SKILL_NEST_DEPTH) {
381
+ this._collectSkills(skillDir, layer, out, depth + 1);
392
382
  }
383
+ continue;
384
+ }
385
+ try {
386
+ const content = fs.readFileSync(skillMd, "utf-8");
387
+ const { data, body } = parseSkillMd(content);
388
+
389
+ out.push({
390
+ id: data.name || entry.name,
391
+ displayName: data.displayName || entry.name,
392
+ description: data.description || "",
393
+ version: data.version || "1.0.0",
394
+ category: data.category || "uncategorized",
395
+ activation: data.activation || "manual",
396
+ tags: data.tags || [],
397
+ userInvocable: data.userInvocable !== false,
398
+ handler: data.handler || null,
399
+ capabilities: data.capabilities || [],
400
+ os: data.os || [],
401
+ // CLI pack extended fields
402
+ executionMode: data.executionMode || null,
403
+ cliDomain: data.cliDomain || null,
404
+ cliVersionHash: data.cliVersionHash || null,
405
+ dirName: entry.name,
406
+ hasHandler: fs.existsSync(path.join(skillDir, "handler.js")),
407
+ body,
408
+ // Skill-Embedded MCP: inline server declarations in a
409
+ // ```mcp-servers fenced code block. Empty array if absent.
410
+ mcpServers: parseSkillMcpServers(body),
411
+ source: layer,
412
+ skillDir,
413
+ });
414
+ } catch {
415
+ // Skip malformed skill files
393
416
  }
394
- } catch {
395
- // Directory unreadable
396
417
  }
397
-
398
- return skills;
399
418
  }
400
419
 
401
420
  /**
@@ -79,7 +79,7 @@ import {
79
79
  listBackgroundShellTasks,
80
80
  } from "../runtime/agent-core.js";
81
81
  import { formatBackgroundTasks } from "./tasks-status.js";
82
- import { expandFileRefs } from "../runtime/file-ref-expander.js";
82
+ import { expandFileRefsAsync } from "../runtime/file-ref-expander.js";
83
83
  import { composeSystemPrompt } from "../runtime/system-prompt.js";
84
84
  import {
85
85
  makeFallbackChatFn,
@@ -88,6 +88,13 @@ import {
88
88
  import { resolveSlashMacro } from "./slash-macro.js";
89
89
  import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
90
90
  import { newCostStore, addUsage } from "./session-cost.js";
91
+ import { parseThinkCommand } from "./think-command.js";
92
+ import { shouldStreamLive } from "./stream-decision.js";
93
+ import {
94
+ parsePermissionTier,
95
+ describeTier,
96
+ nextTier,
97
+ } from "./permission-tier.js";
91
98
 
92
99
  /**
93
100
  * Reference to the runtime DB for hook execution (set during startAgentRepl)
@@ -230,6 +237,13 @@ async function agentLoop(messages, options) {
230
237
  } else if (event.result?.success) {
231
238
  process.stdout.write(chalk.green(` Done\n`));
232
239
  }
240
+ } else if (event.type === "thinking") {
241
+ // Intermediate-step reasoning (before a tool call) — dimmed, inline.
242
+ if (process.env.CC_REPL_THINKING !== "0" && event.text) {
243
+ process.stdout.write(
244
+ "\n" + chalk.dim("💭 " + event.text.replace(/\n/g, "\n ")) + "\n",
245
+ );
246
+ }
233
247
  } else if (event.type === "token-usage") {
234
248
  usageEvents.push(event);
235
249
  } else if (event.type === "iteration-warning") {
@@ -239,7 +253,7 @@ async function agentLoop(messages, options) {
239
253
  chalk.red(`\n [Budget Exhausted] ${event.budget}\n`),
240
254
  );
241
255
  } else if (event.type === "response-complete") {
242
- return { content: event.content, usageEvents };
256
+ return { content: event.content, usageEvents, thinking: event.thinking };
243
257
  }
244
258
  }
245
259
  return { content: "", usageEvents };
@@ -252,10 +266,16 @@ export async function startAgentRepl(options = {}) {
252
266
  let model = options.model || "qwen2.5:7b";
253
267
  let provider = options.provider || "ollama";
254
268
  // Extended thinking (Anthropic; opt-in via --think/--ultrathink). Carried from
255
- // the runtime policy into the agent-loop options below. thinkingBudget
269
+ // the runtime policy into the agent-loop options below. Mutable so the
270
+ // `/think` · `/ultrathink` slash commands can toggle it mid-session (the
271
+ // per-turn agentLoop call below reads the current value). thinkingBudget
256
272
  // (--thinking-budget) is the companion legacy-model budget_tokens override.
257
- const thinking = options.thinking || null;
273
+ let thinking = options.thinking || null;
258
274
  const thinkingBudget = options.thinkingBudget || null;
275
+ // Current ApprovalGate session tier (strict|trusted|autopilot), mirrored here
276
+ // so Shift+Tab can cycle it and `/permissions <tier>` can set it. Kept in sync
277
+ // with _approvalGate.setSessionPolicy below.
278
+ let _sessionTier = "strict";
259
279
  const baseUrl = options.baseUrl || "http://localhost:11434";
260
280
  const apiKey = options.apiKey || null;
261
281
  // Extra workspace roots (--add-dir): advertised in the system prompt and
@@ -325,6 +345,30 @@ export async function startAgentRepl(options = {}) {
325
345
  // Set hook DB reference for tool pipeline
326
346
  _hookDb = db;
327
347
 
348
+ // Live token streaming (Claude-Code parity): stream the answer (and reasoning)
349
+ // token-by-token as the LLM produces it, instead of replaying the finished
350
+ // text with a typewriter. ONLY safe when no AssistantResponse hook is
351
+ // registered — such a hook can rewrite/suppress the final answer, which is
352
+ // impossible once it's already on screen. CC_REPL_STREAM=0 forces the replay.
353
+ let _arHookCount = 0;
354
+ if (_hookDb) {
355
+ try {
356
+ const { listHooks } = await import("../lib/hook-manager.js");
357
+ _arHookCount = (
358
+ listHooks(_hookDb, {
359
+ event: "AssistantResponse",
360
+ enabledOnly: true,
361
+ }) || []
362
+ ).length;
363
+ } catch {
364
+ _arHookCount = -1; // unknown → shouldStreamLive treats as unsafe
365
+ }
366
+ }
367
+ const _streamLive = shouldStreamLive({
368
+ streamEnv: process.env.CC_REPL_STREAM,
369
+ arHookCount: _arHookCount,
370
+ });
371
+
328
372
  // Wire the persistent ApprovalGate singleton (approval-policies.json) with
329
373
  // a readline confirm prompt. agent-core's run_shell branch gates
330
374
  // MEDIUM/HIGH-risk commands against the session's policy tier
@@ -698,6 +742,9 @@ export async function startAgentRepl(options = {}) {
698
742
  sessionId,
699
743
  _bundleResolved.approvalPolicy.default,
700
744
  );
745
+ // Mirror it so Shift+Tab cycling starts from the real tier.
746
+ const applied = parsePermissionTier(_bundleResolved.approvalPolicy.default);
747
+ if (applied) _sessionTier = applied;
701
748
  } catch (_err) {
702
749
  // Non-critical — invalid policy value is silently ignored
703
750
  }
@@ -848,6 +895,7 @@ export async function startAgentRepl(options = {}) {
848
895
  "/ide",
849
896
  "/mcp",
850
897
  "/memory",
898
+ "/microcompact",
851
899
  "/model",
852
900
  "/output-style",
853
901
  "/permissions",
@@ -869,6 +917,8 @@ export async function startAgentRepl(options = {}) {
869
917
  "/tasks",
870
918
  "/terminal-setup",
871
919
  "/theme",
920
+ "/think",
921
+ "/ultrathink",
872
922
  "/vim",
873
923
  ],
874
924
  getIdeOpenFiles: async () => {
@@ -967,6 +1017,38 @@ export async function startAgentRepl(options = {}) {
967
1017
  return;
968
1018
  }
969
1019
 
1020
+ // 1.5) Shift+Tab cycles the session approval tier (Claude-Code mode
1021
+ // cycling): strict → trusted → autopilot → strict. Drives the existing
1022
+ // ApprovalGate.setSessionPolicy seam; intercepted before vim/completion.
1023
+ const isShiftTab =
1024
+ (k.name === "tab" && k.shift) || k.sequence === "\u001b[Z";
1025
+ if (isShiftTab) {
1026
+ if (
1027
+ _approvalGate &&
1028
+ sessionId &&
1029
+ typeof _approvalGate.setSessionPolicy === "function"
1030
+ ) {
1031
+ const next = nextTier(_sessionTier);
1032
+ try {
1033
+ _approvalGate.setSessionPolicy(sessionId, next);
1034
+ _sessionTier = next;
1035
+ process.stdout.write(
1036
+ "\n" +
1037
+ chalk.cyan(`⇥ approval: ${next}`) +
1038
+ " " +
1039
+ chalk.gray(`(${describeTier(next)})`) +
1040
+ "\n",
1041
+ );
1042
+ if (!_turnAbort) prompt();
1043
+ } catch {
1044
+ process.stdout.write("\x07"); // bell on failure
1045
+ }
1046
+ } else {
1047
+ process.stdout.write("\x07"); // no gate this session
1048
+ }
1049
+ return;
1050
+ }
1051
+
970
1052
  // 2) Vim mode: modal editing on the current input line.
971
1053
  if (_vimEnabled && !_turnAbort) {
972
1054
  if (!_vim) {
@@ -1208,6 +1290,9 @@ export async function startAgentRepl(options = {}) {
1208
1290
  ` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
1209
1291
  );
1210
1292
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
1293
+ logger.log(
1294
+ ` ${chalk.cyan("/think")} Extended thinking on/off (/think [on|off|ultra]; /ultrathink = max; Anthropic)`,
1295
+ );
1211
1296
  logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
1212
1297
  logger.log(
1213
1298
  ` ${chalk.cyan("/vim")} Toggle vim-mode line editing (/vim [on|off]; Esc → NORMAL)`,
@@ -1240,7 +1325,7 @@ export async function startAgentRepl(options = {}) {
1240
1325
  ` ${chalk.cyan("/cost")} Session token spend + estimated $ (per model & category)`,
1241
1326
  );
1242
1327
  logger.log(
1243
- ` ${chalk.cyan("/permissions")} Allow/ask/deny rules in effect this session`,
1328
+ ` ${chalk.cyan("/permissions")} Allow/ask/deny rules; set/cycle tier (/permissions <tier> · Shift+Tab cycles)`,
1244
1329
  );
1245
1330
  logger.log(
1246
1331
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
@@ -1260,6 +1345,9 @@ export async function startAgentRepl(options = {}) {
1260
1345
  logger.log(
1261
1346
  ` ${chalk.cyan("/compact")} Smart compact (importance-based)`,
1262
1347
  );
1348
+ logger.log(
1349
+ ` ${chalk.cyan("/microcompact")} Trim large OLD tool results in place (keeps recent + flow)`,
1350
+ );
1263
1351
  logger.log(
1264
1352
  ` ${chalk.cyan("/task")} Set task objective (/task <objective>)`,
1265
1353
  );
@@ -1454,6 +1542,21 @@ export async function startAgentRepl(options = {}) {
1454
1542
  return;
1455
1543
  }
1456
1544
 
1545
+ // Extended-thinking toggle (Anthropic extended thinking; ignored by other
1546
+ // providers). Mutates `thinking`, read by the next turn's agentLoop call.
1547
+ {
1548
+ const think = parseThinkCommand(trimmed);
1549
+ if (think) {
1550
+ thinking = think.thinking;
1551
+ const note = think.anthropic
1552
+ ? " " + chalk.gray("(Anthropic only; applies next turn)")
1553
+ : "";
1554
+ logger.info(`Extended thinking: ${chalk.cyan(think.label)}${note}`);
1555
+ prompt();
1556
+ return;
1557
+ }
1558
+ }
1559
+
1457
1560
  if (trimmed === "/clear") {
1458
1561
  messages.length = 1; // Keep system prompt
1459
1562
  _checkpointMarks.length = 0; // checkpoint marks no longer map to anything
@@ -1780,6 +1883,27 @@ export async function startAgentRepl(options = {}) {
1780
1883
  return;
1781
1884
  }
1782
1885
 
1886
+ // Micro-compaction: surgically trim large OLD tool results in place (keeps
1887
+ // recent messages + the conversation flow). Safe (never orphans a tool
1888
+ // pair); cheaper + less lossy than a full /compact.
1889
+ if (trimmed === "/microcompact") {
1890
+ const { microCompact } = await import("../lib/micro-compact.js");
1891
+ const { messages: mc, stats } = microCompact(messages);
1892
+ if (stats.trimmed > 0) {
1893
+ messages.length = 0;
1894
+ messages.push(...mc);
1895
+ logger.info(
1896
+ `Micro-compacted: trimmed ${stats.trimmed} old tool result(s), ~${stats.saved} chars freed (recent messages kept).`,
1897
+ );
1898
+ } else {
1899
+ logger.info(
1900
+ "Nothing to micro-compact — no large old tool results in context.",
1901
+ );
1902
+ }
1903
+ prompt();
1904
+ return;
1905
+ }
1906
+
1783
1907
  // Task commands
1784
1908
  if (trimmed.startsWith("/task")) {
1785
1909
  const taskArg = trimmed.slice(5).trim();
@@ -2646,7 +2770,39 @@ export async function startAgentRepl(options = {}) {
2646
2770
 
2647
2771
  // `/permissions` — allow/ask/deny rules in effect this session (Claude-Code
2648
2772
  // parity): what the agent runs unprompted, asks about, or is blocked from.
2649
- if (trimmed === "/permissions" || trimmed === "/permissions ") {
2773
+ if (trimmed === "/permissions" || trimmed.startsWith("/permissions ")) {
2774
+ const arg = trimmed.slice("/permissions".length).trim();
2775
+ if (arg) {
2776
+ // Set this session's approval tier mid-session (Claude-Code
2777
+ // permission-mode / Shift+Tab parity; mirrors `cc session policy --set`).
2778
+ const tier = parsePermissionTier(arg);
2779
+ if (!tier) {
2780
+ logger.info(
2781
+ "Usage: /permissions [strict|trusted|autopilot] " +
2782
+ "(aliases: default · accept-edits · bypass). No arg = show rules.",
2783
+ );
2784
+ } else if (
2785
+ !_approvalGate ||
2786
+ !sessionId ||
2787
+ typeof _approvalGate.setSessionPolicy !== "function"
2788
+ ) {
2789
+ logger.info(
2790
+ "Approval gate not available this session — can't change the tier.",
2791
+ );
2792
+ } else {
2793
+ try {
2794
+ _approvalGate.setSessionPolicy(sessionId, tier);
2795
+ _sessionTier = tier;
2796
+ logger.info(
2797
+ `Approval policy → ${chalk.cyan(tier)} ${chalk.gray(`(${describeTier(tier)})`)}`,
2798
+ );
2799
+ } catch (_err) {
2800
+ logger.info("Could not set the approval policy.");
2801
+ }
2802
+ }
2803
+ prompt();
2804
+ return;
2805
+ }
2650
2806
  let files = [];
2651
2807
  try {
2652
2808
  const { loadSettings } = await import("../lib/settings-loader.cjs");
@@ -2656,6 +2812,11 @@ export async function startAgentRepl(options = {}) {
2656
2812
  }
2657
2813
  const { renderPermissions } = await import("./permissions-status.js");
2658
2814
  logger.log(renderPermissions(_permissionRules, { files }));
2815
+ logger.log(
2816
+ chalk.gray(
2817
+ " Set tier mid-session: /permissions <strict|trusted|autopilot>",
2818
+ ),
2819
+ );
2659
2820
  prompt();
2660
2821
  return;
2661
2822
  }
@@ -2821,7 +2982,9 @@ export async function startAgentRepl(options = {}) {
2821
2982
  // about and left as-is.
2822
2983
  let userContent = effectivePrompt;
2823
2984
  try {
2824
- const fileRefs = expandFileRefs(effectivePrompt, { cwd: process.cwd() });
2985
+ const fileRefs = await expandFileRefsAsync(effectivePrompt, {
2986
+ cwd: process.cwd(),
2987
+ });
2825
2988
  userContent = fileRefs.prompt;
2826
2989
  for (const w of fileRefs.warnings) {
2827
2990
  logger.info(chalk.yellow(`[@ref] ${w}`));
@@ -2951,7 +3114,35 @@ export async function startAgentRepl(options = {}) {
2951
3114
  /* goal binding is best-effort — fall back to defaultPrepareCall */
2952
3115
  }
2953
3116
  _turnAbort = new AbortController();
2954
- const { content: response, usageEvents } = await agentLoop(messages, {
3117
+ // Live streaming hooks: write the answer token-by-token, and stream the
3118
+ // reasoning dimmed before it. Skipped (left undefined) in replay mode.
3119
+ let _liveStreamed = false;
3120
+ let _liveThinkStarted = false;
3121
+ const liveOpts = _streamLive
3122
+ ? {
3123
+ onToken: (t) => {
3124
+ // Separate the answer from the dimmed reasoning above it (once).
3125
+ if (!_liveStreamed && _liveThinkStarted) process.stdout.write("\n");
3126
+ _liveStreamed = true;
3127
+ process.stdout.write(t);
3128
+ },
3129
+ onThinking: (t) => {
3130
+ if (process.env.CC_REPL_THINKING === "0") return;
3131
+ if (!_liveThinkStarted) {
3132
+ process.stdout.write(chalk.dim("💭 "));
3133
+ _liveThinkStarted = true;
3134
+ }
3135
+ process.stdout.write(chalk.dim(t));
3136
+ },
3137
+ }
3138
+ : {};
3139
+ if (_streamLive) process.stdout.write("\n");
3140
+ const {
3141
+ content: response,
3142
+ usageEvents,
3143
+ thinking: reasoning,
3144
+ } = await agentLoop(messages, {
3145
+ ...liveOpts,
2955
3146
  signal: _turnAbort.signal,
2956
3147
  provider,
2957
3148
  model: activeModel,
@@ -3032,19 +3223,36 @@ export async function startAgentRepl(options = {}) {
3032
3223
  effectiveResponse = responseDirective.response;
3033
3224
  }
3034
3225
 
3226
+ // Extended-thinking reasoning (Anthropic, when /think is on): shown dimmed
3227
+ // BEFORE the answer. Not subject to the AssistantResponse rewrite/suppress
3228
+ // hook (that governs the answer text only). CC_REPL_THINKING=0 hides it.
3229
+ // In live mode it already streamed via onThinking, so skip the replay.
3230
+ if (reasoning && !_streamLive && process.env.CC_REPL_THINKING !== "0") {
3231
+ process.stdout.write(
3232
+ "\n" + chalk.dim("💭 " + reasoning.replace(/\n/g, "\n ")) + "\n",
3233
+ );
3234
+ }
3235
+
3035
3236
  if (effectiveResponse) {
3036
- // Phase G #2 — route through StreamRouter so REPL / WS / future
3037
- // streaming providers share one StreamEvent protocol.
3038
- const { streamAgentResponse } = await import("../lib/agent-stream.js");
3039
- process.stdout.write("\n");
3040
- const noStream = options.noStream === true;
3041
- const streamResult = await streamAgentResponse(effectiveResponse, {
3042
- noStream,
3043
- writer: noStream ? null : (chunk) => process.stdout.write(chunk),
3044
- });
3045
- if (noStream) process.stdout.write(streamResult.text);
3046
- process.stdout.write("\n\n");
3047
- messages.push({ role: "assistant", content: streamResult.text });
3237
+ if (_streamLive && _liveStreamed) {
3238
+ // Already streamed live token-by-token during the turn (no
3239
+ // AssistantResponse hook to rewrite it) — just terminate + record.
3240
+ process.stdout.write("\n\n");
3241
+ messages.push({ role: "assistant", content: effectiveResponse });
3242
+ } else {
3243
+ // Phase G #2 — route through StreamRouter so REPL / WS / future
3244
+ // streaming providers share one StreamEvent protocol.
3245
+ const { streamAgentResponse } = await import("../lib/agent-stream.js");
3246
+ process.stdout.write("\n");
3247
+ const noStream = options.noStream === true;
3248
+ const streamResult = await streamAgentResponse(effectiveResponse, {
3249
+ noStream,
3250
+ writer: noStream ? null : (chunk) => process.stdout.write(chunk),
3251
+ });
3252
+ if (noStream) process.stdout.write(streamResult.text);
3253
+ process.stdout.write("\n\n");
3254
+ messages.push({ role: "assistant", content: streamResult.text });
3255
+ }
3048
3256
  } else if (!responseDirective.suppress) {
3049
3257
  process.stdout.write("\n");
3050
3258
  }
@@ -12,7 +12,7 @@ import readline from "readline";
12
12
  import chalk from "chalk";
13
13
  import { logger } from "../lib/logger.js";
14
14
  import { BUILT_IN_PROVIDERS } from "../lib/llm-providers.js";
15
- import { expandFileRefs } from "../runtime/file-ref-expander.js";
15
+ import { expandFileRefsAsync } from "../runtime/file-ref-expander.js";
16
16
  import {
17
17
  streamOllama,
18
18
  streamOpenAI,
@@ -159,7 +159,9 @@ export async function startChatRepl(options = {}) {
159
159
  // content; the JSONL log keeps the original line for readability.
160
160
  let userContent = trimmed;
161
161
  try {
162
- const fileRefs = expandFileRefs(trimmed, { cwd: process.cwd() });
162
+ const fileRefs = await expandFileRefsAsync(trimmed, {
163
+ cwd: process.cwd(),
164
+ });
163
165
  userContent = fileRefs.prompt;
164
166
  for (const w of fileRefs.warnings) {
165
167
  logger.info(chalk.yellow(`[@ref] ${w}`));
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Map a `/permissions <arg>` tier alias to an ApprovalGate session-policy tier
3
+ * (strict | trusted | autopilot), or null when the arg is not a known tier.
4
+ * Pure → unit-testable. Mirrors `cc session policy --set` and the headless
5
+ * --permission-mode mapping (default → strict, acceptEdits → trusted,
6
+ * bypassPermissions → autopilot), giving the interactive REPL a mid-session
7
+ * permission-mode toggle (Claude-Code Shift+Tab mode-cycling parity).
8
+ */
9
+ const TIER_ALIASES = {
10
+ // strict — every risky action asks (the default)
11
+ strict: "strict",
12
+ default: "strict",
13
+ normal: "strict",
14
+ off: "strict",
15
+ // trusted — low/medium-risk auto-approved, high-risk still asks (acceptEdits)
16
+ trusted: "trusted",
17
+ accept: "trusted",
18
+ "accept-edits": "trusted",
19
+ acceptedits: "trusted",
20
+ // autopilot — everything auto-approved (bypassPermissions)
21
+ autopilot: "autopilot",
22
+ bypass: "autopilot",
23
+ bypasspermissions: "autopilot",
24
+ yolo: "autopilot",
25
+ };
26
+
27
+ /** Cycle order for Shift+Tab mode cycling (Claude-Code parity). */
28
+ export const TIER_CYCLE = Object.freeze(["strict", "trusted", "autopilot"]);
29
+
30
+ /**
31
+ * Next tier in the Shift+Tab cycle: strict → trusted → autopilot → strict.
32
+ * An unrecognized current tier resets to the first (strict).
33
+ */
34
+ export function nextTier(current) {
35
+ const i = TIER_CYCLE.indexOf(current);
36
+ return TIER_CYCLE[(i + 1) % TIER_CYCLE.length];
37
+ }
38
+
39
+ export function parsePermissionTier(arg) {
40
+ const a = String(arg == null ? "" : arg)
41
+ .trim()
42
+ .toLowerCase();
43
+ return Object.prototype.hasOwnProperty.call(TIER_ALIASES, a)
44
+ ? TIER_ALIASES[a]
45
+ : null;
46
+ }
47
+
48
+ /** One-line description of what a tier auto-approves. */
49
+ export function describeTier(tier) {
50
+ switch (tier) {
51
+ case "autopilot":
52
+ return "everything auto-approved (no prompts)";
53
+ case "trusted":
54
+ return "low/medium-risk auto-approved; high-risk still asks";
55
+ case "strict":
56
+ return "every risky action asks";
57
+ default:
58
+ return "";
59
+ }
60
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Decide whether the REPL may stream the answer live (token-by-token) instead of
3
+ * replaying the finished text. Pure → unit-testable.
4
+ *
5
+ * Safety invariant: live streaming is ONLY allowed when no AssistantResponse
6
+ * hook is registered. Such a hook can rewrite or suppress the final answer, and
7
+ * once tokens are on screen they can't be un-printed. If we can't determine the
8
+ * hook count (a query error → pass arHookCount < 0), we stay safe (no streaming).
9
+ * `CC_REPL_STREAM=0` forces the replay regardless.
10
+ */
11
+ export function shouldStreamLive({ streamEnv, arHookCount = 0 } = {}) {
12
+ if (streamEnv === "0") return false;
13
+ // 0 → no rewrite/suppress hook → safe. Anything else (hooks present, or -1 for
14
+ // "unknown") → fall back to the replay.
15
+ return arHookCount === 0;
16
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pure parser for the REPL `/think` · `/ultrathink` extended-thinking toggle.
3
+ * Maps the slash-command string to the next `thinking` value the agent loop
4
+ * reads (true | "ultra" | <level> | null) plus a human label. Returns null when
5
+ * the input is not a think command, so the REPL falls through to its other
6
+ * handlers. vscode/readline-free → unit-testable (mirrors the chat panel's
7
+ * /think; extended thinking is Anthropic-only, ignored by other providers).
8
+ *
9
+ * /think → on (default budget) /think off → off
10
+ * /think ultra → max budget /think <level> → that level
11
+ * /think-off → off (panel-style alias)
12
+ * /ultrathink → max budget (alias)
13
+ */
14
+ export function parseThinkCommand(trimmed) {
15
+ const t = String(trimmed == null ? "" : trimmed).trim();
16
+ const isThink =
17
+ t === "/think" || t.startsWith("/think ") || t.startsWith("/think-");
18
+ const isUltra = t === "/ultrathink" || t.startsWith("/ultrathink ");
19
+ if (!isThink && !isUltra) return null;
20
+
21
+ const arg = isUltra
22
+ ? "ultra"
23
+ : t.slice(6).replace(/^-+/, "").trim().toLowerCase();
24
+
25
+ if (arg === "ultra") {
26
+ return { thinking: "ultra", label: "ultra (max budget)", anthropic: true };
27
+ }
28
+ if (arg === "off" || arg === "false" || arg === "0" || arg === "none") {
29
+ return { thinking: null, label: "off", anthropic: false };
30
+ }
31
+ if (!arg || arg === "on" || arg === "true") {
32
+ return { thinking: true, label: "on", anthropic: true };
33
+ }
34
+ // An explicit effort level (low/medium/high/…) — passed through to the engine.
35
+ return { thinking: arg, label: arg, anthropic: true };
36
+ }