@duckmind/dm-darwin-x64 0.43.4 → 0.48.5

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 (254) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +242 -50
  3. package/extensions/dm-9router-ext/package.json +2 -19
  4. package/extensions/dm-9router-ext/src/index.js +5 -0
  5. package/extensions/dm-ask-user/index.js +10 -0
  6. package/extensions/dm-ask-user/package.json +1 -21
  7. package/extensions/dm-cli-anything/package.json +0 -6
  8. package/extensions/dm-context/package.json +2 -12
  9. package/extensions/dm-context/src/context.js +1 -0
  10. package/extensions/dm-context/src/index.js +7 -0
  11. package/extensions/dm-context/src/utils.js +1 -0
  12. package/extensions/dm-cua/package.json +0 -7
  13. package/extensions/dm-fff/package.json +1 -28
  14. package/extensions/dm-fff/src/index.js +15 -0
  15. package/extensions/dm-fff/src/query.js +1 -0
  16. package/extensions/dm-goal/package.json +2 -31
  17. package/extensions/dm-goal/src/goal.js +43 -0
  18. package/extensions/dm-grill-me/index.js +174 -0
  19. package/extensions/dm-grill-me/package.json +1 -26
  20. package/extensions/dm-image2/package.json +0 -7
  21. package/extensions/dm-subagents/package.json +1 -32
  22. package/extensions/dm-subagents/src/agents/agent-management.js +37 -0
  23. package/extensions/dm-subagents/src/agents/agent-memory.js +6 -0
  24. package/extensions/dm-subagents/src/agents/agent-scope.js +1 -0
  25. package/extensions/dm-subagents/src/agents/agent-selection.js +1 -0
  26. package/extensions/dm-subagents/src/agents/agent-serializer.js +7 -0
  27. package/extensions/dm-subagents/src/agents/agents.js +11 -0
  28. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -0
  29. package/extensions/dm-subagents/src/agents/frontmatter.js +6 -0
  30. package/extensions/dm-subagents/src/agents/identity.js +1 -0
  31. package/extensions/dm-subagents/src/agents/proactive-skills.js +1 -0
  32. package/extensions/dm-subagents/src/agents/skills.js +6 -0
  33. package/extensions/dm-subagents/src/extension/config.js +2 -0
  34. package/extensions/dm-subagents/src/extension/control-notices.js +4 -0
  35. package/extensions/dm-subagents/src/extension/doctor.js +16 -0
  36. package/extensions/dm-subagents/src/extension/fanout-child.js +231 -0
  37. package/extensions/dm-subagents/src/extension/index.js +367 -0
  38. package/extensions/dm-subagents/src/extension/rpc.js +8 -0
  39. package/extensions/dm-subagents/src/extension/schemas.js +1 -0
  40. package/extensions/dm-subagents/src/extension/{tool-description.ts → tool-description.js} +6 -126
  41. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +13 -0
  42. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +5 -0
  43. package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -0
  44. package/extensions/dm-subagents/src/profiles/profiles.js +3 -0
  45. package/extensions/dm-subagents/src/runs/background/async-execution.js +46 -0
  46. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +14 -0
  47. package/extensions/dm-subagents/src/runs/background/async-resume.js +8 -0
  48. package/extensions/dm-subagents/src/runs/background/async-status.js +12 -0
  49. package/extensions/dm-subagents/src/runs/background/chain-append.js +2 -0
  50. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +1 -0
  51. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +1 -0
  52. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +1 -0
  53. package/extensions/dm-subagents/src/runs/background/control-channel.js +1 -0
  54. package/extensions/dm-subagents/src/runs/background/fleet-view.js +17 -0
  55. package/extensions/dm-subagents/src/runs/background/notify.js +3 -0
  56. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +1 -0
  57. package/extensions/dm-subagents/src/runs/background/result-watcher.js +8 -0
  58. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +4 -0
  59. package/extensions/dm-subagents/src/runs/background/run-status.js +23 -0
  60. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +4 -0
  61. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +11 -0
  62. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +78 -0
  63. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -0
  64. package/extensions/dm-subagents/src/runs/background/wait.js +11 -0
  65. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +12 -0
  66. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +102 -0
  67. package/extensions/dm-subagents/src/runs/foreground/execution.js +51 -0
  68. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +228 -0
  69. package/extensions/dm-subagents/src/runs/shared/acceptance.js +3 -0
  70. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +1 -0
  71. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +3 -0
  72. package/extensions/dm-subagents/src/runs/shared/dm-args.js +1 -0
  73. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +1 -0
  74. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +1 -0
  75. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +1 -0
  76. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +1 -0
  77. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +1 -0
  78. package/extensions/dm-subagents/src/runs/shared/model-scope.js +1 -0
  79. package/extensions/dm-subagents/src/runs/shared/nested-events.js +8 -0
  80. package/extensions/dm-subagents/src/runs/shared/nested-path.js +1 -0
  81. package/extensions/dm-subagents/src/runs/shared/nested-render.js +1 -0
  82. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +5 -0
  83. package/extensions/dm-subagents/src/runs/shared/run-history.js +5 -0
  84. package/extensions/dm-subagents/src/runs/shared/single-output.js +14 -0
  85. package/extensions/dm-subagents/src/runs/shared/structured-output.js +1 -0
  86. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +5 -0
  87. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +22 -0
  88. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +1 -0
  89. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +7 -0
  90. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +1 -0
  91. package/extensions/dm-subagents/src/runs/shared/worktree.js +3 -0
  92. package/extensions/dm-subagents/src/shared/artifacts.js +2 -0
  93. package/extensions/dm-subagents/src/shared/atomic-json.js +1 -0
  94. package/extensions/dm-subagents/src/shared/child-transcript.js +5 -0
  95. package/extensions/dm-subagents/src/shared/file-coalescer.js +1 -0
  96. package/extensions/dm-subagents/src/shared/fork-context.js +6 -0
  97. package/extensions/dm-subagents/src/shared/formatters.js +9 -0
  98. package/extensions/dm-subagents/src/shared/jsonl-writer.js +2 -0
  99. package/extensions/dm-subagents/src/shared/model-info.js +1 -0
  100. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +1 -0
  101. package/extensions/dm-subagents/src/shared/session-identity.js +1 -0
  102. package/extensions/dm-subagents/src/shared/session-tokens.js +2 -0
  103. package/extensions/dm-subagents/src/shared/settings.js +23 -0
  104. package/extensions/dm-subagents/src/shared/status-format.js +1 -0
  105. package/extensions/dm-subagents/src/shared/types.js +8 -0
  106. package/extensions/dm-subagents/src/shared/utils.js +2 -0
  107. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +1 -0
  108. package/extensions/dm-subagents/src/slash/prompt-workflows.js +7 -0
  109. package/extensions/dm-subagents/src/slash/slash-bridge.js +1 -0
  110. package/extensions/dm-subagents/src/slash/slash-commands.js +38 -0
  111. package/extensions/dm-subagents/src/slash/slash-live-state.js +6 -0
  112. package/extensions/dm-subagents/src/tui/render-helpers.js +1 -0
  113. package/extensions/dm-subagents/src/tui/render.js +4 -0
  114. package/extensions/dm-tasks/package.json +1 -30
  115. package/extensions/dm-tasks/src/auto-clear.js +1 -0
  116. package/extensions/dm-tasks/src/index.js +212 -0
  117. package/extensions/dm-tasks/src/process-tracker.js +1 -0
  118. package/extensions/dm-tasks/src/task-store.js +1 -0
  119. package/extensions/dm-tasks/src/tasks-config.js +1 -0
  120. package/extensions/dm-tasks/src/types.js +0 -0
  121. package/extensions/dm-tasks/src/ui/settings-menu.js +1 -0
  122. package/extensions/dm-tasks/src/ui/task-widget.js +1 -0
  123. package/extensions/dm-usage/index.js +9 -0
  124. package/extensions/dm-usage/package.json +1 -18
  125. package/extensions/greedysearch-dm/bin/search.mjs +51 -51
  126. package/extensions/greedysearch-dm/index.js +20 -0
  127. package/extensions/greedysearch-dm/package.json +1 -22
  128. package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
  129. package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
  130. package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
  131. package/extensions/greedysearch-dm/src/search/research.mjs +59 -59
  132. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
  133. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  134. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  135. package/extensions/greedysearch-dm/src/types.js +0 -0
  136. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  137. package/package.json +1 -1
  138. package/extensions/dm-9router-ext/src/index.ts +0 -541
  139. package/extensions/dm-ask-user/index.ts +0 -857
  140. package/extensions/dm-context/src/context.ts +0 -158
  141. package/extensions/dm-context/src/index.ts +0 -439
  142. package/extensions/dm-context/src/utils.ts +0 -6
  143. package/extensions/dm-fff/src/index.ts +0 -1023
  144. package/extensions/dm-fff/src/query.ts +0 -87
  145. package/extensions/dm-goal/src/goal.ts +0 -1073
  146. package/extensions/dm-grill-me/index.ts +0 -1085
  147. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  148. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  149. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  150. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  151. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  152. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  153. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  154. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  155. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  156. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  157. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  158. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  159. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  160. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  161. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  162. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  163. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  164. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  165. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  166. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  167. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  168. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  169. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  170. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  171. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  172. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  173. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  174. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  175. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  176. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  177. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  178. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  179. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  180. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  181. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  182. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  183. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  184. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  185. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  186. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  187. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  188. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  189. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  190. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  191. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  192. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  193. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  194. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  195. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  196. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  197. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  198. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  199. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  200. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  201. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  202. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  203. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  204. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  205. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  206. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  207. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  208. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  209. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  210. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  211. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  212. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  213. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  214. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  215. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  216. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  217. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  218. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  219. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  220. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  221. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  222. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  223. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  224. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  225. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  226. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  227. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  228. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  229. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  230. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  231. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  232. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  233. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  234. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  235. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  236. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  237. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  238. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  239. package/extensions/dm-tasks/src/index.ts +0 -1145
  240. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  241. package/extensions/dm-tasks/src/task-store.ts +0 -305
  242. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  243. package/extensions/dm-tasks/src/types.ts +0 -40
  244. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  245. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  246. package/extensions/dm-usage/index.ts +0 -1718
  247. package/extensions/greedysearch-dm/index.ts +0 -177
  248. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  249. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  250. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  251. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  252. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  253. package/extensions/greedysearch-dm/src/types.ts +0 -110
  254. package/extensions/greedysearch-dm/src/utils/helpers.ts +0 -40
