one-code-extension 0.1.4 → 0.3.1

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 (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +222 -177
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +128 -0
  8. package/docs/guide/configuration.md +173 -0
  9. package/docs/guide/differences-from-claude-code.md +152 -0
  10. package/docs/guide/doctor.md +88 -0
  11. package/docs/guide/hooks.md +111 -0
  12. package/docs/guide/installation.md +126 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
  14. package/docs/guide/providers-and-models.md +196 -0
  15. package/docs/guide/reference.md +226 -0
  16. package/docs/guide/sessions-and-context.md +192 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +178 -0
  18. package/docs/guide/subagents-and-workflows.md +195 -0
  19. package/docs/guide/tasks-and-background-work.md +113 -0
  20. package/docs/guide/terminal-interface.md +157 -0
  21. package/docs/guide/tools.md +177 -0
  22. package/docs/guide/troubleshooting.md +177 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -217
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -15,14 +15,19 @@ import { randomBytes } from "node:crypto";
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import os from "node:os";
17
17
  import { join } from "node:path";
18
+ import type { PermissionBridge } from "../permissions/subagent-gate.ts";
19
+ import type { HookBridge } from "../hooks/subagent-bridge.ts";
20
+ import type { SubagentDefault } from "../subagents/default-model.ts";
18
21
  import { AgentRunner } from "./agent-session.ts";
19
- import { createScriptGlobals, MAX_CONCURRENCY, MAX_AGENTS_PER_RUN, type ScriptRunState } from "./globals.ts";
22
+ import { createRunAdmission, createScriptGlobals, MAX_CONCURRENCY, type ScriptRunState } from "./globals.ts";
20
23
  import { appendJournal, readJournal, ReplayCursor } from "./journal.ts";
24
+ import { AgentRecordStore, previewValue } from "./records.ts";
21
25
  import { findSavedWorkflow } from "./saved-workflows.ts";
22
26
  import { parseWorkflowScript } from "./script-source.ts";
23
27
  import { runWorkflowScript } from "./vm-runtime.ts";
24
28
  import type { RunProgressEvent, RunStatus, WorkflowMeta } from "./types.ts";
25
29
  import { WorkflowScriptError } from "./types.ts";
30
+ import type { ViewerRunSnapshot } from "./viewer.ts";
26
31
 
27
32
  const WALL_CLOCK_CAP_MS = 30 * 60 * 1000;
28
33
  const RECENT_EVENTS_CAP = 200;
@@ -36,8 +41,18 @@ export interface StartRunOptions {
36
41
  cwd: string;
37
42
  sessionDir: string;
38
43
  defaultModel: unknown;
39
- configuredDefaultModel?: string;
44
+ configuredDefault?: SubagentDefault;
40
45
  defaultEffort?: string;
46
+ /** Parent permission bridge, threaded into every agent's gate (see AgentRunnerOptions). */
47
+ getPermissionBridge?: () => PermissionBridge | undefined;
48
+ /** The parent hooks extension's bridge (hooks/subagent-bridge.ts), threaded into every workflow agent. */
49
+ getHookBridge?: () => HookBridge | undefined;
50
+ /** Each finished agent's dollar cost, for the footer's all-in total (review S13). */
51
+ onUsage?: (cost: number) => void;
52
+ /** Models this account cannot run, learned this session (lib/model-unusable.ts); the runner resolves around them. */
53
+ unusableModels?: () => ReadonlySet<string>;
54
+ /** An agent's provider refused its model: published so every picker skips it (lib/model-unusable.ts). */
55
+ onModelUnusable?: (model: string, reason: string) => void;
41
56
  }
42
57
 
43
58
  export class RunHandle extends EventEmitter {
@@ -47,6 +62,8 @@ export class RunHandle extends EventEmitter {
47
62
  readonly startedAt = Date.now();
48
63
  finishedAt?: number;
49
64
  readonly recentEvents: string[] = [];
65
+ /** Per-agent records folded from the event stream (the viewer's data). */
66
+ readonly agents = new AgentRecordStore();
50
67
  state?: ScriptRunState;
51
68
  /** Settles when the run reaches a terminal status (never rejects). */
52
69
  finished!: Promise<RunHandle>;
@@ -75,7 +92,13 @@ export class RunHandle extends EventEmitter {
75
92
  return this.controller.signal;
76
93
  }
77
94
 
95
+ /** The one place the run's signal is fired: every agent, child worker and script hangs off it. */
96
+ private stopWork(): void {
97
+ this.controller.abort();
98
+ }
99
+
78
100
  record(event: RunProgressEvent): void {
101
+ this.agents.apply(event);
79
102
  const line = formatEvent(event);
80
103
  if (line) {
81
104
  this.recentEvents.push(line);
@@ -89,7 +112,7 @@ export class RunHandle extends EventEmitter {
89
112
  // First reason wins — a user stop shouldn't be masked by the script's
90
113
  // consequent "aborted" error.
91
114
  this.errorMessage ??= reason;
92
- this.controller.abort();
115
+ this.stopWork();
93
116
  }
94
117
 
95
118
  finish(status: RunStatus, result?: unknown, errorMessage?: string): void {
@@ -98,6 +121,9 @@ export class RunHandle extends EventEmitter {
98
121
  this.result = result;
99
122
  this.errorMessage ??= errorMessage;
100
123
  this.finishedAt = Date.now();
124
+ // A script can return without awaiting every agent()/workflow() promise.
125
+ // Terminal runs must not leave those agents or child workers running.
126
+ this.stopWork();
101
127
  this.emit("done", this);
102
128
  this.resolveFinished(this);
103
129
  }
@@ -119,6 +145,21 @@ function formatEvent(event: RunProgressEvent): string | undefined {
119
145
  }
120
146
  }
121
147
 
148
+ /** Immutable view of a run for the pure renderers (viewer + status strip). */
149
+ function snapshotRun(handle: RunHandle): ViewerRunSnapshot {
150
+ return {
151
+ runId: handle.runId,
152
+ name: handle.meta.name,
153
+ description: handle.meta.description,
154
+ status: handle.status,
155
+ startedAt: handle.startedAt,
156
+ finishedAt: handle.finishedAt,
157
+ errorMessage: handle.errorMessage,
158
+ agents: handle.agents.list(),
159
+ declaredPhases: handle.meta.phases?.map((phase) => phase.title),
160
+ };
161
+ }
162
+
122
163
  export class WorkflowRunManager {
123
164
  private runs = new Map<string, RunHandle>();
124
165
 
@@ -126,6 +167,16 @@ export class WorkflowRunManager {
126
167
  return [...this.runs.values()];
127
168
  }
128
169
 
170
+ /**
171
+ * Runs as viewer snapshots, newest first (the display order). `limit`
172
+ * keeps per-tick consumers (the status strip) from copying every run and
173
+ * agent record of the session for a handful of display rows.
174
+ */
175
+ snapshots(limit?: number): ViewerRunSnapshot[] {
176
+ const handles = limit !== undefined ? this.list().slice(-limit) : this.list();
177
+ return handles.map(snapshotRun).reverse();
178
+ }
179
+
129
180
  get(runId: string): RunHandle | undefined {
130
181
  return this.runs.get(runId);
131
182
  }
@@ -195,16 +246,20 @@ export class WorkflowRunManager {
195
246
  runner = await AgentRunner.create({
196
247
  cwd: options.cwd,
197
248
  defaultModel: options.defaultModel,
198
- configuredDefaultModel: options.configuredDefaultModel,
249
+ configuredDefault: options.configuredDefault,
199
250
  defaultEffort: options.defaultEffort as never,
200
251
  onNotice: (message) => handle.record({ type: "log", text: `⚠ ${message}` }),
252
+ getPermissionBridge: options.getPermissionBridge,
253
+ getHookBridge: options.getHookBridge,
254
+ onUsage: options.onUsage,
255
+ unusableModels: options.unusableModels,
256
+ onModelUnusable: options.onModelUnusable,
201
257
  });
202
258
 
203
259
  const { globals, state } = createScriptGlobals({
204
- agentCall: (prompt, opts) => runner!.run(prompt, opts, handle.signal),
260
+ agentCall: (prompt, opts, onUpdate) => runner!.run(prompt, opts, handle.signal, onUpdate),
205
261
  args: options.args,
206
- budgetTotal: options.tokenBudget,
207
- concurrency: defaultConcurrency(),
262
+ admission: createRunAdmission({ budgetTotal: options.tokenBudget, concurrency: defaultConcurrency() }),
208
263
  signal: handle.signal,
209
264
  onEvent: (event) => handle.record(event),
210
265
  onJournal: (entry) => appendJournal(journalPath, entry),
@@ -213,7 +268,7 @@ export class WorkflowRunManager {
213
268
  });
214
269
  handle.state = state;
215
270
 
216
- const result = await runWorkflowScript(body, globals, `${handle.meta.name}.js`);
271
+ const result = await runWorkflowScript(body, globals, `${handle.meta.name}.js`, { signal: handle.signal });
217
272
  handle.finish(handle.signal.aborted ? "aborted" : "completed", result);
218
273
  } catch (error) {
219
274
  const aborted = handle.signal.aborted;
@@ -253,26 +308,29 @@ export class WorkflowRunManager {
253
308
 
254
309
  const { meta, body } = parseWorkflowScript(source);
255
310
  const state = parentState();
256
- const remainingAgents = Math.max(0, MAX_AGENTS_PER_RUN - state.agentCount());
257
- const remainingBudget =
258
- options.tokenBudget === null ? null : Math.max(0, options.tokenBudget - state.outputTokens());
259
311
 
260
312
  // Child calls are not journaled in v1: resume replays the parent's own
261
313
  // agent() prefix only. Progress is forwarded under a "▸ name" phase.
262
314
  const { globals } = createScriptGlobals({
263
- agentCall: (prompt, opts) => runner.run(prompt, opts, parent.signal),
315
+ agentCall: (prompt, opts, onUpdate) => runner.run(prompt, opts, parent.signal, onUpdate),
264
316
  args: childArgs,
265
- budgetTotal: remainingBudget,
266
- concurrency: defaultConcurrency(),
267
- maxAgents: remainingAgents,
317
+ // One budget, limiter and agent cap for the whole tree.
318
+ admission: state.admission,
268
319
  signal: parent.signal,
269
- onEvent: (event) => parent.record({ ...event, phase: `▸ ${meta.name}${event.phase ? ` · ${event.phase}` : ""}` }),
320
+ onEvent: (event) =>
321
+ parent.record({
322
+ ...event,
323
+ // source keeps child records distinct (a child restarts callIndex at
324
+ // 0); the phase prefix is the visible grouping in widget and viewer.
325
+ source: meta.name,
326
+ phase: `▸ ${meta.name}${event.phase ? ` · ${event.phase}` : ""}`,
327
+ }),
270
328
  });
271
- return runWorkflowScript(body, globals, `${label}.js`);
329
+ return runWorkflowScript(body, globals, `${label}.js`, { signal: parent.signal });
272
330
  }
273
331
  }
274
332
 
275
- export function defaultConcurrency(): number {
333
+ function defaultConcurrency(): number {
276
334
  return Math.min(MAX_CONCURRENCY, Math.max(1, os.cpus().length - 2));
277
335
  }
278
336
 
@@ -285,19 +343,10 @@ export function buildRunReport(handle: RunHandle): string {
285
343
  : `${duration}s`;
286
344
 
287
345
  if (handle.status === "completed") {
288
- let resultText: string;
289
- if (typeof handle.result === "string") {
290
- resultText = handle.result;
291
- } else if (handle.result === undefined) {
292
- resultText = "(script returned no value)";
293
- } else {
294
- try {
295
- resultText = JSON.stringify(handle.result, null, 2);
296
- } catch {
297
- resultText = String(handle.result);
298
- }
299
- }
300
- if (resultText.length > RESULT_CAP) resultText = `${resultText.slice(0, RESULT_CAP)}\n… (truncated)`;
346
+ const resultText =
347
+ handle.result === undefined
348
+ ? "(script returned no value)"
349
+ : previewValue(handle.result, RESULT_CAP, "\n… (truncated)");
301
350
  return `Workflow **${handle.meta.name}** (${handle.runId}) completed — ${statsLine}.\n\nResult:\n${resultText}`;
302
351
  }
303
352
 
@@ -10,6 +10,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
10
10
  import { basename, join } from "node:path";
11
11
  import { parseWorkflowScript } from "./script-source.ts";
12
12
  import type { SavedWorkflow } from "./types.ts";
13
+ import { claudeUserDir } from "../lib/paths.ts";
13
14
 
14
15
  function scanDir(dir: string, source: SavedWorkflow["source"]): SavedWorkflow[] {
15
16
  if (!existsSync(dir)) return [];
@@ -29,7 +30,7 @@ function scanDir(dir: string, source: SavedWorkflow["source"]): SavedWorkflow[]
29
30
  }
30
31
 
31
32
  export function workflowDirs(cwd: string, home: string): { project: string; user: string } {
32
- return { project: join(cwd, ".claude", "workflows"), user: join(home, ".claude", "workflows") };
33
+ return { project: join(cwd, ".claude", "workflows"), user: join(claudeUserDir(home), "workflows") };
33
34
  }
34
35
 
35
36
  export function discoverSavedWorkflows(cwd: string, home: string): SavedWorkflow[] {
@@ -0,0 +1,110 @@
1
+ /** Plain JS so workers work in the packaged app without pi's TypeScript loader. */
2
+ import vm from "node:vm";
3
+ import { parentPort, workerData } from "node:worker_threads";
4
+
5
+ class WorkflowScriptError extends Error {}
6
+ const pending = new Map();
7
+ let sequence = 0;
8
+ let notices = 0;
9
+ const send = (message) => parentPort.postMessage(message);
10
+ // Shared with the host (vm-runtime.ts): [0] output tokens spent, [1] heartbeat.
11
+ const shared = new BigInt64Array(workerData.shared);
12
+ const SLOT_SPENT = 0;
13
+ const SLOT_HEARTBEAT = 1;
14
+ const budget = Object.freeze({
15
+ total: workerData.total,
16
+ spent: () => Number(Atomics.load(shared, SLOT_SPENT)),
17
+ remaining: () => workerData.total === null ? Infinity : Math.max(0, workerData.total - Number(Atomics.load(shared, SLOT_SPENT))),
18
+ });
19
+
20
+ function call(method, args) {
21
+ if (pending.size >= workerData.maxPending) {
22
+ return Promise.reject(new WorkflowScriptError(`Workflow agent limit reached (${workerData.maxPending} agent()/workflow() calls in flight per run)`));
23
+ }
24
+ const id = sequence++;
25
+ return new Promise((resolve, reject) => {
26
+ pending.set(id, { resolve, reject });
27
+ try { send({ type: "call", id, method, args }); }
28
+ catch (error) { pending.delete(id); reject(new WorkflowScriptError(error.message)); }
29
+ });
30
+ }
31
+
32
+ parentPort.on("message", ({ id, value, error }) => {
33
+ const request = pending.get(id);
34
+ if (!request) return;
35
+ pending.delete(id);
36
+ if (error) request.reject(error.fatal ? new WorkflowScriptError(error.message) : new Error(error.message));
37
+ else request.resolve(value);
38
+ });
39
+
40
+ // Callback combinators must stay in the script thread: functions cannot cross
41
+ // the RPC boundary. Fatal admission errors retain their identity across RPC.
42
+ async function parallel(thunks) {
43
+ if (!Array.isArray(thunks)) throw new WorkflowScriptError("parallel() takes an array of functions");
44
+ if (thunks.length > workerData.maxItems) throw new WorkflowScriptError(`parallel() accepts at most ${workerData.maxItems} items (got ${thunks.length})`);
45
+ const settled = await Promise.allSettled(thunks.map((thunk) => {
46
+ try {
47
+ if (typeof thunk !== "function") throw new WorkflowScriptError("parallel() items must be functions returning promises");
48
+ return Promise.resolve(thunk());
49
+ } catch (error) { return Promise.reject(error); }
50
+ }));
51
+ return settled.map((result) => {
52
+ if (result.status === "fulfilled") return result.value;
53
+ if (result.reason instanceof WorkflowScriptError) throw result.reason;
54
+ return null;
55
+ });
56
+ }
57
+
58
+ async function pipeline(items, ...stages) {
59
+ if (!Array.isArray(items)) throw new WorkflowScriptError("pipeline() takes an array of items");
60
+ if (items.length > workerData.maxItems) throw new WorkflowScriptError(`pipeline() accepts at most ${workerData.maxItems} items (got ${items.length})`);
61
+ if (stages.some((stage) => typeof stage !== "function")) throw new WorkflowScriptError("pipeline() stages must be functions");
62
+ return Promise.all(items.map(async (item, index) => {
63
+ let prev = item;
64
+ for (const stage of stages) {
65
+ try { prev = await stage(prev, item, index); }
66
+ catch (error) {
67
+ if (error instanceof WorkflowScriptError) throw error;
68
+ return null;
69
+ }
70
+ }
71
+ return prev;
72
+ }));
73
+ }
74
+
75
+ function notice(type, value) {
76
+ // Bound message traffic if a script loops around synchronous log/phase.
77
+ if (++notices > workerData.maxNotices) {
78
+ throw new WorkflowScriptError(`Too many workflow progress messages without yielding (${workerData.maxNotices} log()/phase() calls between two heartbeats)`);
79
+ }
80
+ send({ type, value: String(value) });
81
+ }
82
+ const log = (value) => notice("log", value);
83
+ const globals = {
84
+ agent: (prompt, opts) => call("agent", [prompt, opts]),
85
+ workflow: (ref, args) => call("workflow", [ref, args]),
86
+ parallel, pipeline,
87
+ phase: (title) => notice("phase", title),
88
+ log,
89
+ console: { log, info: log, warn: (value) => log(`[warn] ${String(value)}`), error: (value) => log(`[error] ${String(value)}`) },
90
+ args: workerData.args,
91
+ budget,
92
+ };
93
+
94
+ // A busy loop or microtask spin starves this timer, so the counter stops.
95
+ const heartbeat = setInterval(() => { notices = 0; Atomics.add(shared, SLOT_HEARTBEAT, 1n); }, 25);
96
+ // First beat before the script runs: from here on the host applies the script
97
+ // timeout, not the startup grace — a script that spins from its first line is
98
+ // caught just like one that spins after an await.
99
+ Atomics.add(shared, SLOT_HEARTBEAT, 1n);
100
+ try {
101
+ const context = vm.createContext({ __workflow__: globals }, { codeGeneration: { strings: false, wasm: false } });
102
+ const script = new vm.Script(workerData.source, { filename: workerData.filename });
103
+ const value = await script.runInContext(context);
104
+ send({ type: "result", value });
105
+ } catch (error) {
106
+ send({ type: "error", message: error?.message ?? String(error) });
107
+ } finally {
108
+ clearInterval(heartbeat);
109
+ parentPort.close();
110
+ }
@@ -51,8 +51,26 @@ export interface AgentCallResult {
51
51
  worktreePath?: string;
52
52
  }
53
53
 
54
+ /** One tool call an agent made, kept structured so the viewer owns the formatting. */
55
+ export interface ToolActivity {
56
+ name: string;
57
+ /** One-line argument summary (summarizeArgs output); absent when unavailable. */
58
+ argsSummary?: string;
59
+ }
60
+
61
+ /** Mid-run facts an executing agent reports back (resolved model, tool calls). */
62
+ export interface AgentRunUpdate {
63
+ /** Resolved model id, known once per run. */
64
+ model?: string;
65
+ tool?: ToolActivity;
66
+ }
67
+
54
68
  /** The one injection point globals.ts needs: something that runs a real agent. */
55
- export type AgentCallFn = (prompt: string, opts: AgentCallOptions) => Promise<AgentCallResult>;
69
+ export type AgentCallFn = (
70
+ prompt: string,
71
+ opts: AgentCallOptions,
72
+ onUpdate?: (update: AgentRunUpdate) => void,
73
+ ) => Promise<AgentCallResult>;
56
74
 
57
75
  /** One line of journal.jsonl. */
58
76
  export interface JournalEntry {
@@ -71,16 +89,64 @@ export interface BudgetSnapshot {
71
89
 
72
90
  export type RunStatus = "running" | "completed" | "failed" | "aborted";
73
91
 
74
- /** Progress events a run emits (consumed by the widget, /workflows, onUpdate). */
75
- export interface RunProgressEvent {
76
- type: "log" | "phase" | "agentStart" | "agentEnd";
77
- text?: string;
92
+ /** Fields shared by every per-agent progress event. */
93
+ export interface AgentEventBase {
94
+ callIndex: number;
95
+ label: string;
78
96
  phase?: string;
79
- label?: string;
80
- callIndex?: number;
97
+ }
98
+
99
+ /**
100
+ * Progress events a run emits (consumed by the widget, /workflows, onUpdate).
101
+ * A discriminated union so producers (globals.ts) and consumers (records.ts,
102
+ * run-manager's formatEvent) agree on which fields each event carries.
103
+ *
104
+ * `source` marks nested-workflow provenance (the child workflow's name,
105
+ * stamped by run-manager's runChild). A child restarts callIndex at 0, so
106
+ * records must key on (source, callIndex), never callIndex alone.
107
+ */
108
+ export type RunProgressEvent = (
109
+ | { type: "log"; text: string; phase?: string }
110
+ | { type: "phase"; phase: string }
111
+ | ({ type: "agentStart"; prompt: string } & AgentEventBase)
112
+ /** Mid-run update; a bare one may lack the label (records tolerates that). */
113
+ | ({ type: "agentUpdate"; callIndex: number; label?: string; phase?: string } & AgentRunUpdate)
114
+ | ({
115
+ type: "agentEnd";
116
+ tokens?: AgentTokens;
117
+ cost?: number;
118
+ replayed?: boolean;
119
+ /** Replayed ends carry the prompt (there was no agentStart). */
120
+ prompt?: string;
121
+ /** Truncated preview of the produced value. */
122
+ preview?: string;
123
+ /** Failure message; presence means the agent failed. */
124
+ text?: string;
125
+ } & AgentEventBase)
126
+ ) & { source?: string };
127
+
128
+ /** Per-agent state folded from RunProgressEvents (viewer's detail source). */
129
+ export interface AgentRecord {
130
+ callIndex: number;
131
+ /** Nested workflow() provenance: the child workflow's name, if any. */
132
+ source?: string;
133
+ label: string;
134
+ phase?: string;
135
+ prompt?: string;
136
+ model?: string;
137
+ status: "running" | "done" | "failed" | "replayed";
81
138
  tokens?: AgentTokens;
82
139
  cost?: number;
83
- replayed?: boolean;
140
+ startedAt?: number;
141
+ finishedAt?: number;
142
+ /** Tool calls made, oldest first, capped at ACTIVITY_CAP. */
143
+ activity: ToolActivity[];
144
+ /** Total tool calls made, uncapped (activity keeps only the tail). */
145
+ toolCalls: number;
146
+ /** Truncated preview of the agent's returned value. */
147
+ outcome?: string;
148
+ /** Failure message when status === "failed". */
149
+ error?: string;
84
150
  }
85
151
 
86
152
  /** A saved workflow discovered in .claude/workflows/ or ~/.claude/workflows/. */