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.
- package/LICENSE +20 -0
- package/README.md +222 -177
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +128 -0
- package/docs/guide/configuration.md +173 -0
- package/docs/guide/differences-from-claude-code.md +152 -0
- package/docs/guide/doctor.md +88 -0
- package/docs/guide/hooks.md +111 -0
- package/docs/guide/installation.md +126 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
- package/docs/guide/providers-and-models.md +196 -0
- package/docs/guide/reference.md +226 -0
- package/docs/guide/sessions-and-context.md +192 -0
- package/docs/guide/skills-plugins-and-mcp.md +178 -0
- package/docs/guide/subagents-and-workflows.md +195 -0
- package/docs/guide/tasks-and-background-work.md +113 -0
- package/docs/guide/terminal-interface.md +157 -0
- package/docs/guide/tools.md +177 -0
- package/docs/guide/troubleshooting.md +177 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -217
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- 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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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, "
|
|
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 = (
|
|
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
|
-
/**
|
|
75
|
-
export interface
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
/** Fields shared by every per-agent progress event. */
|
|
93
|
+
export interface AgentEventBase {
|
|
94
|
+
callIndex: number;
|
|
95
|
+
label: string;
|
|
78
96
|
phase?: string;
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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/. */
|