@phnx-labs/agents-cli 1.20.34 → 1.20.36

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 (291) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +185 -21
  3. package/README.md +13 -5
  4. package/dist/commands/audit.d.ts +14 -0
  5. package/dist/commands/audit.js +68 -0
  6. package/dist/commands/browser.js +82 -8
  7. package/dist/commands/check.d.ts +15 -0
  8. package/dist/commands/check.js +84 -0
  9. package/dist/commands/cloud.js +143 -3
  10. package/dist/commands/computer-actions.d.ts +15 -0
  11. package/dist/commands/computer-actions.js +120 -22
  12. package/dist/commands/computer.js +81 -0
  13. package/dist/commands/cost.js +17 -12
  14. package/dist/commands/daemon.js +4 -1
  15. package/dist/commands/doctor.d.ts +1 -0
  16. package/dist/commands/doctor.js +54 -97
  17. package/dist/commands/events.d.ts +16 -0
  18. package/dist/commands/events.js +173 -0
  19. package/dist/commands/exec.d.ts +19 -0
  20. package/dist/commands/exec.js +283 -12
  21. package/dist/commands/hosts.js +37 -29
  22. package/dist/commands/inspect.d.ts +2 -0
  23. package/dist/commands/inspect.js +62 -16
  24. package/dist/commands/lock.d.ts +12 -0
  25. package/dist/commands/lock.js +70 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +139 -0
  28. package/dist/commands/message.d.ts +15 -0
  29. package/dist/commands/message.js +56 -0
  30. package/dist/commands/models.d.ts +3 -0
  31. package/dist/commands/models.js +27 -8
  32. package/dist/commands/permissions.js +9 -2
  33. package/dist/commands/repo.d.ts +34 -0
  34. package/dist/commands/repo.js +243 -65
  35. package/dist/commands/resource-view.d.ts +20 -0
  36. package/dist/commands/resource-view.js +90 -28
  37. package/dist/commands/routines.js +101 -5
  38. package/dist/commands/secrets-migrate.js +106 -57
  39. package/dist/commands/secrets.d.ts +31 -18
  40. package/dist/commands/secrets.js +196 -91
  41. package/dist/commands/serve.d.ts +10 -0
  42. package/dist/commands/serve.js +37 -0
  43. package/dist/commands/sessions-inject.d.ts +14 -0
  44. package/dist/commands/sessions-inject.js +111 -0
  45. package/dist/commands/sessions-picker.d.ts +2 -0
  46. package/dist/commands/sessions-picker.js +24 -3
  47. package/dist/commands/sessions-resume.d.ts +2 -0
  48. package/dist/commands/sessions-resume.js +221 -0
  49. package/dist/commands/sessions-tail.d.ts +10 -0
  50. package/dist/commands/sessions-tail.js +11 -0
  51. package/dist/commands/sessions.d.ts +143 -1
  52. package/dist/commands/sessions.js +628 -80
  53. package/dist/commands/setup.js +4 -2
  54. package/dist/commands/sync.d.ts +3 -1
  55. package/dist/commands/sync.js +156 -4
  56. package/dist/commands/teams.js +217 -0
  57. package/dist/commands/versions.js +2 -4
  58. package/dist/commands/view.d.ts +3 -0
  59. package/dist/commands/view.js +19 -8
  60. package/dist/commands/wallet.d.ts +6 -0
  61. package/dist/commands/wallet.js +22 -5
  62. package/dist/commands/watchdog.d.ts +18 -0
  63. package/dist/commands/watchdog.js +238 -0
  64. package/dist/index.js +82 -32
  65. package/dist/lib/agents.js +30 -9
  66. package/dist/lib/audit/log.d.ts +92 -0
  67. package/dist/lib/audit/log.js +177 -0
  68. package/dist/lib/auto-pull.js +2 -1
  69. package/dist/lib/browser/cdp.js +5 -1
  70. package/dist/lib/browser/chrome.d.ts +10 -0
  71. package/dist/lib/browser/chrome.js +18 -7
  72. package/dist/lib/browser/drivers/ssh.d.ts +8 -0
  73. package/dist/lib/browser/drivers/ssh.js +77 -16
  74. package/dist/lib/browser/har.d.ts +84 -0
  75. package/dist/lib/browser/har.js +77 -0
  76. package/dist/lib/browser/ipc.d.ts +3 -0
  77. package/dist/lib/browser/ipc.js +37 -12
  78. package/dist/lib/browser/profiles.d.ts +1 -1
  79. package/dist/lib/browser/profiles.js +8 -10
  80. package/dist/lib/browser/refs.d.ts +65 -0
  81. package/dist/lib/browser/refs.js +73 -1
  82. package/dist/lib/browser/runtime-state.js +1 -0
  83. package/dist/lib/browser/service.d.ts +38 -2
  84. package/dist/lib/browser/service.js +183 -23
  85. package/dist/lib/browser/types.d.ts +14 -1
  86. package/dist/lib/budget/live-cloud.d.ts +42 -0
  87. package/dist/lib/budget/live-cloud.js +79 -0
  88. package/dist/lib/budget/live-team.d.ts +31 -0
  89. package/dist/lib/budget/live-team.js +115 -0
  90. package/dist/lib/cloud/codex.js +4 -0
  91. package/dist/lib/cloud/rush.d.ts +12 -1
  92. package/dist/lib/cloud/rush.js +13 -3
  93. package/dist/lib/cloud/types.d.ts +9 -0
  94. package/dist/lib/computer/dispatch.d.ts +8 -0
  95. package/dist/lib/computer/dispatch.js +125 -0
  96. package/dist/lib/computer/loop.d.ts +62 -0
  97. package/dist/lib/computer/loop.js +98 -0
  98. package/dist/lib/computer/model.d.ts +44 -0
  99. package/dist/lib/computer/model.js +157 -0
  100. package/dist/lib/concurrency.d.ts +19 -0
  101. package/dist/lib/concurrency.js +33 -0
  102. package/dist/lib/daemon.d.ts +92 -0
  103. package/dist/lib/daemon.js +222 -18
  104. package/dist/lib/devices/connect.d.ts +3 -2
  105. package/dist/lib/devices/connect.js +5 -3
  106. package/dist/lib/devices/registry.d.ts +7 -0
  107. package/dist/lib/devices/registry.js +24 -0
  108. package/dist/lib/devices/tailscale.js +1 -1
  109. package/dist/lib/drift.d.ts +52 -0
  110. package/dist/lib/drift.js +112 -0
  111. package/dist/lib/events.d.ts +10 -3
  112. package/dist/lib/events.js +101 -24
  113. package/dist/lib/exec.d.ts +17 -0
  114. package/dist/lib/exec.js +100 -12
  115. package/dist/lib/git.d.ts +27 -0
  116. package/dist/lib/git.js +56 -1
  117. package/dist/lib/hooks/cache.d.ts +6 -0
  118. package/dist/lib/hooks/cache.js +54 -12
  119. package/dist/lib/hooks.d.ts +27 -0
  120. package/dist/lib/hooks.js +135 -10
  121. package/dist/lib/hosts/dispatch.d.ts +15 -0
  122. package/dist/lib/hosts/dispatch.js +45 -13
  123. package/dist/lib/hosts/logs.d.ts +16 -0
  124. package/dist/lib/hosts/logs.js +74 -0
  125. package/dist/lib/hosts/option.js +1 -1
  126. package/dist/lib/hosts/passthrough.js +3 -1
  127. package/dist/lib/hosts/progress.d.ts +66 -0
  128. package/dist/lib/hosts/progress.js +125 -17
  129. package/dist/lib/hosts/ready.d.ts +51 -7
  130. package/dist/lib/hosts/ready.js +98 -25
  131. package/dist/lib/hosts/reconcile.d.ts +53 -0
  132. package/dist/lib/hosts/reconcile.js +81 -0
  133. package/dist/lib/hosts/registry.d.ts +19 -2
  134. package/dist/lib/hosts/registry.js +58 -2
  135. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  136. package/dist/lib/hosts/remote-cmd.js +70 -1
  137. package/dist/lib/hosts/remote-os.d.ts +17 -0
  138. package/dist/lib/hosts/remote-os.js +30 -0
  139. package/dist/lib/hosts/session-index.d.ts +34 -0
  140. package/dist/lib/hosts/session-index.js +56 -0
  141. package/dist/lib/hosts/tasks.d.ts +22 -0
  142. package/dist/lib/hosts/tasks.js +29 -0
  143. package/dist/lib/lock.d.ts +93 -0
  144. package/dist/lib/lock.js +207 -0
  145. package/dist/lib/loop.js +16 -1
  146. package/dist/lib/machine-id.d.ts +21 -0
  147. package/dist/lib/machine-id.js +26 -0
  148. package/dist/lib/mailbox-target.d.ts +36 -0
  149. package/dist/lib/mailbox-target.js +45 -0
  150. package/dist/lib/mailbox.d.ts +47 -0
  151. package/dist/lib/mailbox.js +194 -0
  152. package/dist/lib/mcp.d.ts +5 -0
  153. package/dist/lib/mcp.js +24 -8
  154. package/dist/lib/migrate.d.ts +19 -0
  155. package/dist/lib/migrate.js +134 -26
  156. package/dist/lib/overdue.js +3 -0
  157. package/dist/lib/permissions.d.ts +4 -0
  158. package/dist/lib/permissions.js +35 -0
  159. package/dist/lib/picker.d.ts +26 -0
  160. package/dist/lib/picker.js +127 -0
  161. package/dist/lib/platform/exec.d.ts +46 -0
  162. package/dist/lib/platform/exec.js +74 -0
  163. package/dist/lib/platform/process.d.ts +31 -0
  164. package/dist/lib/platform/process.js +34 -1
  165. package/dist/lib/platform/winpath.js +2 -0
  166. package/dist/lib/plugin-marketplace.d.ts +30 -0
  167. package/dist/lib/plugin-marketplace.js +215 -2
  168. package/dist/lib/plugins.d.ts +5 -0
  169. package/dist/lib/plugins.js +61 -10
  170. package/dist/lib/profiles.d.ts +25 -0
  171. package/dist/lib/profiles.js +22 -6
  172. package/dist/lib/pty-client.js +2 -1
  173. package/dist/lib/rotate.d.ts +61 -0
  174. package/dist/lib/rotate.js +52 -0
  175. package/dist/lib/routines.d.ts +40 -2
  176. package/dist/lib/routines.js +66 -8
  177. package/dist/lib/runner.d.ts +11 -2
  178. package/dist/lib/runner.js +49 -7
  179. package/dist/lib/scheduler.js +6 -1
  180. package/dist/lib/secrets/bundles.d.ts +60 -4
  181. package/dist/lib/secrets/bundles.js +131 -12
  182. package/dist/lib/secrets/filestore.d.ts +3 -0
  183. package/dist/lib/secrets/filestore.js +42 -16
  184. package/dist/lib/secrets/index.d.ts +43 -2
  185. package/dist/lib/secrets/index.js +102 -3
  186. package/dist/lib/secrets/mcp.d.ts +93 -0
  187. package/dist/lib/secrets/mcp.js +205 -0
  188. package/dist/lib/secrets/remote.js +12 -5
  189. package/dist/lib/secrets/sync.js +83 -4
  190. package/dist/lib/secrets/windows.js +14 -3
  191. package/dist/lib/serve/data.d.ts +81 -0
  192. package/dist/lib/serve/data.js +91 -0
  193. package/dist/lib/serve/page.d.ts +7 -0
  194. package/dist/lib/serve/page.js +140 -0
  195. package/dist/lib/serve/server.d.ts +46 -0
  196. package/dist/lib/serve/server.js +115 -0
  197. package/dist/lib/session/active.d.ts +61 -0
  198. package/dist/lib/session/active.js +196 -17
  199. package/dist/lib/session/discover.d.ts +37 -0
  200. package/dist/lib/session/discover.js +204 -27
  201. package/dist/lib/session/inject.d.ts +18 -0
  202. package/dist/lib/session/inject.js +21 -0
  203. package/dist/lib/session/parse.d.ts +6 -0
  204. package/dist/lib/session/parse.js +286 -2
  205. package/dist/lib/session/pid-registry.d.ts +22 -0
  206. package/dist/lib/session/pid-registry.js +104 -0
  207. package/dist/lib/session/provenance.d.ts +14 -2
  208. package/dist/lib/session/provenance.js +39 -8
  209. package/dist/lib/session/remote-active.d.ts +26 -0
  210. package/dist/lib/session/remote-active.js +153 -0
  211. package/dist/lib/session/remote-list.d.ts +51 -0
  212. package/dist/lib/session/remote-list.js +213 -0
  213. package/dist/lib/session/remote.d.ts +7 -1
  214. package/dist/lib/session/remote.js +18 -8
  215. package/dist/lib/session/render.js +1 -1
  216. package/dist/lib/session/sync/config.d.ts +1 -7
  217. package/dist/lib/session/sync/config.js +4 -11
  218. package/dist/lib/session/types.d.ts +18 -1
  219. package/dist/lib/session/types.js +1 -1
  220. package/dist/lib/shims.d.ts +44 -6
  221. package/dist/lib/shims.js +143 -57
  222. package/dist/lib/ssh-exec.d.ts +26 -3
  223. package/dist/lib/ssh-exec.js +47 -3
  224. package/dist/lib/ssh-tunnel.d.ts +24 -5
  225. package/dist/lib/ssh-tunnel.js +62 -63
  226. package/dist/lib/staleness/writers/hooks.js +1 -1
  227. package/dist/lib/startup/command-registry.d.ts +8 -0
  228. package/dist/lib/startup/command-registry.js +17 -1
  229. package/dist/lib/state.d.ts +13 -0
  230. package/dist/lib/state.js +103 -9
  231. package/dist/lib/sync-umbrella.d.ts +14 -7
  232. package/dist/lib/sync-umbrella.js +17 -9
  233. package/dist/lib/teams/forEach.d.ts +110 -0
  234. package/dist/lib/teams/forEach.js +186 -0
  235. package/dist/lib/teams/index.d.ts +1 -0
  236. package/dist/lib/teams/index.js +1 -0
  237. package/dist/lib/teams/pr-watch.d.ts +226 -0
  238. package/dist/lib/teams/pr-watch.js +371 -0
  239. package/dist/lib/teams/registry.js +25 -9
  240. package/dist/lib/teams/supervisor.d.ts +14 -1
  241. package/dist/lib/teams/supervisor.js +19 -0
  242. package/dist/lib/teams/worktree.d.ts +9 -0
  243. package/dist/lib/teams/worktree.js +32 -0
  244. package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
  245. package/dist/lib/terminal/backends/ghostty.js +69 -0
  246. package/dist/lib/terminal/backends/index.d.ts +19 -0
  247. package/dist/lib/terminal/backends/index.js +31 -0
  248. package/dist/lib/terminal/backends/iterm.d.ts +6 -0
  249. package/dist/lib/terminal/backends/iterm.js +62 -0
  250. package/dist/lib/terminal/backends/tmux.d.ts +14 -0
  251. package/dist/lib/terminal/backends/tmux.js +23 -0
  252. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  253. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  254. package/dist/lib/terminal/engine.d.ts +39 -0
  255. package/dist/lib/terminal/engine.js +54 -0
  256. package/dist/lib/terminal/index.d.ts +17 -0
  257. package/dist/lib/terminal/index.js +8 -0
  258. package/dist/lib/terminal/inject.d.ts +204 -0
  259. package/dist/lib/terminal/inject.js +247 -0
  260. package/dist/lib/terminal/policy.d.ts +11 -0
  261. package/dist/lib/terminal/policy.js +11 -0
  262. package/dist/lib/terminal/quote.d.ts +11 -0
  263. package/dist/lib/terminal/quote.js +13 -0
  264. package/dist/lib/terminal/resolve.d.ts +64 -0
  265. package/dist/lib/terminal/resolve.js +90 -0
  266. package/dist/lib/terminal/shell.d.ts +6 -0
  267. package/dist/lib/terminal/shell.js +23 -0
  268. package/dist/lib/terminal/transport.d.ts +15 -0
  269. package/dist/lib/terminal/transport.js +40 -0
  270. package/dist/lib/terminal/types.d.ts +59 -0
  271. package/dist/lib/terminal/types.js +13 -0
  272. package/dist/lib/triggers/webhook.d.ts +85 -0
  273. package/dist/lib/triggers/webhook.js +141 -0
  274. package/dist/lib/types.d.ts +7 -0
  275. package/dist/lib/versions.d.ts +30 -3
  276. package/dist/lib/versions.js +129 -18
  277. package/dist/lib/watchdog/index.d.ts +3 -0
  278. package/dist/lib/watchdog/index.js +5 -0
  279. package/dist/lib/watchdog/read.d.ts +35 -0
  280. package/dist/lib/watchdog/read.js +149 -0
  281. package/dist/lib/watchdog/runner.d.ts +127 -0
  282. package/dist/lib/watchdog/runner.js +322 -0
  283. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  284. package/dist/lib/watchdog/watchdog.js +166 -0
  285. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  286. package/dist/lib/watchdog/watchdogTail.js +154 -0
  287. package/dist/lib/whats-new.d.ts +9 -0
  288. package/dist/lib/whats-new.js +35 -0
  289. package/dist/lib/workflows.d.ts +166 -0
  290. package/dist/lib/workflows.js +193 -0
  291. package/package.json +5 -4