@@ -1,1296 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import { keyText, type ExtensionAPI, type ExtensionContext } from "@duckmind/dm-coding-agent";
5
- import { Key, matchesKey } from "@duckmind/dm-tui";
6
- import { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, type ChainConfig } from "../agents/agents.ts";
7
- import {
8
- DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS,
9
- applySubagentProfile,
10
- checkSubagentProfile,
11
- generateProfilesForProvider,
12
- listSubagentProfiles,
13
- readSubagentProfile,
14
- refreshProviderModelCatalog,
15
- } from "../profiles/profiles.ts";
16
- import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
17
- import { isDynamicParallelStep, isParallelStep, type ChainStep } from "../shared/settings.ts";
18
- import { findModelInfo, toModelInfo } from "../shared/model-info.ts";
19
- import { formatTokens } from "../shared/formatters.ts";
20
- import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
21
- import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
22
- import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
23
- import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
24
- import {
25
- applySlashUpdate,
26
- buildSlashInitialResult,
27
- failSlashResult,
28
- finalizeSlashResult,
29
- resolveSlashMessageDetails,
30
- } from "./slash-live-state.ts";
31
- import {
32
- SLASH_RESULT_TYPE,
33
- SLASH_TEXT_RESULT_TYPE,
34
- SLASH_SUBAGENT_CANCEL_EVENT,
35
- SLASH_SUBAGENT_REQUEST_EVENT,
36
- SLASH_SUBAGENT_RESPONSE_EVENT,
37
- SLASH_SUBAGENT_STARTED_EVENT,
38
- SLASH_SUBAGENT_UPDATE_EVENT,
39
- type Details,
40
- type JsonSchemaObject,
41
- type SingleResult,
42
- type SubagentState,
43
- type Usage,
44
- } from "../shared/types.ts";
45
-
46
- interface InlineConfig {
47
- output?: string | false;
48
- outputMode?: "inline" | "file-only";
49
- reads?: string[] | false;
50
- model?: string;
51
- skill?: string[] | false;
52
- progress?: boolean;
53
- as?: string;
54
- label?: string;
55
- phase?: string;
56
- cwd?: string;
57
- count?: number;
58
- outputSchema?: string;
59
- acceptance?: string;
60
- }
61
-
62
- const parseInlineConfig = (raw: string): InlineConfig => {
63
- const config: InlineConfig = {};
64
- for (const part of raw.split(",")) {
65
- const trimmed = part.trim();
66
- if (!trimmed) continue;
67
- const eq = trimmed.indexOf("=");
68
- if (eq === -1) {
69
- if (trimmed === "progress") config.progress = true;
70
- continue;
71
- }
72
- const key = trimmed.slice(0, eq).trim();
73
- const val = trimmed.slice(eq + 1).trim();
74
- switch (key) {
75
- case "output": config.output = val === "false" ? false : val; break;
76
- case "outputMode": if (val === "inline" || val === "file-only") config.outputMode = val; break;
77
- case "reads": config.reads = val === "false" ? false : val.split("+").filter(Boolean); break;
78
- case "model": config.model = val || undefined; break;
79
- case "skill": case "skills": config.skill = val === "false" ? false : val.split("+").filter(Boolean); break;
80
- case "progress": config.progress = val !== "false"; break;
81
- case "as": config.as = val || undefined; break;
82
- case "label": config.label = val || undefined; break;
83
- case "phase": config.phase = val || undefined; break;
84
- case "cwd": config.cwd = val || undefined; break;
85
- case "count": { const n = Number(val); if (Number.isInteger(n) && n > 0) config.count = n; break; }
86
- case "outputSchema": config.outputSchema = val || undefined; break;
87
- case "acceptance": config.acceptance = val || undefined; break;
88
- }
89
- }
90
- return config;
91
- };
92
-
93
- const parseAgentToken = (token: string): { name: string; config: InlineConfig } => {
94
- const bracket = token.indexOf("[");
95
- if (bracket === -1) return { name: token, config: {} };
96
- const end = token.lastIndexOf("]");
97
- return { name: token.slice(0, bracket), config: parseInlineConfig(token.slice(bracket + 1, end !== -1 ? end : undefined)) };
98
- };
99
-
100
- const extractExecutionFlags = (rawArgs: string): { args: string; bg: boolean; fork: boolean } => {
101
- let args = rawArgs.trim();
102
- let bg = false;
103
- let fork = false;
104
-
105
- while (true) {
106
- if (args.endsWith(" --bg") || args === "--bg") {
107
- bg = true;
108
- args = args === "--bg" ? "" : args.slice(0, -5).trim();
109
- continue;
110
- }
111
- if (args.endsWith(" --fork") || args === "--fork") {
112
- fork = true;
113
- args = args === "--fork" ? "" : args.slice(0, -7).trim();
114
- continue;
115
- }
116
- break;
117
- }
118
-
119
- return { args, bg, fork };
120
- };
121
-
122
- const makeAgentCompletions = (state: SubagentState, multiAgent: boolean) => (prefix: string) => {
123
- if (!state.baseCwd) return null;
124
- const agents = discoverAgents(state.baseCwd, "both").agents;
125
- if (!multiAgent) {
126
- if (prefix.includes(" ")) return null;
127
- return agents.filter((a) => a.name.startsWith(prefix)).map((a) => ({ value: a.name, label: a.name }));
128
- }
129
-
130
- // Find the start of the current chain step: after the last top-level `->` arrow or `(`,
131
- // or after a `|` *inside* a group. A `|` at depth 0 is plain task text (only `(` opens a
132
- // group), so it must not restart agent completion — otherwise `scout -- do x | wr` would
133
- // wrongly resume suggesting agents past the `--` task. Quotes are tracked so separators
134
- // inside a task are ignored.
135
- let inSingle = false, inDouble = false, depth = 0, segStart = 0;
136
- for (let i = 0; i < prefix.length; i++) {
137
- const ch = prefix[i]!;
138
- if (inSingle) { if (ch === "'") inSingle = false; continue; }
139
- if (inDouble) { if (ch === '"') inDouble = false; continue; }
140
- if (ch === "'") { inSingle = true; continue; }
141
- if (ch === '"') { inDouble = true; continue; }
142
- if (ch === "(") {
143
- if (!prefix.slice(segStart, i).includes(" -- ")) {
144
- depth++;
145
- segStart = i + 1;
146
- }
147
- }
148
- else if (ch === ")") {
149
- if (depth > 0) {
150
- depth--;
151
- segStart = i + 1;
152
- }
153
- }
154
- else if (ch === "|" && depth > 0) segStart = i + 1;
155
- else if (ch === ">" && prefix[i - 1] === "-" && depth === 0) segStart = i + 1;
156
- }
157
- // Inside an open quote, or once the task has started (`--` / a quote), we are no
158
- // longer typing an agent name.
159
- if (inSingle || inDouble) return null;
160
- const segment = prefix.slice(segStart);
161
- if (segment.includes(" -- ") || segment.includes('"') || segment.includes("'")) return null;
162
-
163
- const lastWord = (segment.match(/(\S*)$/) || ["", ""])[1];
164
- let beforeLastWord = prefix.slice(0, prefix.length - lastWord.length);
165
- // A bare `->` or `|` just typed (no trailing space) needs a separating space;
166
- // `(` glues naturally to the agent name.
167
- if (lastWord === "" && /[>|]$/.test(beforeLastWord)) beforeLastWord = `${beforeLastWord} `;
168
-
169
- return agents.filter((a) => a.name.startsWith(lastWord)).map((a) => ({ value: `${beforeLastWord}${a.name}`, label: a.name }));
170
- };
171
-
172
- const discoverSavedChains = (cwd: string): ChainConfig[] => {
173
- const chainsByName = new Map<string, ChainConfig>();
174
- for (const chain of discoverAgentsAll(cwd).chains) {
175
- chainsByName.set(chain.name, chain);
176
- }
177
- return Array.from(chainsByName.values());
178
- };
179
-
180
- const makeChainCompletions = (state: SubagentState) => (prefix: string) => {
181
- if (prefix.includes(" ") || !state.baseCwd) return null;
182
- return discoverSavedChains(state.baseCwd)
183
- .filter((chain) => chain.name.startsWith(prefix))
184
- .map((chain) => ({ value: chain.name, label: chain.name }));
185
- };
186
-
187
- const makeBuiltinAgentNameCompletions = () => (prefix: string) => {
188
- if (prefix.includes(" ")) return null;
189
- return BUILTIN_AGENT_NAMES
190
- .filter((name) => name.startsWith(prefix))
191
- .map((name) => ({ value: name, label: name }));
192
- };
193
-
194
- const makeProviderCompletions = (state: SubagentState) => (prefix: string) => {
195
- if (prefix.includes(" ")) return null;
196
- const available = state.lastUiContext?.modelRegistry?.getAvailable?.();
197
- if (!Array.isArray(available)) return null;
198
- const providers = [...new Set(available
199
- .map((model) => typeof model?.provider === "string" ? model.provider : "")
200
- .filter(Boolean))]
201
- .sort((a, b) => a.localeCompare(b));
202
- return providers
203
- .filter((provider) => provider.startsWith(prefix))
204
- .map((provider) => ({ value: provider, label: provider }));
205
- };
206
-
207
- function sendSlashText(pi: ExtensionAPI, text: string): void {
208
- pi.sendMessage({ customType: SLASH_TEXT_RESULT_TYPE, content: text, display: true });
209
- }
210
-
211
- async function withSlashStatus<T>(
212
- ctx: ExtensionContext,
213
- text: string,
214
- run: () => Promise<T>,
215
- ): Promise<T> {
216
- if (ctx.hasUI) ctx.ui.setStatus("subagent-slash-text", text);
217
- try {
218
- return await run();
219
- } finally {
220
- if (ctx.hasUI) ctx.ui.setStatus("subagent-slash-text", undefined);
221
- }
222
- }
223
-
224
- function emptyUsage(): Usage {
225
- return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
226
- }
227
-
228
- function addUsage(target: Usage, source: Usage): void {
229
- target.input += source.input;
230
- target.output += source.output;
231
- target.cacheRead += source.cacheRead;
232
- target.cacheWrite += source.cacheWrite;
233
- target.cost += source.cost;
234
- target.turns += source.turns;
235
- }
236
-
237
- function usageHasValue(usage: Usage): boolean {
238
- return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0;
239
- }
240
-
241
- function assistantUsageFromMessage(message: unknown): Usage | undefined {
242
- if (!message || typeof message !== "object") return undefined;
243
- const msg = message as { role?: unknown; usage?: unknown };
244
- if (msg.role !== "assistant" || !msg.usage || typeof msg.usage !== "object") return undefined;
245
- const usage = msg.usage as {
246
- input?: unknown;
247
- output?: unknown;
248
- cacheRead?: unknown;
249
- cacheWrite?: unknown;
250
- cost?: { total?: unknown };
251
- };
252
- return {
253
- input: typeof usage.input === "number" ? usage.input : 0,
254
- output: typeof usage.output === "number" ? usage.output : 0,
255
- cacheRead: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
256
- cacheWrite: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0,
257
- cost: typeof usage.cost?.total === "number" ? usage.cost.total : 0,
258
- turns: 1,
259
- };
260
- }
261
-
262
- function isSubagentDetails(value: unknown): value is Details {
263
- if (!value || typeof value !== "object") return false;
264
- const details = value as { mode?: unknown; results?: unknown };
265
- return typeof details.mode === "string" && Array.isArray(details.results);
266
- }
267
-
268
- function detailsFromSessionEntry(entry: unknown): Details | undefined {
269
- if (!entry || typeof entry !== "object") return undefined;
270
- const record = entry as { type?: unknown; customType?: unknown; details?: unknown; message?: unknown };
271
- if (record.type === "custom_message" && record.customType === SLASH_RESULT_TYPE) {
272
- const details = resolveSlashMessageDetails(record.details)?.result.details;
273
- return isSubagentDetails(details) ? details : undefined;
274
- }
275
- if (record.type !== "message" || !record.message || typeof record.message !== "object") return undefined;
276
- const message = record.message as { role?: unknown; toolName?: unknown; details?: unknown };
277
- if (message.role !== "toolResult" || message.toolName !== "subagent") return undefined;
278
- return isSubagentDetails(message.details) ? message.details : undefined;
279
- }
280
-
281
- function formatCostUsage(label: string, usage: Usage): string {
282
- const extras = [
283
- usage.cacheRead ? `cache read ${formatTokens(usage.cacheRead)}` : "",
284
- usage.cacheWrite ? `cache write ${formatTokens(usage.cacheWrite)}` : "",
285
- usage.turns ? `${usage.turns} turn${usage.turns === 1 ? "" : "s"}` : "",
286
- ].filter(Boolean);
287
- return `${label}: ↑${formatTokens(usage.input)} ↓${formatTokens(usage.output)} $${usage.cost.toFixed(4)}${extras.length ? ` (${extras.join(", ")})` : ""}`;
288
- }
289
-
290
- function buildSubagentCostReport(ctx: ExtensionContext): string {
291
- const parent = emptyUsage();
292
- const childTotal = emptyUsage();
293
- const total = emptyUsage();
294
- const children: Array<{ label: string; usage: Usage; sessionFile?: string }> = [];
295
- for (const entry of ctx.sessionManager.getBranch()) {
296
- const message = entry.type === "message" ? (entry as { message?: unknown }).message : undefined;
297
- const parentUsage = assistantUsageFromMessage(message);
298
- if (parentUsage) addUsage(parent, parentUsage);
299
- const details = detailsFromSessionEntry(entry);
300
- if (!details) continue;
301
- for (const result of details.results) {
302
- if (!usageHasValue(result.usage)) continue;
303
- const usage = { ...result.usage };
304
- children.push({
305
- label: `Child ${children.length + 1} (${result.agent})`,
306
- usage,
307
- ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
308
- });
309
- addUsage(childTotal, usage);
310
- }
311
- }
312
- addUsage(total, parent);
313
- addUsage(total, childTotal);
314
- const lines = [
315
- "Subagent cost",
316
- "",
317
- formatCostUsage("Parent", parent),
318
- ];
319
- if (children.length === 0) {
320
- lines.push("No subagent child usage found in this session.");
321
- } else {
322
- for (const child of children) {
323
- lines.push(formatCostUsage(child.label, child.usage));
324
- if (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);
325
- }
326
- }
327
- lines.push("────────────────────────────", formatCostUsage("Children", childTotal), formatCostUsage("Total", total));
328
- return lines.join("\n");
329
- }
330
-
331
- function parseSingleRequiredArg(args: string, usage: string): { ok: true; value: string } | { ok: false; message: string } {
332
- const parts = args.trim().split(/\s+/).filter(Boolean);
333
- if (parts.length !== 1) return { ok: false, message: usage };
334
- return { ok: true, value: parts[0]! };
335
- }
336
-
337
- function getProfileWorkerModel(profile: { subagents?: { agentOverrides?: Record<string, { model?: string }> } }): string | undefined {
338
- const model = profile.subagents?.agentOverrides?.worker?.model;
339
- return typeof model === "string" && model.trim() ? model.trim() : undefined;
340
- }
341
-
342
- function loadSavedOutputSchema(chain: ChainConfig, stepAgent: string, outputSchema: unknown): JsonSchemaObject | undefined {
343
- if (outputSchema === undefined) return undefined;
344
- if (typeof outputSchema === "string") {
345
- const schemaPath = path.isAbsolute(outputSchema)
346
- ? outputSchema
347
- : path.join(path.dirname(chain.filePath), outputSchema);
348
- const parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as unknown;
349
- assertJsonSchemaObject(parsed, `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`);
350
- return parsed;
351
- }
352
- assertJsonSchemaObject(outputSchema, `outputSchema for chain '${chain.name}' step '${stepAgent}'`);
353
- return outputSchema;
354
- }
355
-
356
- const mapSavedChainSteps = (chain: ChainConfig, worktree = false): ChainStep[] => {
357
- return (chain.steps as unknown as Array<ChainStep & { skills?: string[] | false }>).map((step) => {
358
- if (isParallelStep(step)) {
359
- const parallel = step.parallel.map((task) => {
360
- const { outputSchema: rawOutputSchema, ...rest } = task as typeof task & { outputSchema?: unknown };
361
- const outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);
362
- return { ...rest, ...(outputSchema ? { outputSchema } : {}) };
363
- });
364
- return { ...step, parallel, ...(worktree ? { worktree: true } : {}) };
365
- }
366
- if (isDynamicParallelStep(step)) {
367
- const { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel as typeof step.parallel & { outputSchema?: unknown };
368
- const outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);
369
- const collectSchema = loadSavedOutputSchema(chain, `${step.collect.as} collection`, step.collect.outputSchema);
370
- return {
371
- ...step,
372
- parallel: { ...parallelRest, ...(outputSchema ? { outputSchema } : {}) },
373
- collect: { ...step.collect, ...(collectSchema ? { outputSchema: collectSchema } : {}) },
374
- };
375
- }
376
- const outputSchema = loadSavedOutputSchema(chain, step.agent, (step as { outputSchema?: unknown }).outputSchema);
377
- return {
378
- agent: step.agent,
379
- task: step.task || undefined,
380
- ...(step.phase ? { phase: step.phase } : {}),
381
- ...(step.label ? { label: step.label } : {}),
382
- ...(step.as ? { as: step.as } : {}),
383
- ...(outputSchema ? { outputSchema } : {}),
384
- ...((step as { acceptance?: unknown }).acceptance !== undefined ? { acceptance: (step as { acceptance?: unknown }).acceptance } : {}),
385
- output: step.output,
386
- outputMode: step.outputMode,
387
- reads: step.reads,
388
- progress: step.progress,
389
- skill: step.skill ?? step.skills,
390
- model: step.model,
391
- };
392
- });
393
- };
394
-
395
- async function requestSlashRun(
396
- pi: ExtensionAPI,
397
- ctx: ExtensionContext,
398
- requestId: string,
399
- params: SubagentParamsLike,
400
- ): Promise<SlashSubagentResponse> {
401
- return new Promise((resolve, reject) => {
402
- let done = false;
403
- let started = false;
404
-
405
- const startTimeoutMs = 15_000;
406
- const startTimeout = setTimeout(() => {
407
- finish(() => reject(new Error(
408
- "Slash subagent bridge did not start within 15s. Ensure the extension is loaded correctly.",
409
- )));
410
- }, startTimeoutMs);
411
-
412
- const onStarted = (data: unknown) => {
413
- if (done || !data || typeof data !== "object") return;
414
- if ((data as { requestId?: unknown }).requestId !== requestId) return;
415
- started = true;
416
- clearTimeout(startTimeout);
417
- if (ctx.hasUI) ctx.ui.setStatus("subagent-slash", "running...");
418
- };
419
-
420
- const onResponse = (data: unknown) => {
421
- if (done || !data || typeof data !== "object") return;
422
- const response = data as Partial<SlashSubagentResponse>;
423
- if (response.requestId !== requestId) return;
424
- clearTimeout(startTimeout);
425
- finish(() => resolve(response as SlashSubagentResponse));
426
- };
427
-
428
- const onUpdate = (data: unknown) => {
429
- if (done || !data || typeof data !== "object") return;
430
- const update = data as SlashSubagentUpdate;
431
- if (update.requestId !== requestId) return;
432
- applySlashUpdate(requestId, update);
433
- if (!ctx.hasUI) return;
434
- const tool = update.currentTool ? ` ${update.currentTool}` : "";
435
- const count = update.toolCount ?? 0;
436
- const liveDetailKey = keyText("app.tools.expand");
437
- ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ${liveDetailKey} live detail`);
438
- };
439
-
440
- const onTerminalInput = ctx.hasUI
441
- ? ctx.ui.onTerminalInput((input) => {
442
- if (!matchesKey(input, Key.escape)) return undefined;
443
- pi.events.emit(SLASH_SUBAGENT_CANCEL_EVENT, { requestId });
444
- finish(() => reject(new Error("Cancelled")));
445
- return { consume: true };
446
- })
447
- : undefined;
448
-
449
- const unsubStarted = pi.events.on(SLASH_SUBAGENT_STARTED_EVENT, onStarted);
450
- const unsubResponse = pi.events.on(SLASH_SUBAGENT_RESPONSE_EVENT, onResponse);
451
- const unsubUpdate = pi.events.on(SLASH_SUBAGENT_UPDATE_EVENT, onUpdate);
452
-
453
- const finish = (next: () => void) => {
454
- if (done) return;
455
- done = true;
456
- clearTimeout(startTimeout);
457
- unsubStarted();
458
- unsubResponse();
459
- unsubUpdate();
460
- onTerminalInput?.();
461
- next();
462
- };
463
-
464
- pi.events.emit(SLASH_SUBAGENT_REQUEST_EVENT, { requestId, params, ctx });
465
-
466
- // Bridge emits STARTED synchronously during REQUEST emit.
467
- // If not started, no bridge received the request.
468
- if (!started && done) return;
469
- if (!started) {
470
- finish(() => reject(new Error(
471
- "No slash subagent bridge responded. Ensure the subagent extension is loaded correctly.",
472
- )));
473
- }
474
- });
475
- }
476
-
477
- function extractSlashMessageText(content: string | Array<{ type?: string; text?: string }>): string {
478
- if (typeof content === "string") return content;
479
- if (!Array.isArray(content)) return "";
480
- return content
481
- .filter((part): part is { type: "text"; text: string } => part?.type === "text" && typeof part.text === "string")
482
- .map((part) => part.text)
483
- .join("\n");
484
- }
485
-
486
- function formatExportPathList(paths: string[]): string {
487
- return paths.map((file) => `- \`${file}\``).join("\n");
488
- }
489
-
490
- function collectResultPaths(results: SingleResult[], getPath: (result: SingleResult) => string | undefined): string[] {
491
- return results
492
- .map(getPath)
493
- .filter((file): file is string => typeof file === "string" && file.length > 0);
494
- }
495
-
496
- function buildSlashExportText(response: SlashSubagentResponse): string {
497
- const output = extractSlashMessageText(response.result.content) || response.errorText || "(no output)";
498
- const results = response.result.details?.results ?? [];
499
- const sessionFiles = collectResultPaths(results, (result) => result.sessionFile);
500
- const savedOutputs = collectResultPaths(results, (result) => result.savedOutputPath);
501
- const artifactOutputs = collectResultPaths(results, (result) => result.artifactPaths?.outputPath);
502
- const sections = ["## Subagent result", output];
503
- if (sessionFiles.length > 0) sections.push("## Child session exports", formatExportPathList(sessionFiles));
504
- if (savedOutputs.length > 0) sections.push("## Saved outputs", formatExportPathList(savedOutputs));
505
- if (artifactOutputs.length > 0) sections.push("## Artifact outputs", formatExportPathList(artifactOutputs));
506
- return sections.join("\n\n");
507
- }
508
-
509
- function persistSlashSessionSnapshot(ctx: ExtensionContext): void {
510
- try {
511
- if (!ctx.sessionManager) return;
512
- const sessionManager = ctx.sessionManager as typeof ctx.sessionManager & {
513
- _rewriteFile?: () => void;
514
- flushed?: boolean;
515
- };
516
- const sessionFile = sessionManager.getSessionFile();
517
- if (!sessionFile || typeof sessionManager._rewriteFile !== "function") return;
518
- fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
519
- sessionManager._rewriteFile();
520
- sessionManager.flushed = true;
521
- } catch (error) {
522
- console.error("Failed to persist slash session snapshot for export:", error);
523
- }
524
- }
525
-
526
- async function runSlashSubagent(
527
- pi: ExtensionAPI,
528
- ctx: ExtensionContext,
529
- params: SubagentParamsLike,
530
- ): Promise<void> {
531
- if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
532
- const requestId = randomUUID();
533
- const initialDetails = buildSlashInitialResult(requestId, params);
534
- const initialText = extractSlashMessageText(initialDetails.result.content) || "Running subagent...";
535
- pi.sendMessage({
536
- customType: SLASH_RESULT_TYPE,
537
- content: initialText,
538
- display: true,
539
- details: initialDetails,
540
- });
541
- persistSlashSessionSnapshot(ctx);
542
-
543
- try {
544
- const response = await requestSlashRun(pi, ctx, requestId, params);
545
- const finalDetails = finalizeSlashResult(response);
546
- pi.sendMessage({
547
- customType: SLASH_RESULT_TYPE,
548
- content: buildSlashExportText(response),
549
- display: !ctx.hasUI,
550
- details: finalDetails,
551
- });
552
- persistSlashSessionSnapshot(ctx);
553
- if (ctx.hasUI) {
554
- ctx.ui.setStatus("subagent-slash", undefined);
555
- }
556
- if (response.isError && ctx.hasUI) {
557
- ctx.ui.notify(response.errorText || "Subagent failed", "error");
558
- }
559
- } catch (error) {
560
- const message = error instanceof Error ? error.message : String(error);
561
- const failedDetails = failSlashResult(requestId, params, message);
562
- pi.sendMessage({
563
- customType: SLASH_RESULT_TYPE,
564
- content: `## Subagent result\n\n${message}`,
565
- display: !ctx.hasUI,
566
- details: failedDetails,
567
- });
568
- persistSlashSessionSnapshot(ctx);
569
- if (ctx.hasUI) {
570
- ctx.ui.setStatus("subagent-slash", undefined);
571
- }
572
- if (message === "Cancelled") {
573
- if (ctx.hasUI) ctx.ui.notify("Cancelled", "warning");
574
- return;
575
- }
576
- if (ctx.hasUI) ctx.ui.notify(message, "error");
577
- }
578
- }
579
-
580
-
581
- export interface GroupConfig { concurrency?: number; failFast?: boolean; worktree?: boolean }
582
- export interface ParsedStep { kind: "step"; name: string; config: InlineConfig; task?: string }
583
- export interface ParsedGroup { kind: "group"; tasks: ParsedStep[]; config: GroupConfig }
584
- export type ParsedGroupStep = ParsedStep | ParsedGroup;
585
-
586
- export const PARALLEL_GROUP_USAGE =
587
- 'Usage: /chain agent "task" -> (agent2 "task" | agent3 "task") -> agent4';
588
-
589
- export class SlashParseError extends Error {}
590
-
591
- // Walk `input` tracking quote/paren state; returns true if parens are unbalanced.
592
- function findUnmatchedCloseParen(input: string): boolean {
593
- let depth = 0, inSingle = false, inDouble = false;
594
- for (let i = 0; i < input.length; i++) {
595
- const ch = input[i]!;
596
- if (inSingle) { if (ch === "'") inSingle = false; continue; }
597
- if (inDouble) { if (ch === '"') inDouble = false; continue; }
598
- if (ch === "'") { inSingle = true; continue; }
599
- if (ch === '"') { inDouble = true; continue; }
600
- if (ch === "(") depth++;
601
- else if (ch === ")") { depth--; if (depth < 0) return true; }
602
- }
603
- return depth !== 0;
604
- }
605
-
606
- // Split on top-level " -> ", ignoring arrows inside quotes or parentheses.
607
- function splitOnArrow(input: string): string[] {
608
- const segments: string[] = [];
609
- let depth = 0, inSingle = false, inDouble = false, start = 0;
610
- for (let i = 0; i < input.length; i++) {
611
- const ch = input[i]!;
612
- if (inSingle) { if (ch === "'") inSingle = false; continue; }
613
- if (inDouble) { if (ch === '"') inDouble = false; continue; }
614
- if (ch === "'") { inSingle = true; continue; }
615
- if (ch === '"') { inDouble = true; continue; }
616
- if (ch === "(") depth++;
617
- else if (ch === ")") depth--;
618
- else if (depth === 0 && ch === "-" && input[i + 1] === ">" && input[i + 2] === " ") {
619
- segments.push(input.slice(start, i));
620
- i += 2;
621
- start = i + 1;
622
- }
623
- }
624
- segments.push(input.slice(start));
625
- return segments;
626
- }
627
-
628
- // Split a group's inner text on top-level " | ", ignoring pipes inside quotes/parens.
629
- function splitGroupTasks(inner: string): string[] {
630
- const parts: string[] = [];
631
- let depth = 0, inSingle = false, inDouble = false, start = 0;
632
- for (let i = 0; i < inner.length; i++) {
633
- const ch = inner[i]!;
634
- if (inSingle) { if (ch === "'") inSingle = false; continue; }
635
- if (inDouble) { if (ch === '"') inDouble = false; continue; }
636
- if (ch === "'") { inSingle = true; continue; }
637
- if (ch === '"') { inDouble = true; continue; }
638
- if (ch === "(") depth++;
639
- else if (ch === ")") depth--;
640
- else if (ch === "|" && depth === 0) {
641
- parts.push(inner.slice(start, i));
642
- start = i + 1;
643
- }
644
- }
645
- parts.push(inner.slice(start));
646
- return parts;
647
- }
648
-
649
- export function parseSingleTaskToken(token: string): ParsedStep {
650
- let agentPart: string;
651
- let task: string | undefined;
652
- const qMatch = token.match(/^(\S+(?:\[[^\]]*\])?)\s+(?:"([^"]*)"|'([^']*)')$/);
653
- if (qMatch) {
654
- agentPart = qMatch[1]!;
655
- task = (qMatch[2] ?? qMatch[3]) || undefined;
656
- } else {
657
- const dashIdx = token.indexOf(" -- ");
658
- if (dashIdx !== -1) {
659
- agentPart = token.slice(0, dashIdx).trim();
660
- task = token.slice(dashIdx + 4).trim() || undefined;
661
- } else {
662
- agentPart = token;
663
- }
664
- }
665
- return { kind: "step", ...parseAgentToken(agentPart), task };
666
- }
667
-
668
- const parseGroupConfig = (raw: string): GroupConfig => {
669
- const config: GroupConfig = {};
670
- for (const part of raw.split(",")) {
671
- const trimmed = part.trim();
672
- if (!trimmed) continue;
673
- const eq = trimmed.indexOf("=");
674
- const key = eq === -1 ? trimmed : trimmed.slice(0, eq).trim();
675
- const val = eq === -1 ? "" : trimmed.slice(eq + 1).trim();
676
- switch (key) {
677
- case "concurrency": { const n = Number(val); if (Number.isInteger(n) && n > 0) config.concurrency = n; break; }
678
- case "failFast": config.failFast = eq === -1 ? true : val !== "false"; break;
679
- case "worktree": config.worktree = eq === -1 ? true : val !== "false"; break;
680
- }
681
- }
682
- return config;
683
- };
684
-
685
- // Split `(...)` from an optional trailing `[...]` group-config suffix, respecting
686
- // quotes and nested parens. Returns the inner group text and the parsed config.
687
- const splitGroupBody = (trimmed: string): { inner: string; config: GroupConfig } => {
688
- let depth = 0, inSingle = false, inDouble = false, closeIdx = -1;
689
- for (let i = 0; i < trimmed.length; i++) {
690
- const ch = trimmed[i]!;
691
- if (inSingle) { if (ch === "'") inSingle = false; continue; }
692
- if (inDouble) { if (ch === '"') inDouble = false; continue; }
693
- if (ch === "'") { inSingle = true; continue; }
694
- if (ch === '"') { inDouble = true; continue; }
695
- if (ch === "(") depth++;
696
- else if (ch === ")") { depth--; if (depth === 0) { closeIdx = i; break; } }
697
- }
698
- if (closeIdx === -1) throw new SlashParseError(`Unmatched parentheses in group: '${trimmed}'`);
699
- const inner = trimmed.slice(1, closeIdx);
700
- const suffix = trimmed.slice(closeIdx + 1).trim();
701
- if (!suffix) return { inner, config: {} };
702
- if (!suffix.startsWith("[") || !suffix.endsWith("]")) {
703
- throw new SlashParseError(`Group options must be wrapped in [...]: '${suffix}'`);
704
- }
705
- return { inner, config: parseGroupConfig(suffix.slice(1, -1)) };
706
- };
707
-
708
- export function parseGroupSegment(segment: string): ParsedGroup {
709
- const trimmed = segment.trim();
710
- if (!trimmed.startsWith("(")) {
711
- throw new SlashParseError(`Parallel group must be wrapped in parentheses: '${trimmed}'`);
712
- }
713
- const { inner, config } = splitGroupBody(trimmed);
714
- const rawParts = splitGroupTasks(inner).map((p) => p.trim()).filter((p) => p.length > 0);
715
- if (rawParts.length < 2) {
716
- throw new SlashParseError("Parallel group must contain at least two tasks separated by ' | '");
717
- }
718
- return { kind: "group", tasks: rawParts.map((part) => parseSingleTaskToken(part)), config };
719
- }
720
-
721
- // True if `input` uses inline parallel-group syntax. A group is a *step* that begins
722
- // with `(` at the top level, so we split on top-level ` -> ` arrows and look for a
723
- // segment that opens with `(`. Parentheses appearing inside a task (e.g.
724
- // `scout -- inspect auth (backend)`) do not count, which keeps legacy shared-task
725
- // `-- task` parsing — including a bare `|` inside the task — on the single-agent path.
726
- export function hasGroupSyntax(input: string): boolean {
727
- return splitOnArrow(input).some((seg) => seg.trim().startsWith("("));
728
- }
729
-
730
- export function parseChainExpression(input: string): { steps: ParsedGroupStep[] } {
731
- const trimmed = input.trim();
732
- if (!trimmed.includes(" -> ")) {
733
- throw new SlashParseError('Parallel groups in /chain require " -> " between steps');
734
- }
735
- if (findUnmatchedCloseParen(trimmed)) {
736
- throw new SlashParseError("Unmatched parentheses in /chain expression");
737
- }
738
- const steps: ParsedGroupStep[] = [];
739
- for (const seg of splitOnArrow(trimmed)) {
740
- const t = seg.trim();
741
- if (!t) continue;
742
- if (t.startsWith("(")) {
743
- steps.push(parseGroupSegment(t));
744
- continue;
745
- }
746
- if (findUnmatchedCloseParen(t)) {
747
- throw new SlashParseError(`Unmatched parentheses in chain segment: '${t}'`);
748
- }
749
- steps.push(parseSingleTaskToken(t));
750
- }
751
- if (steps.length === 0) {
752
- throw new SlashParseError("/chain expression must include at least one step");
753
- }
754
- return { steps };
755
- }
756
-
757
- const parseAgentArgs = (
758
- state: SubagentState,
759
- args: string,
760
- command: string,
761
- ctx: ExtensionContext,
762
- ): { steps: ParsedStep[]; task: string } | null => {
763
- const input = args.trim();
764
- const usage = `Usage: /${command} agent1 "task1" -> agent2 "task2"`;
765
- let steps: ParsedStep[];
766
- let sharedTask: string;
767
- let perStep = false;
768
-
769
- if (input.includes(" -> ")) {
770
- perStep = true;
771
- const segments = input.split(" -> ");
772
- steps = [];
773
- for (const seg of segments) {
774
- const trimmed = seg.trim();
775
- if (!trimmed) continue;
776
- steps.push(parseSingleTaskToken(trimmed));
777
- }
778
- sharedTask = steps.find((s) => s.task)?.task ?? "";
779
- } else {
780
- const delimiterIndex = input.indexOf(" -- ");
781
- if (delimiterIndex === -1) {
782
- ctx.ui.notify(usage, "error");
783
- return null;
784
- }
785
- const agentsPart = input.slice(0, delimiterIndex).trim();
786
- sharedTask = input.slice(delimiterIndex + 4).trim();
787
- if (!agentsPart || !sharedTask) {
788
- ctx.ui.notify(usage, "error");
789
- return null;
790
- }
791
- steps = agentsPart.split(/\s+/).filter(Boolean).map((t) => parseSingleTaskToken(t));
792
- }
793
-
794
- if (steps.length === 0) {
795
- ctx.ui.notify(usage, "error");
796
- return null;
797
- }
798
- if (!state.baseCwd) {
799
- ctx.ui.notify("Subagent session cwd is not initialized yet", "error");
800
- return null;
801
- }
802
- const agents = discoverAgents(state.baseCwd, "both").agents;
803
- for (const step of steps) {
804
- if (!agents.find((a) => a.name === step.name)) {
805
- ctx.ui.notify(`Unknown agent: ${step.name}`, "error");
806
- return null;
807
- }
808
- }
809
- if (command === "chain" && !steps[0]?.task && (perStep || !sharedTask)) {
810
- ctx.ui.notify(`First step must have a task: /chain agent "task" -> agent2`, "error");
811
- return null;
812
- }
813
- if (command === "parallel" && !steps.some((s) => s.task) && !sharedTask) {
814
- ctx.ui.notify("At least one step must have a task", "error");
815
- return null;
816
- }
817
- return { steps, task: sharedTask };
818
- };
819
-
820
- type ChainStepObject = {
821
- agent: string;
822
- task?: string;
823
- output?: string | false;
824
- outputMode?: "inline" | "file-only";
825
- reads?: string[] | false;
826
- model?: string;
827
- skill?: string[] | false;
828
- progress?: boolean;
829
- as?: string;
830
- label?: string;
831
- phase?: string;
832
- cwd?: string;
833
- count?: number;
834
- outputSchema?: JsonSchemaObject;
835
- acceptance?: string;
836
- };
837
-
838
- const INLINE_ACCEPTANCE_LEVELS = new Set(["auto", "attested", "checked"]);
839
-
840
- function validateInlineAcceptanceInput(value: string, agent: string): void {
841
- const errors = validateAcceptanceInput(value, `acceptance for step '${agent}'`);
842
- if (errors.length > 0) throw new SlashParseError(errors[0]!);
843
- if (!INLINE_ACCEPTANCE_LEVELS.has(value)) {
844
- throw new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none, verified, or reviewed acceptance contracts.`);
845
- }
846
- }
847
-
848
- // Load an inline `outputSchema=<path>` JSON file, resolved against the session cwd.
849
- // Throws (SlashParseError / fs / JSON) on a missing or malformed schema.
850
- function loadInlineOutputSchema(baseCwd: string, agent: string, value: string): JsonSchemaObject {
851
- const schemaPath = path.isAbsolute(value) ? value : path.join(baseCwd, value);
852
- const label = `outputSchema for step '${agent}' (${schemaPath})`;
853
- let parsed: unknown;
854
- try {
855
- parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
856
- } catch (error) {
857
- throw new SlashParseError(`Cannot read ${label}: ${error instanceof Error ? error.message : String(error)}`);
858
- }
859
- assertJsonSchemaObject(parsed, label);
860
- return parsed;
861
- }
862
-
863
- // Build a ChainStep object from a parsed token. `inGroup` enables `count` (parallel-only).
864
- // May throw SlashParseError for an invalid acceptance level or outputSchema path.
865
- const mapParsedTaskToStepObject = (
866
- step: ParsedStep,
867
- fallbackTask: string | undefined,
868
- isFirst: boolean,
869
- opts: { baseCwd: string; inGroup: boolean },
870
- ): ChainStepObject => {
871
- const { name, config, task: stepTask } = step;
872
- if (config.acceptance !== undefined) validateInlineAcceptanceInput(config.acceptance, name);
873
- return {
874
- agent: name,
875
- ...(stepTask ? { task: stepTask } : isFirst && fallbackTask ? { task: fallbackTask } : {}),
876
- ...(config.output !== undefined ? { output: config.output } : {}),
877
- ...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),
878
- ...(config.reads !== undefined ? { reads: config.reads } : {}),
879
- ...(config.model ? { model: config.model } : {}),
880
- ...(config.skill !== undefined ? { skill: config.skill } : {}),
881
- ...(config.progress !== undefined ? { progress: config.progress } : {}),
882
- ...(config.as ? { as: config.as } : {}),
883
- ...(config.label ? { label: config.label } : {}),
884
- ...(config.phase ? { phase: config.phase } : {}),
885
- ...(config.cwd ? { cwd: config.cwd } : {}),
886
- ...(opts.inGroup && config.count !== undefined ? { count: config.count } : {}),
887
- ...(config.outputSchema ? { outputSchema: loadInlineOutputSchema(opts.baseCwd, name, config.outputSchema) } : {}),
888
- ...(config.acceptance ? { acceptance: config.acceptance } : {}),
889
- };
890
- };
891
-
892
- export function buildChainExpressionSteps(
893
- state: SubagentState,
894
- input: string,
895
- ctx: ExtensionContext,
896
- ): { chain: ChainStep[]; task: string } | null {
897
- const notify = (message: string) => ctx.ui.notify(message, "error");
898
- if (!hasGroupSyntax(input)) {
899
- const parsed = parseAgentArgs(state, input, "chain", ctx);
900
- if (!parsed) return null;
901
- const baseCwd = state.baseCwd!; // parseAgentArgs already verified baseCwd is set
902
- try {
903
- const chain: ChainStep[] = parsed.steps.map((step, i) =>
904
- mapParsedTaskToStepObject(step, parsed.task || undefined, i === 0, { baseCwd, inGroup: false }),
905
- );
906
- return { chain, task: parsed.task };
907
- } catch (error) {
908
- notify(error instanceof Error ? error.message : String(error));
909
- return null;
910
- }
911
- }
912
-
913
- let expression: { steps: ParsedGroupStep[] };
914
- try {
915
- expression = parseChainExpression(input);
916
- } catch (error) {
917
- notify(error instanceof Error ? error.message : String(error));
918
- return null;
919
- }
920
- if (!state.baseCwd) {
921
- notify("Subagent session cwd is not initialized yet");
922
- return null;
923
- }
924
- const agents = discoverAgents(state.baseCwd, "both").agents;
925
- const stepAgentNames = expression.steps.flatMap((step) =>
926
- step.kind === "group" ? step.tasks.map((t) => t.name) : [step.name],
927
- );
928
- for (const name of stepAgentNames) {
929
- if (!agents.find((a) => a.name === name)) {
930
- notify(`Unknown agent: ${name}`);
931
- return null;
932
- }
933
- }
934
- // Every task inside a parallel group needs its own task; there is no shared-task fallback.
935
- for (const step of expression.steps) {
936
- if (step.kind === "group" && step.tasks.some((t) => !t.task)) {
937
- notify('Each task in a parallel group needs a task: (agent "a" | agent "b")');
938
- return null;
939
- }
940
- }
941
- const firstStep = expression.steps[0]!;
942
- const firstHasTask =
943
- firstStep.kind === "group"
944
- ? firstStep.tasks.some((t) => Boolean(t.task))
945
- : Boolean(firstStep.task);
946
- if (!firstHasTask) {
947
- notify('First step must have a task: /chain agent "task" -> agent2');
948
- return null;
949
- }
950
- const sharedTask =
951
- firstStep.kind === "group"
952
- ? (firstStep.tasks.find((t) => t.task)?.task ?? "")
953
- : (firstStep.task ?? "");
954
- const baseCwd = state.baseCwd;
955
- let chain: ChainStep[];
956
- try {
957
- chain = expression.steps.map((step) => {
958
- if (step.kind === "group") {
959
- const parallel = step.tasks.map((t) => mapParsedTaskToStepObject(t, undefined, false, { baseCwd, inGroup: true }));
960
- return {
961
- parallel,
962
- ...(step.config.concurrency !== undefined ? { concurrency: step.config.concurrency } : {}),
963
- ...(step.config.failFast !== undefined ? { failFast: step.config.failFast } : {}),
964
- ...(step.config.worktree !== undefined ? { worktree: step.config.worktree } : {}),
965
- };
966
- }
967
- return mapParsedTaskToStepObject(step, sharedTask || undefined, false, { baseCwd, inGroup: false });
968
- });
969
- } catch (error) {
970
- notify(error instanceof Error ? error.message : String(error));
971
- return null;
972
- }
973
- return { chain, task: sharedTask };
974
- }
975
-
976
- export function registerSlashCommands(
977
- pi: ExtensionAPI,
978
- state: SubagentState,
979
- ): void {
980
- pi.registerCommand("run", {
981
- description: "Run a subagent directly: /run agent[output=file] [task] [--bg] [--fork]",
982
- getArgumentCompletions: makeAgentCompletions(state, false),
983
- handler: async (args, ctx) => {
984
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
985
- const input = cleanedArgs.trim();
986
- const firstSpace = input.indexOf(" ");
987
- if (!input) { ctx.ui.notify("Usage: /run <agent> [task] [--bg] [--fork]", "error"); return; }
988
- const { name: agentName, config: inline } = parseAgentToken(firstSpace === -1 ? input : input.slice(0, firstSpace));
989
- const task = firstSpace === -1 ? "" : input.slice(firstSpace + 1).trim();
990
-
991
- if (!state.baseCwd) { ctx.ui.notify("Subagent session cwd is not initialized yet", "error"); return; }
992
- const agents = discoverAgents(state.baseCwd, "both").agents;
993
- if (!agents.find((a) => a.name === agentName)) { ctx.ui.notify(`Unknown agent: ${agentName}`, "error"); return; }
994
-
995
- let finalTask = task;
996
- if (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {
997
- finalTask = `[Read from: ${inline.reads.join(", ")}]\n\n${finalTask}`;
998
- }
999
- const params: SubagentParamsLike = { agent: agentName, task: finalTask, clarify: false, agentScope: "both" };
1000
- if (inline.output !== undefined) params.output = inline.output;
1001
- if (inline.outputMode !== undefined) params.outputMode = inline.outputMode;
1002
- if (inline.skill !== undefined) params.skill = inline.skill;
1003
- if (inline.model) params.model = inline.model;
1004
- if (bg) params.async = true;
1005
- if (fork) params.context = "fork";
1006
- await runSlashSubagent(pi, ctx, params);
1007
- },
1008
- });
1009
-
1010
- pi.registerCommand("chain", {
1011
- description: "Run agents in sequence: /chain scout \"task\" -> planner [--bg] [--fork]",
1012
- getArgumentCompletions: makeAgentCompletions(state, true),
1013
- handler: async (args, ctx) => {
1014
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1015
- const built = buildChainExpressionSteps(state, cleanedArgs, ctx);
1016
- if (!built) return;
1017
- const params: SubagentParamsLike = { chain: built.chain, task: built.task, clarify: false, agentScope: "both" };
1018
- if (bg) params.async = true;
1019
- if (fork) params.context = "fork";
1020
- await runSlashSubagent(pi, ctx, params);
1021
- },
1022
- });
1023
-
1024
- pi.registerCommand("run-chain", {
1025
- description: "Run a saved chain: /run-chain chainName -- task [--bg] [--fork]",
1026
- getArgumentCompletions: makeChainCompletions(state),
1027
- handler: async (args, ctx) => {
1028
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1029
- const delimiterIndex = cleanedArgs.indexOf(" -- ");
1030
- const usage = "Usage: /run-chain <chainName> -- <task> [--bg] [--fork]";
1031
- if (delimiterIndex === -1) {
1032
- ctx.ui.notify(usage, "error");
1033
- return;
1034
- }
1035
- const chainName = cleanedArgs.slice(0, delimiterIndex).trim();
1036
- const task = cleanedArgs.slice(delimiterIndex + 4).trim();
1037
- if (!chainName || !task) {
1038
- ctx.ui.notify(usage, "error");
1039
- return;
1040
- }
1041
- if (!state.baseCwd) { ctx.ui.notify("Subagent session cwd is not initialized yet", "error"); return; }
1042
- const chain = discoverSavedChains(state.baseCwd).find((candidate) => candidate.name === chainName);
1043
- if (!chain) {
1044
- ctx.ui.notify(`Unknown chain: ${chainName}`, "error");
1045
- return;
1046
- }
1047
- const params: SubagentParamsLike = { chain: mapSavedChainSteps(chain), task, clarify: false, agentScope: "both" };
1048
- if (bg) params.async = true;
1049
- if (fork) params.context = "fork";
1050
- await runSlashSubagent(pi, ctx, params);
1051
- },
1052
- });
1053
-
1054
- pi.registerCommand("parallel", {
1055
- description: "Run agents in parallel: /parallel scout \"task1\" -> reviewer \"task2\" [--bg] [--fork]",
1056
- getArgumentCompletions: makeAgentCompletions(state, true),
1057
- handler: async (args, ctx) => {
1058
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1059
- const parsed = parseAgentArgs(state, cleanedArgs, "parallel", ctx);
1060
- if (!parsed) return;
1061
- const tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({
1062
- agent: name,
1063
- task: stepTask ?? parsed.task,
1064
- ...(config.output !== undefined ? { output: config.output } : {}),
1065
- ...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),
1066
- ...(config.reads !== undefined ? { reads: config.reads } : {}),
1067
- ...(config.model ? { model: config.model } : {}),
1068
- ...(config.skill !== undefined ? { skill: config.skill } : {}),
1069
- ...(config.progress !== undefined ? { progress: config.progress } : {}),
1070
- }));
1071
- const params: SubagentParamsLike = { tasks, clarify: false, agentScope: "both" };
1072
- if (bg) params.async = true;
1073
- if (fork) params.context = "fork";
1074
- await runSlashSubagent(pi, ctx, params);
1075
- },
1076
- });
1077
-
1078
- pi.registerCommand("subagent-cost", {
1079
- description: "Show parent and subagent child usage cost for this session",
1080
- handler: async (_args, ctx) => {
1081
- sendSlashText(pi, buildSubagentCostReport(ctx));
1082
- },
1083
- });
1084
-
1085
- pi.registerCommand("subagents-doctor", {
1086
- description: "Show subagent diagnostics",
1087
- handler: async (_args, ctx) => {
1088
- await runSlashSubagent(pi, ctx, { action: "doctor" });
1089
- },
1090
- });
1091
-
1092
- pi.registerCommand("subagents-fleet", {
1093
- description: "Show active subagent fleet status and transcript commands",
1094
- handler: async (_args, ctx) => {
1095
- await runSlashSubagent(pi, ctx, { action: "status", view: "fleet" });
1096
- },
1097
- });
1098
-
1099
- registerPromptWorkflowCommands({
1100
- pi,
1101
- run: (params, ctx) => runSlashSubagent(pi, ctx, params),
1102
- });
1103
-
1104
- pi.registerCommand("subagents-models", {
1105
- description: "Show runtime-loaded builtin subagent models",
1106
- getArgumentCompletions: makeBuiltinAgentNameCompletions(),
1107
- handler: async (args, ctx) => {
1108
- const trimmed = args.trim();
1109
- if (!trimmed) {
1110
- await runSlashSubagent(pi, ctx, { action: "models" });
1111
- return;
1112
- }
1113
- const parts = trimmed.split(/\s+/).filter(Boolean);
1114
- if (parts.length !== 1) {
1115
- ctx.ui.notify("Usage: /subagents-models [builtin-agent-name]", "error");
1116
- return;
1117
- }
1118
- const agent = parts[0]!;
1119
- if (!(BUILTIN_AGENT_NAMES as readonly string[]).includes(agent)) {
1120
- ctx.ui.notify(`Unknown builtin agent: ${agent}`, "error");
1121
- return;
1122
- }
1123
- await runSlashSubagent(pi, ctx, { action: "models", agent });
1124
- },
1125
- });
1126
-
1127
- pi.registerCommand("subagents-profiles", {
1128
- description: "List saved subagent profiles",
1129
- handler: async (_args, _ctx) => {
1130
- const profiles = listSubagentProfiles();
1131
- if (profiles.length === 0) {
1132
- sendSlashText(pi, "Subagent profiles\n\nNo subagent profiles found in ~/.dm/agent/profiles/dm-subagents/");
1133
- return;
1134
- }
1135
- sendSlashText(pi, `Subagent profiles\n\n${profiles.join("\n")}`);
1136
- },
1137
- });
1138
-
1139
- pi.registerCommand("subagents-load-profile", {
1140
- description: "Load a subagent profile into ~/.dm/agent/settings.json",
1141
- getArgumentCompletions: (prefix) => {
1142
- if (prefix.includes(" ")) return null;
1143
- return listSubagentProfiles()
1144
- .filter((name) => name.startsWith(prefix))
1145
- .map((name) => ({ value: name, label: name }));
1146
- },
1147
- handler: async (args, ctx) => {
1148
- const parsed = parseSingleRequiredArg(args, "Usage: /subagents-load-profile <name>");
1149
- if (!parsed.ok) {
1150
- ctx.ui.notify(parsed.message, "error");
1151
- return;
1152
- }
1153
- try {
1154
- await withSlashStatus(ctx, `Loading profile ${parsed.value}…`, async () => {
1155
- const { profile } = readSubagentProfile(parsed.value);
1156
- const workerModel = getProfileWorkerModel(profile);
1157
- const result = applySubagentProfile(parsed.value);
1158
- const lines = [
1159
- `Loaded subagent profile: ${parsed.value}`,
1160
- `Profile: ${result.filePath}`,
1161
- `Updated: ${result.settingsPath}`,
1162
- ];
1163
-
1164
- if (workerModel && typeof pi.setModel === "function" && typeof ctx.modelRegistry?.find === "function" && typeof ctx.modelRegistry?.getAvailable === "function") {
1165
- const shouldSwitch = await ctx.ui.confirm(
1166
- "",
1167
- `Profile loaded. Also switch this session to the profile worker model?\n\n${workerModel}`,
1168
- );
1169
- if (shouldSwitch) {
1170
- const modelInfo = findModelInfo(workerModel, ctx.modelRegistry.getAvailable().map(toModelInfo));
1171
- const model = modelInfo ? ctx.modelRegistry.find(modelInfo.provider, modelInfo.id) : undefined;
1172
- if (!modelInfo || !model) {
1173
- lines.push(`Could not switch current session model: '${workerModel}' is not available in the current model registry.`);
1174
- } else {
1175
- const success = await pi.setModel(model);
1176
- if (success) lines.push(`Current session model switched to: ${modelInfo.fullId}`);
1177
- else lines.push(`Could not switch current session model to '${workerModel}': no API key or provider access is available.`);
1178
- }
1179
- }
1180
- } else if (workerModel) {
1181
- lines.push(`Profile worker model: ${workerModel}`);
1182
- }
1183
-
1184
- sendSlashText(pi, lines.join("\n"));
1185
- });
1186
- } catch (error) {
1187
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1188
- }
1189
- },
1190
- });
1191
-
1192
- pi.registerCommand("subagents-refresh-provider-models", {
1193
- description: "Refresh the cached model catalog for one provider",
1194
- getArgumentCompletions: makeProviderCompletions(state),
1195
- handler: async (args, ctx) => {
1196
- const trimmed = args.trim();
1197
- const force = /(?:^|\s)--force$/.test(trimmed) || /(?:^|\s)force$/.test(trimmed);
1198
- const withoutForce = trimmed.replace(/(?:^|\s)(?:--force|force)$/, "").trim();
1199
- const parsed = parseSingleRequiredArg(withoutForce, "Usage: /subagents-refresh-provider-models <provider> [--force]");
1200
- if (!parsed.ok) {
1201
- ctx.ui.notify(parsed.message, "error");
1202
- return;
1203
- }
1204
- try {
1205
- await withSlashStatus(ctx, `Refreshing provider models for ${parsed.value}…`, async () => {
1206
- const result = await refreshProviderModelCatalog(pi, ctx, parsed.value, { force, maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });
1207
- const lines = [
1208
- "Provider model catalog",
1209
- `Provider: ${parsed.value}`,
1210
- `Status: ${result.reused ? "fresh cache reused" : "refreshed"}`,
1211
- `File: ${result.filePath}`,
1212
- `Models: ${result.catalog.models.length}`,
1213
- `Refreshed at: ${result.catalog.refreshedAt}`,
1214
- ];
1215
- if (result.heuristicFallbackCount > 0) {
1216
- lines.push(`Warning: ${result.heuristicFallbackCount} model${result.heuristicFallbackCount === 1 ? " was" : "s were"} classified with name heuristics fallback.`);
1217
- }
1218
- sendSlashText(pi, lines.join("\n"));
1219
- });
1220
- } catch (error) {
1221
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1222
- }
1223
- },
1224
- });
1225
-
1226
- pi.registerCommand("subagents-generate-profiles", {
1227
- description: "Generate <provider>.quota and <provider>.quality subagent profiles",
1228
- getArgumentCompletions: makeProviderCompletions(state),
1229
- handler: async (args, ctx) => {
1230
- const parsed = parseSingleRequiredArg(args, "Usage: /subagents-generate-profiles <provider>");
1231
- if (!parsed.ok) {
1232
- ctx.ui.notify(parsed.message, "error");
1233
- return;
1234
- }
1235
- try {
1236
- await withSlashStatus(ctx, `Generating profiles for ${parsed.value}…`, async () => {
1237
- const result = await generateProfilesForProvider(pi, ctx, parsed.value, { maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });
1238
- const lines = [
1239
- "Generated subagent profiles",
1240
- `Provider: ${parsed.value}`,
1241
- `Catalog: ${result.catalogPath}`,
1242
- `Quota: ${result.quotaPath}`,
1243
- ` cheap=${result.quotaModels.cheap}`,
1244
- ` medium=${result.quotaModels.medium}`,
1245
- ` strong=${result.quotaModels.strong}`,
1246
- `Quality: ${result.qualityPath}`,
1247
- ` cheap=${result.qualityModels.cheap}`,
1248
- ` medium=${result.qualityModels.medium}`,
1249
- ` strong=${result.qualityModels.strong}`,
1250
- ];
1251
- if (result.selectedHeuristicFallbackCount > 0) {
1252
- lines.push(`Warning: generated profiles depend on heuristic-only classification for ${result.selectedHeuristicFallbackCount} selected model${result.selectedHeuristicFallbackCount === 1 ? "" : "s"}.`);
1253
- } else if (result.heuristicFallbackCount > 0) {
1254
- lines.push(`Warning: provider catalog still contains ${result.heuristicFallbackCount} heuristic-classified model${result.heuristicFallbackCount === 1 ? "" : "s"}.`);
1255
- }
1256
- sendSlashText(pi, lines.join("\n"));
1257
- });
1258
- } catch (error) {
1259
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1260
- }
1261
- },
1262
- });
1263
-
1264
- pi.registerCommand("subagents-check-profile", {
1265
- description: "Check whether a saved profile still points to usable models",
1266
- getArgumentCompletions: (prefix) => {
1267
- if (prefix.includes(" ")) return null;
1268
- return listSubagentProfiles()
1269
- .filter((name) => name.startsWith(prefix))
1270
- .map((name) => ({ value: name, label: name }));
1271
- },
1272
- handler: async (args, ctx) => {
1273
- const parsed = parseSingleRequiredArg(args, "Usage: /subagents-check-profile <name>");
1274
- if (!parsed.ok) {
1275
- ctx.ui.notify(parsed.message, "error");
1276
- return;
1277
- }
1278
- try {
1279
- await withSlashStatus(ctx, `Checking profile ${parsed.value}…`, async () => {
1280
- const result = await checkSubagentProfile(pi, ctx, parsed.value);
1281
- const lines = [
1282
- "Subagent profile check",
1283
- `Profile: ${result.profileName}`,
1284
- `File: ${result.filePath}`,
1285
- "",
1286
- ...result.results.map((entry) => `${entry.agent} → ${entry.model} — registry ${entry.inRegistry ? "ok" : "missing"}; probe ${entry.probe.status}${entry.probe.message ? ` (${entry.probe.message.split(/\r?\n/, 1)[0]})` : ""}`),
1287
- ];
1288
- sendSlashText(pi, lines.join("\n"));
1289
- });
1290
- } catch (error) {
1291
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1292
- }
1293
- },
1294
- });
1295
-
1296
- }