@duckmind/dm-darwin-x64 0.43.4 → 0.43.8

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 (251) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +241 -49
  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/index.js +20 -0
  126. package/extensions/greedysearch-dm/package.json +1 -22
  127. package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
  128. package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
  129. package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
  130. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  131. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  132. package/extensions/greedysearch-dm/src/types.js +0 -0
  133. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  134. package/package.json +1 -1
  135. package/extensions/dm-9router-ext/src/index.ts +0 -541
  136. package/extensions/dm-ask-user/index.ts +0 -857
  137. package/extensions/dm-context/src/context.ts +0 -158
  138. package/extensions/dm-context/src/index.ts +0 -439
  139. package/extensions/dm-context/src/utils.ts +0 -6
  140. package/extensions/dm-fff/src/index.ts +0 -1023
  141. package/extensions/dm-fff/src/query.ts +0 -87
  142. package/extensions/dm-goal/src/goal.ts +0 -1073
  143. package/extensions/dm-grill-me/index.ts +0 -1085
  144. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  145. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  146. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  147. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  148. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  149. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  150. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  151. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  152. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  153. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  154. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  155. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  156. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  157. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  158. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  159. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  160. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  161. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  162. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  163. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  164. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  165. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  166. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  167. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  168. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  169. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  170. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  171. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  172. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  173. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  174. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  175. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  176. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  177. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  178. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  179. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  180. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  181. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  182. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  183. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  184. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  185. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  186. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  187. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  188. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  189. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  190. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  191. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  192. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  193. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  194. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  195. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  196. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  197. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  198. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  199. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  200. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  201. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  202. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  203. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  204. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  205. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  206. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  207. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  208. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  209. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  210. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  211. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  212. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  213. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  214. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  215. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  216. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  217. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  218. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  219. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  220. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  221. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  222. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  223. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  224. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  225. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  226. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  227. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  228. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  229. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  230. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  231. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  232. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  233. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  234. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  235. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  236. package/extensions/dm-tasks/src/index.ts +0 -1145
  237. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  238. package/extensions/dm-tasks/src/task-store.ts +0 -305
  239. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  240. package/extensions/dm-tasks/src/types.ts +0 -40
  241. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  242. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  243. package/extensions/dm-usage/index.ts +0 -1718
  244. package/extensions/greedysearch-dm/index.ts +0 -177
  245. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  246. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  247. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  248. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  249. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  250. package/extensions/greedysearch-dm/src/types.ts +0 -110
  251. 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
- }