@@ -11,6 +11,13 @@ import * as yaml from 'yaml';
11
11
  import { capableAgents } from './capabilities.js';
12
12
  import { getProjectAgentsDir, getSystemWorkflowsDir, getUserWorkflowsDir, getTrashWorkflowsDir, getEnabledExtraRepos, } from './state.js';
13
13
  import { listInstalledVersions, getVersionHomePath } from './versions.js';
14
+ /**
15
+ * Hard upper bound on items a single `for_each` expands, absent an explicit
16
+ * `max_items`. A guard against a runaway producer spawning unbounded teammates
17
+ * (acceptance criterion in issue #343). Anthropic's Dynamic Workflows cap at
18
+ * 1000; we default lower and let authors raise it deliberately.
19
+ */
20
+ export const DEFAULT_FOR_EACH_CAP = 256;
14
21
  /** Parse WORKFLOW.md frontmatter from a workflow directory. Returns null if invalid. */
15
22
  export function parseWorkflowFrontmatter(workflowDir) {
16
23
  const workflowMdPath = path.join(workflowDir, 'WORKFLOW.md');
@@ -42,6 +49,7 @@ export function parseWorkflowFrontmatter(workflowDir) {
42
49
  allowedAgents: asStringArray(parsed.allowedAgents),
43
50
  secrets: asStringArray(parsed.secrets),
44
51
  loop: parseLoopBlock(parsed.loop),
52
+ forEach: parseForEachBlock(parsed.for_each),
45
53
  };
46
54
  }
