@yycholla/pi-dynamic-workflows 3.3.0
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 +22 -0
- package/README.md +325 -0
- package/assets/readme/hero.png +0 -0
- package/assets/readme/hero.svg +70 -0
- package/assets/readme/package-cover.png +0 -0
- package/assets/readme/package-cover.svg +66 -0
- package/assets/readme/workflow.png +0 -0
- package/assets/readme/workflow.svg +77 -0
- package/dist/accept-workflow-guidance.d.ts +14 -0
- package/dist/accept-workflow-guidance.js +53 -0
- package/dist/adversarial-review.d.ts +30 -0
- package/dist/adversarial-review.js +107 -0
- package/dist/agent-history.d.ts +20 -0
- package/dist/agent-history.js +122 -0
- package/dist/agent-registry.d.ts +83 -0
- package/dist/agent-registry.js +190 -0
- package/dist/agent.d.ts +364 -0
- package/dist/agent.js +714 -0
- package/dist/builtin-commands.d.ts +19 -0
- package/dist/builtin-commands.js +251 -0
- package/dist/builtin-workflows.d.ts +45 -0
- package/dist/builtin-workflows.js +121 -0
- package/dist/code-review.d.ts +26 -0
- package/dist/code-review.js +181 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +44 -0
- package/dist/deep-research.d.ts +30 -0
- package/dist/deep-research.js +124 -0
- package/dist/display.d.ts +134 -0
- package/dist/display.js +248 -0
- package/dist/effort-command.d.ts +28 -0
- package/dist/effort-command.js +68 -0
- package/dist/enums.d.ts +69 -0
- package/dist/enums.js +78 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +140 -0
- package/dist/extension-reload.d.ts +37 -0
- package/dist/extension-reload.js +78 -0
- package/dist/fs-persistence.d.ts +63 -0
- package/dist/fs-persistence.js +102 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +35 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.js +66 -0
- package/dist/model-routing.d.ts +30 -0
- package/dist/model-routing.js +50 -0
- package/dist/model-spec.d.ts +29 -0
- package/dist/model-spec.js +252 -0
- package/dist/model-tier-config.d.ts +133 -0
- package/dist/model-tier-config.js +249 -0
- package/dist/run-persistence.d.ts +180 -0
- package/dist/run-persistence.js +294 -0
- package/dist/saved-commands.d.ts +28 -0
- package/dist/saved-commands.js +100 -0
- package/dist/shared-store.d.ts +98 -0
- package/dist/shared-store.js +212 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/task-panel.d.ts +61 -0
- package/dist/task-panel.js +422 -0
- package/dist/usage-limit-scheduler.d.ts +145 -0
- package/dist/usage-limit-scheduler.js +368 -0
- package/dist/web-tools.d.ts +20 -0
- package/dist/web-tools.js +120 -0
- package/dist/workflow-authoring-coverage.d.ts +70 -0
- package/dist/workflow-authoring-coverage.js +421 -0
- package/dist/workflow-authoring-reference.d.ts +20 -0
- package/dist/workflow-authoring-reference.js +156 -0
- package/dist/workflow-capability-contract.d.ts +131 -0
- package/dist/workflow-capability-contract.js +604 -0
- package/dist/workflow-commands.d.ts +18 -0
- package/dist/workflow-commands.js +260 -0
- package/dist/workflow-comprehension.d.ts +133 -0
- package/dist/workflow-comprehension.js +1321 -0
- package/dist/workflow-context-measurement.d.ts +72 -0
- package/dist/workflow-context-measurement.js +213 -0
- package/dist/workflow-control-tool.d.ts +30 -0
- package/dist/workflow-control-tool.js +176 -0
- package/dist/workflow-delivery-choice.d.ts +20 -0
- package/dist/workflow-delivery-choice.js +48 -0
- package/dist/workflow-editor.d.ts +93 -0
- package/dist/workflow-editor.js +363 -0
- package/dist/workflow-manager.d.ts +492 -0
- package/dist/workflow-manager.js +1124 -0
- package/dist/workflow-paths.d.ts +22 -0
- package/dist/workflow-paths.js +46 -0
- package/dist/workflow-release-gate.d.ts +39 -0
- package/dist/workflow-release-gate.js +309 -0
- package/dist/workflow-saved.d.ts +38 -0
- package/dist/workflow-saved.js +126 -0
- package/dist/workflow-settings.d.ts +70 -0
- package/dist/workflow-settings.js +131 -0
- package/dist/workflow-tool.d.ts +71 -0
- package/dist/workflow-tool.js +367 -0
- package/dist/workflow-ui.d.ts +182 -0
- package/dist/workflow-ui.js +1587 -0
- package/dist/workflow.d.ts +333 -0
- package/dist/workflow.js +1151 -0
- package/dist/workflows-models-command.d.ts +31 -0
- package/dist/workflows-models-command.js +156 -0
- package/dist/worktree.d.ts +25 -0
- package/dist/worktree.js +61 -0
- package/extensions/workflow.ts +151 -0
- package/package.json +104 -0
- package/skills/workflow-authoring/SKILL.md +30 -0
- package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
- package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
- package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
- package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
- package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
- package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
- package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
- package/skills/workflow-authoring/examples/structured-output.js +37 -0
- package/skills/workflow-authoring/examples/tournament.js +81 -0
- package/skills/workflow-authoring/examples/validated-gate.js +63 -0
- package/skills/workflow-authoring/references/capabilities.md +41 -0
- package/skills/workflow-authoring/references/capability-details.md +357 -0
- package/skills/workflow-authoring/references/common-helpers.md +4 -0
- package/skills/workflow-authoring/references/debugging.md +27 -0
- package/skills/workflow-authoring/references/focused-recipes.md +11 -0
- package/skills/workflow-authoring/references/helpers.md +9 -0
- package/skills/workflow-authoring/references/lifecycle.md +33 -0
- package/skills/workflow-authoring/references/pattern-selection.md +15 -0
- package/skills/workflow-authoring/references/quality-helpers.md +8 -0
- package/skills/workflow-authoring/references/registry-ownership.md +15 -0
- package/skills/workflow-authoring/references/retry-helper.md +5 -0
- package/skills/workflow-authoring/references/review.md +43 -0
- package/skills/workflow-authoring/references/runtime.md +27 -0
- package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
- package/skills/workflow-authoring/references/versions.md +13 -0
- package/skills/workflow-patterns/SKILL.md +51 -0
- package/src/accept-workflow-guidance.ts +71 -0
- package/src/adversarial-review.ts +120 -0
- package/src/agent-history.ts +157 -0
- package/src/agent-registry.ts +221 -0
- package/src/agent.ts +929 -0
- package/src/builtin-commands.ts +286 -0
- package/src/builtin-workflows.ts +155 -0
- package/src/code-review.ts +183 -0
- package/src/config.ts +55 -0
- package/src/deep-research.ts +135 -0
- package/src/display.ts +367 -0
- package/src/effort-command.ts +87 -0
- package/src/enums.ts +77 -0
- package/src/errors.ts +199 -0
- package/src/extension-reload.ts +100 -0
- package/src/fs-persistence.ts +124 -0
- package/src/index.ts +176 -0
- package/src/logger.ts +88 -0
- package/src/model-routing.ts +73 -0
- package/src/model-spec.ts +309 -0
- package/src/model-tier-config.ts +296 -0
- package/src/run-persistence.ts +484 -0
- package/src/saved-commands.ts +115 -0
- package/src/shared-store.ts +228 -0
- package/src/structured-output.ts +47 -0
- package/src/task-panel.ts +490 -0
- package/src/usage-limit-scheduler.ts +432 -0
- package/src/web-tools.ts +124 -0
- package/src/workflow-authoring-coverage.ts +486 -0
- package/src/workflow-authoring-reference.ts +186 -0
- package/src/workflow-capability-contract.ts +806 -0
- package/src/workflow-commands.ts +287 -0
- package/src/workflow-comprehension.ts +1673 -0
- package/src/workflow-context-measurement.ts +262 -0
- package/src/workflow-control-tool.ts +238 -0
- package/src/workflow-delivery-choice.ts +69 -0
- package/src/workflow-editor.ts +444 -0
- package/src/workflow-manager.ts +1405 -0
- package/src/workflow-paths.ts +63 -0
- package/src/workflow-release-gate.ts +529 -0
- package/src/workflow-saved.ts +180 -0
- package/src/workflow-settings.ts +194 -0
- package/src/workflow-tool.ts +464 -0
- package/src/workflow-ui.ts +1789 -0
- package/src/workflow.ts +1615 -0
- package/src/workflows-models-command.ts +211 -0
- package/src/worktree.ts +76 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow manager for background execution, pause/resume, and run management.
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
5
|
+
import type { ModelRegistry, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { WorkflowAgent } from "./agent.js";
|
|
7
|
+
import { type WorkflowAgentSnapshot, type WorkflowSnapshot } from "./display.js";
|
|
8
|
+
import { WorkflowError } from "./errors.js";
|
|
9
|
+
import { type PersistedRunState, type RunLease, type RunPersistence, type RunStatus } from "./run-persistence.js";
|
|
10
|
+
import { type JournalEntry, type WorkflowRunResult } from "./workflow.js";
|
|
11
|
+
export interface ManagedRun {
|
|
12
|
+
runId: string;
|
|
13
|
+
status: RunStatus;
|
|
14
|
+
snapshot: WorkflowSnapshot;
|
|
15
|
+
result?: WorkflowRunResult;
|
|
16
|
+
error?: WorkflowError;
|
|
17
|
+
controller: AbortController;
|
|
18
|
+
startedAt: Date;
|
|
19
|
+
/** The real script, kept so the run can be resumed. */
|
|
20
|
+
script: string;
|
|
21
|
+
args?: unknown;
|
|
22
|
+
/** Accumulated agent results for resume (deterministic call index -> result). */
|
|
23
|
+
journal: JournalEntry[];
|
|
24
|
+
/** Cross-process execution lease for this run, when it is actively executing. */
|
|
25
|
+
lease?: RunLease;
|
|
26
|
+
/**
|
|
27
|
+
* True when the run was started in the background (or resumed) and the caller is
|
|
28
|
+
* not awaiting its result inline. Only background runs deliver their result back
|
|
29
|
+
* into the conversation; a foreground sync run already returns it as the tool
|
|
30
|
+
* result, so re-delivering would duplicate it.
|
|
31
|
+
*/
|
|
32
|
+
background: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Auto-resume eligibility for this run (see ExecOptions.autoResume). Set once
|
|
35
|
+
* at creation and carried through resume() so it survives pause/resume cycles.
|
|
36
|
+
* Undefined means eligible (default-on); false opts out.
|
|
37
|
+
*/
|
|
38
|
+
autoResume?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The run's resolved hard token budget (per-run value, else the manager
|
|
41
|
+
* default), fixed at run start and carried through resume() — a resumed run
|
|
42
|
+
* must keep the budget it started with, not re-resolve against the current
|
|
43
|
+
* default (an explicit `null` opt-out would otherwise regain a budget).
|
|
44
|
+
*/
|
|
45
|
+
tokenBudget?: number | null;
|
|
46
|
+
/**
|
|
47
|
+
* Named toolset tag for this run (see WorkflowManagerOptions.toolsets).
|
|
48
|
+
* ToolDefinitions are functions and can't be persisted, so the tag is what
|
|
49
|
+
* survives on disk — resume() re-resolves it so e.g. a resumed
|
|
50
|
+
* `/deep-research` run keeps its web tools instead of silently degrading to
|
|
51
|
+
* the default coding tools.
|
|
52
|
+
*/
|
|
53
|
+
toolset?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Real per-agent start/end timestamps, captured at onAgentStart/onAgentEnd
|
|
56
|
+
* (never fabricated), keyed by the agent's snapshot id. A running agent has
|
|
57
|
+
* an entry with no endedAt; persistRun() reads from here instead of stamping
|
|
58
|
+
* every agent with the run's startedAt / "now".
|
|
59
|
+
*/
|
|
60
|
+
agentTimestamps: Map<number, {
|
|
61
|
+
startedAt: string;
|
|
62
|
+
endedAt?: string;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Live snapshot-agent lookup keyed by the agent CALL's unique id (see
|
|
66
|
+
* WorkflowRunOptions.onAgentStart/onAgentEnd/onAgentHistory's `id` field in
|
|
67
|
+
* workflow.ts — unique per call, never per label). onAgentEnd/onAgentHistory
|
|
68
|
+
* must resolve the snapshot entry to update through this map, never by
|
|
69
|
+
* scanning managed.snapshot.agents for a label match: two concurrent agents
|
|
70
|
+
* routinely share a label (e.g. parallel()'s default `"${phase} agent N"`
|
|
71
|
+
* labeling, or an author-supplied label reused across a fan-out), and a
|
|
72
|
+
* label+status scan would update whichever same-label entry it happens to
|
|
73
|
+
* find first — misattributing one agent's end/history event to a different,
|
|
74
|
+
* still-running sibling.
|
|
75
|
+
*/
|
|
76
|
+
agentsById: Map<string, WorkflowAgentSnapshot>;
|
|
77
|
+
/**
|
|
78
|
+
* The run's cap on total agents (per-run value, else left undefined so
|
|
79
|
+
* runWorkflow applies its own MAX_AGENTS_PER_RUN default), fixed at run
|
|
80
|
+
* start/resume and carried through resume() — mirrors ManagedRun.tokenBudget
|
|
81
|
+
* exactly: a resumed run must keep the cap it started with, not silently
|
|
82
|
+
* regain the (much larger) default because ExecOptions.maxAgents isn't
|
|
83
|
+
* threaded through resume()'s executeRun() call.
|
|
84
|
+
*/
|
|
85
|
+
maxAgents?: number;
|
|
86
|
+
/**
|
|
87
|
+
* The run's resolved per-agent timeout (per-run value, else the manager
|
|
88
|
+
* default at the time), fixed at run start/resume — same rationale as
|
|
89
|
+
* tokenBudget/maxAgents: resume() must not re-resolve against the manager's
|
|
90
|
+
* CURRENT defaultAgentTimeoutMs.
|
|
91
|
+
*/
|
|
92
|
+
agentTimeoutMs?: number | null;
|
|
93
|
+
/**
|
|
94
|
+
* The run's resolved concurrency (per-run value, else the manager's
|
|
95
|
+
* concurrency at the time), fixed at run start/resume for the same reason
|
|
96
|
+
* as tokenBudget.
|
|
97
|
+
*/
|
|
98
|
+
concurrency?: number;
|
|
99
|
+
/**
|
|
100
|
+
* The run's resolved agent-retry count (per-run value, else the manager
|
|
101
|
+
* default at the time), fixed at run start/resume for the same reason as
|
|
102
|
+
* tokenBudget.
|
|
103
|
+
*/
|
|
104
|
+
agentRetries?: number;
|
|
105
|
+
}
|
|
106
|
+
/** Per-execution options shared by sync, background, and resume runs. */
|
|
107
|
+
export interface ExecOptions {
|
|
108
|
+
/**
|
|
109
|
+
* Replay these journaled agent/checkpoint results for the unchanged prefix
|
|
110
|
+
* (resume), keyed by `${runId}:${index}` — see
|
|
111
|
+
* WorkflowRunOptions.resumeJournal in workflow.ts.
|
|
112
|
+
*/
|
|
113
|
+
resumeJournal?: Map<string, JournalEntry>;
|
|
114
|
+
/** Cap on total agents for this run. */
|
|
115
|
+
maxAgents?: number;
|
|
116
|
+
/** Per-agent timeout in milliseconds. null/omitted means no hard timeout. */
|
|
117
|
+
agentTimeoutMs?: number | null;
|
|
118
|
+
/** Host signal (e.g. tool/Esc) that should abort this run when fired. */
|
|
119
|
+
externalSignal?: AbortSignal;
|
|
120
|
+
/** Called with the live snapshot on every progress event. */
|
|
121
|
+
onProgress?: (snapshot: WorkflowSnapshot) => void;
|
|
122
|
+
/** Hard token budget for this run; once spent reaches it, agent() throws. */
|
|
123
|
+
tokenBudget?: number | null;
|
|
124
|
+
/**
|
|
125
|
+
* Tool set for this run's subagents, replacing the default coding tools —
|
|
126
|
+
* e.g. built-in `/deep-research` appends web tools. Omit for the default.
|
|
127
|
+
* Not persistable (functions): pair with `toolset` so a resumed run can
|
|
128
|
+
* re-resolve the same tools.
|
|
129
|
+
*/
|
|
130
|
+
tools?: ToolDefinition[];
|
|
131
|
+
/**
|
|
132
|
+
* Named toolset tag, resolved via WorkflowManagerOptions.toolsets. Persisted
|
|
133
|
+
* with the run and re-resolved on resume(). When both `tools` and `toolset`
|
|
134
|
+
* are given, `tools` wins for this execution and `toolset` is what resumes use.
|
|
135
|
+
*/
|
|
136
|
+
toolset?: string;
|
|
137
|
+
/** Max concurrent agents for this execution. */
|
|
138
|
+
concurrency?: number;
|
|
139
|
+
/** Retry attempts after recoverable agent failures for this execution. */
|
|
140
|
+
agentRetries?: number;
|
|
141
|
+
/** Resolve a checkpoint() question with a human reply (only for UI-bearing runs). */
|
|
142
|
+
confirm?: (promptText: string, options: unknown) => Promise<unknown>;
|
|
143
|
+
/**
|
|
144
|
+
* Whether this run is eligible for auto-resume when it pauses on a provider
|
|
145
|
+
* usage limit. Default-on: omit or pass true to stay eligible, pass false to
|
|
146
|
+
* opt out. Persisted on the run so a cold-start UsageLimitScheduler respects
|
|
147
|
+
* it too. See usage-limit-scheduler.ts.
|
|
148
|
+
*/
|
|
149
|
+
autoResume?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Seed for the execution's cumulative token counters — passed through to
|
|
152
|
+
* runWorkflow's WorkflowRunOptions.initialTokenUsage. Only resume() sets
|
|
153
|
+
* this (from the persisted run's tokenUsage-at-pause), so the resumed
|
|
154
|
+
* execution's fresh SharedRuntime starts counting from the already-spent
|
|
155
|
+
* total instead of zero (see A2 in workflow-manager's resume()).
|
|
156
|
+
*/
|
|
157
|
+
initialTokenUsage?: {
|
|
158
|
+
input: number;
|
|
159
|
+
output: number;
|
|
160
|
+
total: number;
|
|
161
|
+
cost: number;
|
|
162
|
+
cacheRead: number;
|
|
163
|
+
cacheWrite: number;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export interface WorkflowManagerOptions {
|
|
167
|
+
cwd?: string;
|
|
168
|
+
concurrency?: number;
|
|
169
|
+
/** Resolve a saved-workflow name to its script, enabling nested `workflow('name')`. */
|
|
170
|
+
loadSavedWorkflow?: (name: string) => string | undefined;
|
|
171
|
+
/** Inject a custom agent runner (tests); defaults to a real subagent session. */
|
|
172
|
+
agent?: Pick<WorkflowAgent, "run">;
|
|
173
|
+
/** The session's main model (provider/id), for auto-tiering explore agents. */
|
|
174
|
+
mainModel?: string;
|
|
175
|
+
/**
|
|
176
|
+
* The host Pi session's model registry. When provided, workflow subagents
|
|
177
|
+
* resolve models against the same registry as the main session, including
|
|
178
|
+
* extension-registered providers such as ollama-cloud.
|
|
179
|
+
*/
|
|
180
|
+
modelRegistry?: ModelRegistry;
|
|
181
|
+
/** The pi session id to tag runs with (see setSessionId). */
|
|
182
|
+
sessionId?: string;
|
|
183
|
+
/** Default per-agent timeout when a run does not pass agentTimeoutMs. null means no hard timeout. */
|
|
184
|
+
defaultAgentTimeoutMs?: number | null;
|
|
185
|
+
/** Default retry attempts after recoverable agent failures. */
|
|
186
|
+
defaultAgentRetries?: number;
|
|
187
|
+
/** Default hard token budget when a run does not pass tokenBudget. null/omitted means no budget. */
|
|
188
|
+
defaultTokenBudget?: number | null;
|
|
189
|
+
/**
|
|
190
|
+
* Named toolsets resolvable by ExecOptions.toolset — e.g.
|
|
191
|
+
* `{ "web-research": () => [...createCodingTools(cwd), ...createWebTools()] }`.
|
|
192
|
+
* Called lazily per execution (including on resume). An unknown tag resolves
|
|
193
|
+
* to the default coding tools.
|
|
194
|
+
*/
|
|
195
|
+
toolsets?: Record<string, () => ToolDefinition[]>;
|
|
196
|
+
/**
|
|
197
|
+
* Extra tool NAMES to deny in every subagent session, on top of the always-on
|
|
198
|
+
* `workflow`/`workflow_control` defaults (see DEFAULT_EXCLUDED_SUBAGENT_TOOLS).
|
|
199
|
+
* Host wiring passes settings.excludeSubagentTools here so users can also block
|
|
200
|
+
* other recursive-orchestration tools (#107).
|
|
201
|
+
*/
|
|
202
|
+
excludeSubagentTools?: string[];
|
|
203
|
+
/**
|
|
204
|
+
* Persist each subagent transcript as a real pi session file under the
|
|
205
|
+
* standard sessions directory. Default false (in-memory, discarded).
|
|
206
|
+
*/
|
|
207
|
+
persistAgentSessions?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* How many terminal (completed/failed/aborted) runs to retain full
|
|
210
|
+
* in-memory state for before the oldest is evicted from `runs` (see the
|
|
211
|
+
* class-level doc comment on that field). Defaults to
|
|
212
|
+
* DEFAULT_MAX_TERMINAL_RUNS_IN_MEMORY; exposed mainly for tests that want
|
|
213
|
+
* to observe eviction without creating dozens of runs.
|
|
214
|
+
*/
|
|
215
|
+
maxTerminalRunsInMemory?: number;
|
|
216
|
+
}
|
|
217
|
+
/** Options that a fresh extension generation may safely refresh on a live
|
|
218
|
+
* manager handed across `/reload`. Execution identity (`cwd`, persistence,
|
|
219
|
+
* injected agent, and in-memory runs) is intentionally excluded. */
|
|
220
|
+
export type WorkflowManagerReloadOptions = Pick<WorkflowManagerOptions, "concurrency" | "loadSavedWorkflow" | "defaultAgentTimeoutMs" | "defaultAgentRetries" | "defaultTokenBudget" | "toolsets" | "excludeSubagentTools" | "persistAgentSessions">;
|
|
221
|
+
export declare class WorkflowManager extends EventEmitter {
|
|
222
|
+
/**
|
|
223
|
+
* Lifecycle contract for `runs`:
|
|
224
|
+
*
|
|
225
|
+
* - An entry is added when a run starts (startInBackground/runSync) or is
|
|
226
|
+
* resumed (resume()), always with a live AbortController and (usually)
|
|
227
|
+
* an active RunLease.
|
|
228
|
+
* - While status is "running" or "paused", the entry is NEVER evicted —
|
|
229
|
+
* its execution could still settle (a pending executeRun() promise) or
|
|
230
|
+
* it is mid-usage-limit-checkpoint/manually-paused and still considered
|
|
231
|
+
* "the current state of this run" by callers. Eviction only ever
|
|
232
|
+
* considers an entry AFTER executeRun() has fully settled it to
|
|
233
|
+
* "completed" | "failed" | "aborted" (see IN_MEMORY_TERMINAL_STATUSES)
|
|
234
|
+
* and persisted + released its lease — i.e. strictly after the same
|
|
235
|
+
* isCurrent()-gated persistRun()/releaseRunLease() calls in
|
|
236
|
+
* executeRun()'s success/catch tails.
|
|
237
|
+
* - Once terminal, an entry becomes eviction-ELIGIBLE (recordTerminalRun())
|
|
238
|
+
* but is not necessarily evicted immediately: up to
|
|
239
|
+
* maxTerminalRunsInMemory terminal entries are kept, oldest evicted
|
|
240
|
+
* first, so a `getRun()` call immediately after completion (e.g. the
|
|
241
|
+
* "complete" event's own synchronous listeners — task-panel's result
|
|
242
|
+
* delivery, `/workflows watch`) still sees the live object. Once
|
|
243
|
+
* evicted, the entry is simply removed from `runs`; nothing else reads
|
|
244
|
+
* or writes it again.
|
|
245
|
+
* - Every caller of getRun()/getSnapshot() must treat "undefined"/null as
|
|
246
|
+
* "no live in-memory copy right now" and fall back to listRuns() (backed
|
|
247
|
+
* by run-persistence.ts, which is what's authoritative for a run once
|
|
248
|
+
* the in-memory copy is gone) — this mirrors how those callers already
|
|
249
|
+
* treat any run this process never had in memory (e.g. one started by a
|
|
250
|
+
* different process and only ever seen via listRuns()). resume() never
|
|
251
|
+
* depends on `runs` for a run's state either: it always reloads from
|
|
252
|
+
* persistence, so an evicted runId resumes exactly like one from a
|
|
253
|
+
* prior process.
|
|
254
|
+
* - isCurrent(managed) composes with eviction the same way it composes
|
|
255
|
+
* with resume()/deleteRun() replacing or removing an entry: eviction
|
|
256
|
+
* removes the map entry outright, so a stale execution's later settle
|
|
257
|
+
* (isCurrent() check) sees `this.runs.get(runId) !== managed` (in fact
|
|
258
|
+
* undefined) and correctly no-ops, exactly as it would after
|
|
259
|
+
* resume()/deleteRun().
|
|
260
|
+
*/
|
|
261
|
+
private runs;
|
|
262
|
+
/**
|
|
263
|
+
* FIFO of runIds that reached IN_MEMORY_TERMINAL_STATUSES, oldest first —
|
|
264
|
+
* the eviction order for `runs` (see its doc comment). A runId can appear
|
|
265
|
+
* more than once (e.g. resumed after eviction, then terminates again);
|
|
266
|
+
* evicting is idempotent (recordTerminalRun() re-checks the CURRENT status
|
|
267
|
+
* of the current map entry for that id before deleting), so duplicates
|
|
268
|
+
* are harmless.
|
|
269
|
+
*/
|
|
270
|
+
private terminalRunQueue;
|
|
271
|
+
private maxTerminalRunsInMemory;
|
|
272
|
+
private persistence;
|
|
273
|
+
private cwd;
|
|
274
|
+
private concurrency;
|
|
275
|
+
private loadSavedWorkflow?;
|
|
276
|
+
private agent?;
|
|
277
|
+
/** The session's main model (provider/id), for auto-tiering explore agents. */
|
|
278
|
+
private mainModel?;
|
|
279
|
+
/** The host Pi session's model registry, shared with subagents. */
|
|
280
|
+
private modelRegistry?;
|
|
281
|
+
/** The current pi session id; runs are stamped with it and listRuns() filters by it. */
|
|
282
|
+
private sessionId?;
|
|
283
|
+
private defaultAgentTimeoutMs;
|
|
284
|
+
private defaultAgentRetries;
|
|
285
|
+
private defaultTokenBudget;
|
|
286
|
+
private toolsets?;
|
|
287
|
+
private excludeSubagentTools?;
|
|
288
|
+
private persistAgentSessions;
|
|
289
|
+
constructor(options?: WorkflowManagerOptions);
|
|
290
|
+
/** Bind the manager to the current pi session, so new runs are tagged with it and
|
|
291
|
+
* the navigator/task-panel show only this session's runs (set on session_start). */
|
|
292
|
+
setSessionId(id: string | undefined): void;
|
|
293
|
+
/**
|
|
294
|
+
* On startup, any persisted run still marked "running" belongs to a process
|
|
295
|
+
* that died mid-run (this fresh manager has it nowhere in memory). Reconcile it
|
|
296
|
+
* to "paused" — never "failed" — so its journal is preserved and resume() can
|
|
297
|
+
* replay the completed prefix and finish the rest.
|
|
298
|
+
*/
|
|
299
|
+
private recoverStaleRuns;
|
|
300
|
+
/**
|
|
301
|
+
* Refresh host configuration after Pi reloads the extension while retaining
|
|
302
|
+
* this manager's live runs, controllers, leases, and event listeners.
|
|
303
|
+
* Existing executions keep the options they captured at start; subsequent
|
|
304
|
+
* runs and resumes use these refreshed defaults.
|
|
305
|
+
*/
|
|
306
|
+
reconfigureAfterReload(options: WorkflowManagerReloadOptions): void;
|
|
307
|
+
/** Set the session's main model (provider/id). Used to auto-tier explore agents. */
|
|
308
|
+
setMainModel(spec: string | undefined): void;
|
|
309
|
+
/** Set the host session's model registry so subagents resolve models consistently. */
|
|
310
|
+
setModelRegistry(registry: ModelRegistry): void;
|
|
311
|
+
/**
|
|
312
|
+
* Expose the host session's model registry to integrations sharing this
|
|
313
|
+
* manager. Workflow execution reads the same registry internally.
|
|
314
|
+
*/
|
|
315
|
+
getModelRegistry(): ModelRegistry | undefined;
|
|
316
|
+
/**
|
|
317
|
+
* Start a workflow in the background.
|
|
318
|
+
* Returns immediately with a run ID; the workflow executes asynchronously.
|
|
319
|
+
*/
|
|
320
|
+
startInBackground(script: string, args?: unknown, exec?: ExecOptions): {
|
|
321
|
+
runId: string;
|
|
322
|
+
promise: Promise<WorkflowRunResult>;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* Execute a workflow synchronously (blocking) while still tracking it like a
|
|
326
|
+
* background run, so the `/workflows` navigator and the live task panel see it.
|
|
327
|
+
* `onProgress` fires on every progress event with the current snapshot, letting
|
|
328
|
+
* a caller (e.g. the workflow tool) drive its own inline display.
|
|
329
|
+
*/
|
|
330
|
+
runSync(script: string, args?: unknown, exec?: ExecOptions): Promise<WorkflowRunResult>;
|
|
331
|
+
/** Build a fresh managed run with an empty snapshot. */
|
|
332
|
+
private createManaged;
|
|
333
|
+
private executeRun;
|
|
334
|
+
/**
|
|
335
|
+
* True when `managed` is still the live, current entry for its runId in
|
|
336
|
+
* `this.runs` — false once resume() has replaced it with a new ManagedRun
|
|
337
|
+
* object for the same runId, or deleteRun() has removed it entirely. A
|
|
338
|
+
* superseded ManagedRun's async completion (executeRun's promise settling
|
|
339
|
+
* well after something else already took over or tore down that runId)
|
|
340
|
+
* must not write to disk or touch lease state on the newer execution's
|
|
341
|
+
* behalf — see writeRunToDisk() and executeRun()'s post-await persist calls.
|
|
342
|
+
*/
|
|
343
|
+
private isCurrent;
|
|
344
|
+
/**
|
|
345
|
+
* Emit an event on behalf of `managed`, but only while it's still the
|
|
346
|
+
* current entry for its runId (see isCurrent()) — mirrors the disk/lease
|
|
347
|
+
* guard for the observer-facing side of the same problem. A superseded
|
|
348
|
+
* execution's progress/terminal events (log, phase, agentStart/End,
|
|
349
|
+
* tokenUsage, complete, error, paused) are not just stale-but-harmless:
|
|
350
|
+
* "complete" in particular can drive background result delivery into the
|
|
351
|
+
* conversation, so letting a deleted/superseded run's stale settle still
|
|
352
|
+
* fire it would deliver a result for a run that, from the caller's POV, no
|
|
353
|
+
* longer exists (or has since been superseded by a newer execution whose
|
|
354
|
+
* own events already tell the true story). No event in this set has a
|
|
355
|
+
* legitimate reason to still reach listeners once superseded — unlike
|
|
356
|
+
* disk writes there's no "expected race, harmless no-op" nuance here, it's
|
|
357
|
+
* simply wrong to notify twice (or for a run that's gone). Events emitted
|
|
358
|
+
* directly by pause()/stop()/resume()/deleteRun() themselves are NOT routed
|
|
359
|
+
* through this helper — those methods own the transition and ARE current
|
|
360
|
+
* at the moment they fire, same precedent as their persist/lease calls.
|
|
361
|
+
*/
|
|
362
|
+
private emitLive;
|
|
363
|
+
/**
|
|
364
|
+
* Mark `runId` as eviction-eligible now that its execution has genuinely
|
|
365
|
+
* settled to a terminal status (completed/failed/aborted — see
|
|
366
|
+
* IN_MEMORY_TERMINAL_STATUSES), and evict the oldest eligible entries
|
|
367
|
+
* beyond maxTerminalRunsInMemory. Callers must only invoke this after the
|
|
368
|
+
* same isCurrent()-gated persistRun()/releaseRunLease() sequence executeRun()
|
|
369
|
+
* already uses (see the `runs` field doc comment for the full contract) —
|
|
370
|
+
* this method itself re-validates the CURRENT entry's status before
|
|
371
|
+
* deleting anything, so it never evicts a run that isn't (or is no longer)
|
|
372
|
+
* genuinely terminal, including one resumed back to "running" after being
|
|
373
|
+
* queued here but before its turn to be evicted came up.
|
|
374
|
+
*/
|
|
375
|
+
private recordTerminalRun;
|
|
376
|
+
/**
|
|
377
|
+
* Additively fold one agent-call's token cost into the run-wide persisted
|
|
378
|
+
* aggregate (managed.snapshot.tokenUsage), seeded (on resume) from the
|
|
379
|
+
* persisted total-at-pause — see A2. Shared by onAgentEnd (a completed or
|
|
380
|
+
* finally-failed agent call) and onRetrySpend (a failed attempt that WILL
|
|
381
|
+
* be retried, whose cost recordTokens() already folded into
|
|
382
|
+
* shared.spent/tokenUsage in workflow.ts, but which onAgentEnd never sees —
|
|
383
|
+
* see WorkflowRunOptions.onRetrySpend for why that needs its own channel).
|
|
384
|
+
*/
|
|
385
|
+
private accumulateTokenUsage;
|
|
386
|
+
private releaseRunLease;
|
|
387
|
+
/** Trailing-edge throttle window for high-frequency progress persists (see schedulePersist). */
|
|
388
|
+
private static readonly PERSIST_THROTTLE_MS;
|
|
389
|
+
/** Pending trailing-edge persist timers for high-frequency progress events, keyed by runId. */
|
|
390
|
+
private persistTimers;
|
|
391
|
+
/**
|
|
392
|
+
* Coalesce rapid progress persists (currently: onAgentJournal, which fires
|
|
393
|
+
* once per completed agent and can burst under concurrency) to at most one
|
|
394
|
+
* disk write per PERSIST_THROTTLE_MS (trailing edge) instead of one write
|
|
395
|
+
* per tick — persistRun() does a full JSON.stringify of the run plus up to
|
|
396
|
+
* 3 sync writes, so firing it once per agent in a long run is O(N^2).
|
|
397
|
+
*
|
|
398
|
+
* Lifecycle-critical writes (status transitions, run end, pause/resume/stop)
|
|
399
|
+
* must NOT use this — call persistRun() directly, which flushes (and cancels)
|
|
400
|
+
* any pending timer first so a stale trailing write can never fire after, and
|
|
401
|
+
* resurrect, a terminal state.
|
|
402
|
+
*/
|
|
403
|
+
private schedulePersist;
|
|
404
|
+
/**
|
|
405
|
+
* Persist immediately and synchronously. Cancels any pending throttled write
|
|
406
|
+
* for this run first, so the write that lands is always the caller's current
|
|
407
|
+
* (final) state — never superseded by a stale deferred write. Use this for
|
|
408
|
+
* every lifecycle-critical persist: run start, status transitions, run end,
|
|
409
|
+
* pause()/resume()/stop().
|
|
410
|
+
*/
|
|
411
|
+
private persistRun;
|
|
412
|
+
private writeRunToDisk;
|
|
413
|
+
/**
|
|
414
|
+
* Pause a running workflow.
|
|
415
|
+
*/
|
|
416
|
+
pause(runId: string): boolean;
|
|
417
|
+
/**
|
|
418
|
+
* Resume an interrupted run: replay journaled results for the unchanged prefix
|
|
419
|
+
* and run the rest live. Returns false if there is nothing resumable.
|
|
420
|
+
*
|
|
421
|
+
* `opts.script` lets the orchestrating model resume with an EDITED script
|
|
422
|
+
* (cached-prefix reuse / iteration): unchanged agent() calls whose content
|
|
423
|
+
* hash still matches the journal entry at their positional callIndex replay
|
|
424
|
+
* from cache, while the first changed or newly inserted call — and everything
|
|
425
|
+
* after it — re-runs live. When `opts.script` is omitted, resume behaves
|
|
426
|
+
* exactly as before and uses the persisted script (auto-resume, TUI resume);
|
|
427
|
+
* this keeps the existing single-arg `resume(runId)` callers (e.g. the
|
|
428
|
+
* UsageLimitScheduler) unchanged. `opts.args` overrides the persisted args
|
|
429
|
+
* only when provided; otherwise the persisted args are kept.
|
|
430
|
+
*/
|
|
431
|
+
resume(runId: string, opts?: {
|
|
432
|
+
script?: string;
|
|
433
|
+
args?: unknown;
|
|
434
|
+
}): Promise<boolean>;
|
|
435
|
+
/**
|
|
436
|
+
* Stop a running workflow.
|
|
437
|
+
*
|
|
438
|
+
* Fast path: the run is live in this process (`this.runs`) — abort its
|
|
439
|
+
* controller and persist "aborted" as before. Fallback: the run is not in
|
|
440
|
+
* memory but is persisted as "running" or "paused" — e.g. it belongs to a
|
|
441
|
+
* prior pi session that this process's recoverStaleRuns() flipped to
|
|
442
|
+
* "paused" on disk without repopulating this.runs (see workflow-control-tool's
|
|
443
|
+
* findRun(), which resolves candidates from disk via listRuns()). There is no
|
|
444
|
+
* live controller to abort in that case — the run simply isn't executing in
|
|
445
|
+
* this process — so mark it aborted on disk directly, mirroring resume()'s
|
|
446
|
+
* persisted-fallback lease handling.
|
|
447
|
+
*/
|
|
448
|
+
stop(runId: string): boolean;
|
|
449
|
+
/**
|
|
450
|
+
* Get status of a specific run.
|
|
451
|
+
*/
|
|
452
|
+
getRun(runId: string): ManagedRun | undefined;
|
|
453
|
+
/**
|
|
454
|
+
* List all runs (active + persisted).
|
|
455
|
+
*/
|
|
456
|
+
/**
|
|
457
|
+
* Runs for the navigator/task panel. Once bound to a session (setSessionId), only
|
|
458
|
+
* that session's runs are returned — runs from other sessions stay on disk and
|
|
459
|
+
* reappear when you switch back. Unbound (tests/legacy) returns everything.
|
|
460
|
+
*/
|
|
461
|
+
listRuns(): PersistedRunState[];
|
|
462
|
+
/** All persisted runs regardless of session (used by cross-session recovery). */
|
|
463
|
+
listAllRuns(): PersistedRunState[];
|
|
464
|
+
/**
|
|
465
|
+
* Get snapshot of a run.
|
|
466
|
+
*/
|
|
467
|
+
getSnapshot(runId: string): WorkflowSnapshot | null;
|
|
468
|
+
/**
|
|
469
|
+
* Delete a persisted run.
|
|
470
|
+
*
|
|
471
|
+
* If `runId` is still live in this process (running or paused-in-memory),
|
|
472
|
+
* abort its controller FIRST, before any teardown below — a live run left
|
|
473
|
+
* un-aborted would otherwise keep executing in the background indefinitely
|
|
474
|
+
* (burning API calls/tokens/holding a worktree) after its record is gone.
|
|
475
|
+
* Aborting first, while `managed` is still `this.runs.get(runId)`, costs
|
|
476
|
+
* nothing extra: the abort signal is fire-and-forget (cooperative — the
|
|
477
|
+
* execution winds down on its own schedule), so the exact instant we flip
|
|
478
|
+
* `this.runs`/release the lease/delete files relative to it doesn't matter
|
|
479
|
+
* for correctness. What DOES matter is that once this method returns, the
|
|
480
|
+
* aborted execution's eventual settle (executeRun's success/catch path,
|
|
481
|
+
* asynchronously, possibly much later) must be a harmless no-op rather than
|
|
482
|
+
* a resurrection — that's what isCurrent() guarantees: `this.runs.delete()`
|
|
483
|
+
* below means executeRun's later persistRun()/releaseRunLease() calls on
|
|
484
|
+
* this same `managed` object find `this.runs.get(runId) !== managed` (in
|
|
485
|
+
* fact `undefined`, since the entry is gone) and skip writing/releasing.
|
|
486
|
+
*/
|
|
487
|
+
deleteRun(runId: string): boolean;
|
|
488
|
+
/**
|
|
489
|
+
* Get the persistence layer (for saving workflows).
|
|
490
|
+
*/
|
|
491
|
+
getPersistence(): RunPersistence;
|
|
492
|
+
}
|