47
55
  catch {
@@ -83,6 +91,152 @@ export function parseLoopBlock(v) {
83
91
  out.interval = raw.interval;
84
92
  return Object.keys(out).length > 0 ? out : undefined;
85
93
  }
94
+ /** A finite positive integer, or undefined. Shared guard for count-like fields. */
95
+ function asPosInt(v) {
96
+ return typeof v === 'number' && Number.isFinite(v) && Number.isInteger(v) && v > 0
97
+ ? v
98
+ : undefined;
99
+ }
100
+ /** A non-empty trimmed string, or undefined. */
101
+ function asNonEmptyString(v) {
102
+ return typeof v === 'string' && v.trim().length > 0 ? v : undefined;
103
+ }
104
+ /**
105
+ * Defensively coerce a frontmatter `verify:` sub-block into a ForEachVerifySpec.
106
+ *
107
+ * Requires an `agent`; drops the whole block otherwise (a verify panel with no
108
+ * skeptic is meaningless). `votes` defaults to 1 (a single confirmation) and
109
+ * `keep_if` to `majority`; both are validated against their allowed shapes.
110
+ */
111
+ export function parseVerifyBlock(v) {
112
+ if (!v || typeof v !== 'object' || Array.isArray(v))
113
+ return undefined;
114
+ const raw = v;
115
+ const agent = asNonEmptyString(raw.agent);
116
+ if (!agent)
117
+ return undefined;
118
+ const keepIf = raw.keep_if;
119
+ const out = {
120
+ agent,
121
+ votes: asPosInt(raw.votes) ?? 1,
122
+ keep_if: keepIf === 'all' || keepIf === 'any' || keepIf === 'majority'
123
+ ? keepIf
124
+ : 'majority',
125
+ };
126
+ const prompt = asNonEmptyString(raw.prompt);
127
+ if (prompt)
128
+ out.prompt = prompt;
129
+ return out;
130
+ }
131
+ /**
132
+ * Defensively coerce a frontmatter `for_each:` block into a ForEachSpec (issue
133
+ * #343). Mirrors `parseLoopBlock`'s discipline: a block missing the two
134
+ * load-bearing fields (`agent` + `prompt`) drops to undefined rather than
135
+ * passing a half-formed spec to the expander. Optional numeric/verify fields
136
+ * are individually validated and dropped when malformed.
137
+ */
138
+ export function parseForEachBlock(v) {
139
+ if (!v || typeof v !== 'object' || Array.isArray(v))
140
+ return undefined;
141
+ const raw = v;
142
+ const agent = asNonEmptyString(raw.agent);
143
+ const prompt = asNonEmptyString(raw.prompt);
144
+ if (!agent || !prompt)
145
+ return undefined;
146
+ const out = { agent, prompt };
147
+ const produce = asNonEmptyString(raw.produce);
148
+ if (produce)
149
+ out.produce = produce;
150
+ // `for_each: ${step}` references a prior step's list. Accept either the
151
+ // snake-case `for_each` key or an explicit `items_ref`.
152
+ const itemsRef = asNonEmptyString(raw.for_each) ?? asNonEmptyString(raw.items_ref);
153
+ if (itemsRef)
154
+ out.itemsRef = itemsRef;
155
+ const name = asNonEmptyString(raw.name);
156
+ if (name)
157
+ out.name = name;
158
+ const concurrency = asPosInt(raw.concurrency);
159
+ if (concurrency)
160
+ out.concurrency = concurrency;
161
+ const maxItems = asPosInt(raw.max_items);
162
+ if (maxItems)
163
+ out.max_items = maxItems;
164
+ const verify = parseVerifyBlock(raw.verify);
165
+ if (verify)
166
+ out.verify = verify;
167
+ return out;
168
+ }
169
+ /**
170
+ * Substitute `{{item}}` / `{{index}}` (and 1-based `{{n}}`) in a prompt
171
+ * template. Unknown `{{...}}` tokens are left intact so a template can carry
172
+ * placeholders the caller resolves elsewhere.
173
+ */
174
+ export function renderForEachTemplate(template, item, index) {
175
+ return template
176
+ .replace(/\{\{\s*item\s*\}\}/g, item)
177
+ .replace(/\{\{\s*index\s*\}\}/g, String(index))
178
+ .replace(/\{\{\s*n\s*\}\}/g, String(index + 1));
179
+ }
180
+ /**
181
+ * Expand a `for_each` spec against a producer's output into concrete teammate
182
+ * descriptors (issue #343) — the heart of the declarative fan-out.
183
+ *
184
+ * Pure and deterministic: no I/O, no spawning. `src/lib/teams/forEach.ts`
185
+ * feeds the result to `AgentManager.spawn`, staging each descriptor into the
186
+ * supervisor's existing mid-flight-add path — so this reuses the dynamic-DAG
187
+ * substrate rather than introducing a new engine.
188
+ *
189
+ * For N produced items (capped at `spec.max_items` / `DEFAULT_FOR_EACH_CAP`):
190
+ * - one `stage` teammate per item, depending on `producerName` if given;
191
+ * - when `verify` is set, `votes` `verify` teammates per item, each
192
+ * depending on that item's stage teammate.
193
+ *
194
+ * Names are unique (`<base>-<n>` and `<base>-<n>-verify-<v>`) so `--after`
195
+ * linkage and the teams cycle check carry over unchanged.
196
+ */
197
+ export function expandForEach(spec, items, opts = {}) {
198
+ const cap = spec.max_items ?? DEFAULT_FOR_EACH_CAP;
199
+ const producedCount = items.length;
200
+ const used = items.slice(0, cap);
201
+ const base = spec.name ?? 'item';
202
+ const teammates = [];
203
+ used.forEach((item, itemIndex) => {
204
+ const stageName = `${base}-${itemIndex + 1}`;
205
+ teammates.push({
206
+ role: 'stage',
207
+ name: stageName,
208
+ agentType: spec.agent,
209
+ prompt: renderForEachTemplate(spec.prompt, item, itemIndex),
210
+ after: opts.producerName ? [opts.producerName] : [],
211
+ item,
212
+ itemIndex,
213
+ });
214
+ if (spec.verify) {
215
+ const verifyPrompt = spec.verify.prompt ?? spec.prompt;
216
+ for (let vote = 1; vote <= spec.verify.votes; vote++) {
217
+ teammates.push({
218
+ role: 'verify',
219
+ name: `${stageName}-verify-${vote}`,
220
+ agentType: spec.verify.agent,
221
+ prompt: renderForEachTemplate(verifyPrompt, item, itemIndex),
222
+ after: [stageName],
223
+ item,
224
+ itemIndex,
225
+ vote,
226
+ votes: spec.verify.votes,
227
+ keep_if: spec.verify.keep_if,
228
+ });
229
+ }
230
+ }
231
+ });
232
+ return {
233
+ teammates,
234
+ producedCount,
235
+ usedCount: used.length,
236
+ truncated: producedCount - used.length,
237
+ cap,
238
+ };
239
+ }
86
240
  /**
87
241
  * Decide which subagent .md stems a workflow may use, given the discovered
88
242
  * subagent files and the parsed `allowedAgents` frontmatter. This is the
@@ -110,6 +264,45 @@ export function resolveAllowedSubagents(available, allowedAgents) {
110
264
  missing: allowedAgents.filter(a => !present.has(a)),
111
265
  };
112
266
  }
267
+ /**
268
+ * Prune stale workflow-managed subagent files from the shared per-agent agents
269
+ * dir before a scoped run writes the permitted set (issue #401, follow-up to
270
+ * #324). A prior *unrestricted* run of a workflow copies every subagent
271
+ * definition into the shared `~/.claude/agents/` dir; a later run that declares
272
+ * `allowedAgents:` copies only the permitted ones but never removes the
273
+ * leftovers — so an unlisted subagent stays on disk and remains dispatchable,
274
+ * silently defeating the fail-closed scope.
275
+ *
276
+ * Fail-closed fix (mirrors how `cleanupWorkflowMcpConfig` only tears down what
277
+ * the workflow itself created): remove any file that (a) belongs to THIS
278
+ * workflow's subagents/ — matched by filename, i.e. the workflow-managed
279
+ * universe — and (b) is NOT in the permitted set. A user's own hand-placed
280
+ * subagent shares no name with a workflow subagent file, so it is never
281
+ * touched. Permitted files are left in place; the caller (re)copies them.
282
+ *
283
+ * `workflowSubagentFiles` are the .md filenames in the workflow's subagents/
284
+ * dir (e.g. `security.md`); `allowedStems` are the permitted stems from
285
+ * `resolveAllowedSubagents`. Returns the filenames actually removed.
286
+ */
287
+ export function pruneStaleWorkflowSubagents(sharedAgentsDir, workflowSubagentFiles, allowedStems) {
288
+ if (!fs.existsSync(sharedAgentsDir))
289
+ return [];
290
+ const allow = new Set(allowedStems);
291
+ const pruned = [];
292
+ for (const file of workflowSubagentFiles) {
293
+ if (!file.endsWith('.md'))
294
+ continue;
295
+ const stem = file.replace(/\.md$/, '');
296
+ if (allow.has(stem))
297
+ continue; // permitted → the copy step will (re)write it
298
+ const target = path.join(sharedAgentsDir, file);
299
+ if (fs.existsSync(target)) {
300
+ fs.rmSync(target, { force: true });
301
+ pruned.push(file);
302
+ }
303
+ }
304
+ return pruned;
305
+ }
113
306
  /** Count subagent .md files in a workflow's subagents/ directory. */
114
307
  export function countWorkflowSubagents(workflowDir) {
115
308
  const subagentsDir = path.join(workflowDir, 'subagents');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.34",
3
+ "version": "1.20.36",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -46,6 +46,7 @@
46
46
  "scripts": {
47
47
  "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ \"$(uname)\" = \"Darwin\" ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ \"$(uname)\" = \"Darwin\" ] && [ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
48
48
  "build:bin": "scripts/build-bin.sh",
49
+ "prepare": "npm run build",
49
50
  "prepack": "scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
50
51
  "postinstall": "node scripts/postinstall.js",
51
52
  "dev": "tsx src/index.ts",
@@ -72,7 +73,7 @@
72
73
  "multi-agent"
73
74
  ],
74
75
  "author": "Phoenix Labs",
75
- "license": "MIT",
76
+ "license": "Apache-2.0",
76
77
  "engines": {
77
78
  "node": ">=22.5.0",
78
79
  "npm": ">=9"
@@ -102,8 +103,8 @@
102
103
  "devDependencies": {
103
104
  "@types/diff": "8.0.0",
104
105
  "@types/marked-terminal": "6.1.1",
105
- "@types/node": "25.9.3",
106
- "tsx": "4.22.4",
106
+ "@types/node": "26.1.0",
107
+ "tsx": "4.23.0",
107
108
  "typescript": "6.0.3",
108
109
  "vitest": "4.1.9"
109
110
  }