@hyperdrive.bot/paseo-server 0.3.42 → 0.3.44
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/dist/server/server/agent/agent-manager.d.ts +6 -0
- package/dist/server/server/agent/agent-manager.js +7 -1
- package/dist/server/server/agent/import-sessions.d.ts +6 -1
- package/dist/server/server/agent/import-sessions.js +27 -2
- package/dist/server/server/agent/mcp-server.js +6 -1
- package/dist/server/server/agent/providers/claude/agent.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/agent.js +21 -2
- package/dist/server/server/agent/providers/claude/background-work-kinds.d.ts +20 -0
- package/dist/server/server/agent/providers/claude/background-work-kinds.js +14 -0
- package/dist/server/server/bootstrap.js +1 -0
- package/dist/server/server/session.js +1 -0
- package/dist/server/server/workflow/workflow-agent-resolution.d.ts +82 -0
- package/dist/server/server/workflow/workflow-agent-resolution.js +105 -0
- package/dist/server/server/workflow/workflow-manager.d.ts +155 -20
- package/dist/server/server/workflow/workflow-manager.js +439 -31
- package/dist/server/server/workflow/workflow-progress.d.ts +53 -0
- package/dist/server/server/workflow/workflow-progress.js +96 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-9c3bcdc334cf1c08001b6bea510e0292.js → index-6a99048b3a9acca9bc30efd44300414e.js} +7 -7
- package/dist/server/web-ui/_expo/static/js/web/index-6a99048b3a9acca9bc30efd44300414e.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-6a99048b3a9acca9bc30efd44300414e.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-9c3bcdc334cf1c08001b6bea510e0292.js.map.br → index-6a99048b3a9acca9bc30efd44300414e.js.map.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-9c3bcdc334cf1c08001b6bea510e0292.js.map.gz → index-6a99048b3a9acca9bc30efd44300414e.js.map.gz} +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-9c3bcdc334cf1c08001b6bea510e0292.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-9c3bcdc334cf1c08001b6bea510e0292.js.gz +0 -0
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import type { WorkflowLifecycleEvent, WorkflowSnapshot } from "@hyperdrive.bot/paseo-protocol/messages";
|
|
1
|
+
import type { WorkflowAgentPreset, WorkflowLifecycleEvent, WorkflowSnapshot } from "@hyperdrive.bot/paseo-protocol/messages";
|
|
2
2
|
import type { AgentManager, ManagedAgent } from "../agent/agent-manager.js";
|
|
3
3
|
import type { AgentSessionConfig } from "../agent/agent-sdk-types.js";
|
|
4
4
|
import type { WorkflowStorage } from "./workflow-storage.js";
|
|
5
5
|
export { WorkflowCycleError } from "./workflow-progress.js";
|
|
6
|
+
export { WorkflowInvalidGraphError } from "./workflow-progress.js";
|
|
7
|
+
export { WorkflowUnknownAgentError } from "./workflow-agent-resolution.js";
|
|
6
8
|
export declare const WORKFLOW_PROJECTION_COALESCE_WINDOW_MS = 60;
|
|
7
|
-
/** Injectable timer surface
|
|
9
|
+
/** Injectable timer surface - defaults to the globals; overridable for deterministic tests. */
|
|
8
10
|
export interface WorkflowProjectionTimers {
|
|
9
11
|
setTimeout: (callback: () => void, ms?: number) => ReturnType<typeof setTimeout>;
|
|
10
12
|
clearTimeout: (handle: ReturnType<typeof setTimeout>) => void;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
15
|
+
* Minimal logging surface: a pino `Logger` satisfies it structurally.
|
|
16
|
+
*
|
|
17
|
+
* `error` is declared as a METHOD shorthand (bivariant) rather than a function
|
|
18
|
+
* property, so both a pino `Logger` and a plain test stub are assignable.
|
|
19
|
+
*/
|
|
20
|
+
export interface WorkflowManagerLogger {
|
|
21
|
+
error(context: Record<string, unknown>, message: string): void;
|
|
22
|
+
trace(context: Record<string, unknown>, message: string): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* WorkflowManager - Epic 1, Story 1.2.
|
|
14
26
|
*
|
|
15
27
|
* The daemon-owned orchestration core. It decomposes a task-graph into child agents
|
|
16
28
|
* spawned through the EXISTING `agentManager.createAgent` lifecycle, so every child
|
|
@@ -41,12 +53,14 @@ export interface TaskNode {
|
|
|
41
53
|
estimatedMinutes: number;
|
|
42
54
|
/** Optional agent type to use (dev, architect, qa, etc.). */
|
|
43
55
|
agentType?: string;
|
|
56
|
+
/** Optional inline config override for this task; highest precedence. */
|
|
57
|
+
agentConfig?: WorkflowAgentPreset;
|
|
44
58
|
/** Optional specific files this task operates on (per-file tasks). */
|
|
45
59
|
targetFiles?: string[];
|
|
46
60
|
}
|
|
47
61
|
/** Metadata about the task graph. */
|
|
48
62
|
export interface TaskGraphMetadata {
|
|
49
|
-
/** Execution layers
|
|
63
|
+
/** Execution layers - each layer runs in parallel, layers run sequentially. */
|
|
50
64
|
executionLayers: string[][];
|
|
51
65
|
/** Maximum number of tasks that can run in parallel. */
|
|
52
66
|
maxParallelism: number;
|
|
@@ -87,13 +101,15 @@ export interface CreateWorkflowInput {
|
|
|
87
101
|
graph: TaskGraph;
|
|
88
102
|
/** Base session config (provider/model template) each child is spawned with. */
|
|
89
103
|
baseConfig: AgentSessionConfig;
|
|
104
|
+
/** Optional named agent presets; a task's `agentType` selects one. Persisted on the snapshot (Story 2.2). */
|
|
105
|
+
agentPresets?: Record<string, WorkflowAgentPreset>;
|
|
90
106
|
/** Optional explicit workflow id (defaults to a random UUID). */
|
|
91
107
|
id?: string;
|
|
92
108
|
/** Optional display title (defaults to the graph goal). */
|
|
93
109
|
title?: string | null;
|
|
94
110
|
/** Optional labels copied onto every spawned child alongside the workflow id. */
|
|
95
111
|
labels?: Record<string, string>;
|
|
96
|
-
/** Optional owning agent id
|
|
112
|
+
/** Optional owning agent id - children also carry PARENT_AGENT_ID_LABEL. */
|
|
97
113
|
parentAgentId?: string;
|
|
98
114
|
}
|
|
99
115
|
/** Event emitted to workflow subscribers whenever a snapshot changes. */
|
|
@@ -121,11 +137,13 @@ export declare class WorkflowManager {
|
|
|
121
137
|
private readonly coalesceWindowMs;
|
|
122
138
|
private readonly projectionTimers;
|
|
123
139
|
private readonly unsubscribeAgents;
|
|
140
|
+
private readonly logger?;
|
|
124
141
|
constructor(options: {
|
|
125
142
|
agentManager: AgentManager;
|
|
126
143
|
storage: WorkflowStorage;
|
|
127
144
|
coalesceWindowMs?: number;
|
|
128
145
|
timers?: WorkflowProjectionTimers;
|
|
146
|
+
logger?: WorkflowManagerLogger;
|
|
129
147
|
});
|
|
130
148
|
/**
|
|
131
149
|
* Detach the AgentManager subscription and cancel any pending projection timers.
|
|
@@ -135,11 +153,11 @@ export declare class WorkflowManager {
|
|
|
135
153
|
/**
|
|
136
154
|
* Build a workflow snapshot from a task graph, persist it (Story 1.1 storage),
|
|
137
155
|
* register it in memory, emit to subscribers, and return the snapshot. No process
|
|
138
|
-
* is spawned here
|
|
156
|
+
* is spawned here - that happens in {@link startWorkflow}/{@link spawnPhaseAgent}.
|
|
139
157
|
*/
|
|
140
158
|
createWorkflow(input: CreateWorkflowInput): Promise<WorkflowSnapshot>;
|
|
141
159
|
/**
|
|
142
|
-
* List every known workflow
|
|
160
|
+
* List every known workflow - storage records overlaid with in-memory snapshots.
|
|
143
161
|
* Live workflows are returned with progress PROJECTED from current child state
|
|
144
162
|
* (Story 3.1), so list surfaces show the same live counts as the detail/SSE views.
|
|
145
163
|
*/
|
|
@@ -147,7 +165,7 @@ export declare class WorkflowManager {
|
|
|
147
165
|
/**
|
|
148
166
|
* Resolve a single workflow. A hard miss throws {@link WorkflowNotFoundError}.
|
|
149
167
|
* For a live workflow the returned snapshot carries progress PROJECTED from the
|
|
150
|
-
* current child-agent states
|
|
168
|
+
* current child-agent states - the persisted counter is never trusted as truth.
|
|
151
169
|
*/
|
|
152
170
|
getWorkflow(id: string): Promise<WorkflowSnapshot>;
|
|
153
171
|
/**
|
|
@@ -161,14 +179,48 @@ export declare class WorkflowManager {
|
|
|
161
179
|
/**
|
|
162
180
|
* Return the workflow's recorded lifecycle events, derived deterministically
|
|
163
181
|
* from its snapshot's timestamps (created/started/completed/archived). A
|
|
164
|
-
* one-shot read
|
|
182
|
+
* one-shot read - continuous event push is Epic 2. Throws
|
|
165
183
|
* {@link WorkflowNotFoundError} for an unknown id.
|
|
166
184
|
*/
|
|
167
185
|
getWorkflowEvents(workflowId: string): Promise<WorkflowLifecycleEvent[]>;
|
|
168
186
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
187
|
+
* Readiness predicate for the spawn scheduler - a pure function of `live`.
|
|
188
|
+
*
|
|
189
|
+
* Returns, in `graph.tasks` array order (so spawn order is deterministic), the NODE of
|
|
190
|
+
* every task that is not yet spawned, not yet terminal, and whose every declared
|
|
191
|
+
* dependency has already recorded a `"success"` outcome. Nodes, not ids: both callers
|
|
192
|
+
* need the node to spawn it, and returning ids made each of them re-scan `graph.tasks`
|
|
193
|
+
* (an O(n^2) lookup) behind an unreachable `if (!node) continue` guard.
|
|
194
|
+
*
|
|
195
|
+
* Readiness is computed from `TaskNode.dependencies` DIRECTLY, never from
|
|
196
|
+
* `resolveExecutionLayers`, whose declared-layer branch returns
|
|
197
|
+
* `metadata.executionLayers` verbatim and unvalidated, and never from layer output.
|
|
198
|
+
*
|
|
199
|
+
* A dependency id absent from `terminalOutcomes` is NOT satisfied. That single rule
|
|
200
|
+
* also makes an out-of-graph dependency unsatisfiable: a ghost id can never gain a
|
|
201
|
+
* terminal outcome, because outcomes are only ever written for real spawned children.
|
|
202
|
+
* This is a DELIBERATE divergence from `computeLayersFromDependencies`
|
|
203
|
+
* (workflow-progress.ts:268), whose `|| !byId.has(dep)` clause treats an out-of-graph
|
|
204
|
+
* dep as already met so it can keep layering already-stored graphs. Under a scheduler,
|
|
205
|
+
* "satisfied because it does not exist" would launch a task whose stated precondition
|
|
206
|
+
* was never met.
|
|
207
|
+
*
|
|
208
|
+
* Performs no I/O, no clock read and no `agentManager` call.
|
|
209
|
+
*/
|
|
210
|
+
private readyTaskNodes;
|
|
211
|
+
/**
|
|
212
|
+
* Drive a workflow's FIRST spawn wave: transition pending -> running, then spawn
|
|
213
|
+
* exactly the tasks that are ready: those whose declared `dependencies` are already
|
|
214
|
+
* satisfied. On a fresh graph that is the dependency-free set; a task that declares a
|
|
215
|
+
* dependency is deliberately NOT launched here, which is what makes
|
|
216
|
+
* `TaskNode.dependencies` a real runtime constraint rather than a decorative field.
|
|
217
|
+
*
|
|
218
|
+
* Parallelism is residual, not declared: two tasks run together because nothing holds
|
|
219
|
+
* them, not because a layer listed them side by side.
|
|
220
|
+
*
|
|
221
|
+
* This method never launches a later wave. Tasks unblocked by a child reaching a
|
|
222
|
+
* terminal state are launched by the continuation path (Story 1.2), driven from
|
|
223
|
+
* `onAgentEvent`, not from here. Returns the updated snapshot.
|
|
172
224
|
*/
|
|
173
225
|
startWorkflow(workflowId: string): Promise<WorkflowSnapshot>;
|
|
174
226
|
/**
|
|
@@ -176,14 +228,36 @@ export declare class WorkflowManager {
|
|
|
176
228
|
* `agentManager.createAgent` surface, tagging it with WORKFLOW_ID_LABEL (and
|
|
177
229
|
* PARENT_AGENT_ID_LABEL when the workflow is owned by a parent agent). Records the
|
|
178
230
|
* child id on the snapshot and persists. Never spawns a process directly.
|
|
231
|
+
*
|
|
232
|
+
* `config` arrives ALREADY RESOLVED by the caller (`resolveTaskConfig`, Story 2.1);
|
|
233
|
+
* this method performs no resolution of its own, so one helper serves both spawn
|
|
234
|
+
* call sites with no duplicated precedence rule.
|
|
235
|
+
*
|
|
236
|
+
* Returns the child on the normal path and `undefined` when the workflow was cancelled
|
|
237
|
+
* mid-spawn and the child was discarded (gate 1.002 NIT-4). The two cases are otherwise
|
|
238
|
+
* indistinguishable to a caller - same type, same shape - and the discarded agent is a
|
|
239
|
+
* corpse: archived, and deliberately absent from `childAgentIds`. `undefined` is the
|
|
240
|
+
* only honest way to say "there is no child here" through a public surface.
|
|
179
241
|
*/
|
|
180
242
|
spawnPhaseAgent(args: {
|
|
181
243
|
workflowId: string;
|
|
182
244
|
node: TaskNode;
|
|
183
245
|
config: AgentSessionConfig;
|
|
184
|
-
}): Promise<ManagedAgent>;
|
|
246
|
+
}): Promise<ManagedAgent | undefined>;
|
|
185
247
|
/**
|
|
186
|
-
*
|
|
248
|
+
* Tear down a child that was created for a workflow which went terminal mid-spawn.
|
|
249
|
+
* Uses `archiveAgent`, the same public surface {@link cascadeArchiveChildren} uses, so
|
|
250
|
+
* the child is closed and marked archived rather than left running for a workflow
|
|
251
|
+
* nobody is watching. The child id is deliberately NOT appended to the snapshot: a
|
|
252
|
+
* cancelled workflow gains no children.
|
|
253
|
+
*
|
|
254
|
+
* Best effort and never throws. It runs on the failure path of a spawn that has already
|
|
255
|
+
* happened; a teardown error must not turn into a rejected `startWorkflow`, nor into an
|
|
256
|
+
* unhandled rejection from the detached continuation IIFE.
|
|
257
|
+
*/
|
|
258
|
+
private discardOrphanedChild;
|
|
259
|
+
/**
|
|
260
|
+
* Archive every child agent tagged to the workflow - no orphans - mirroring
|
|
187
261
|
* `AgentManager.cascadeArchiveChildren`, but keyed on WORKFLOW_ID_LABEL and using
|
|
188
262
|
* only the public archive surface (`listAgents` + `archiveAgent`). `archiveAgent`
|
|
189
263
|
* requires a live agent, so we iterate the live set exactly as the original guards
|
|
@@ -191,7 +265,7 @@ export declare class WorkflowManager {
|
|
|
191
265
|
*/
|
|
192
266
|
cascadeArchiveChildren(workflowId: string): Promise<void>;
|
|
193
267
|
/**
|
|
194
|
-
* Number of live snapshot subscribers. Read-only observability
|
|
268
|
+
* Number of live snapshot subscribers. Read-only observability - used by the
|
|
195
269
|
* SSE route's leak-guard test (Story 2.1) to assert that a disconnected client
|
|
196
270
|
* unsubscribes back to baseline. Additive; does not affect emit behaviour.
|
|
197
271
|
*/
|
|
@@ -200,23 +274,84 @@ export declare class WorkflowManager {
|
|
|
200
274
|
subscribe(subscriber: WorkflowSubscriber): () => void;
|
|
201
275
|
private emit;
|
|
202
276
|
/**
|
|
203
|
-
* AgentManager subscription handler
|
|
204
|
-
*
|
|
205
|
-
*
|
|
277
|
+
* AgentManager subscription handler. Reacts only to child state changes for an
|
|
278
|
+
* agent tagged to a tracked, RUNNING workflow (pending, cancelled and finished
|
|
279
|
+
* workflows are all rejected by the early return). It does three things:
|
|
280
|
+
*
|
|
281
|
+
* 1. Records the child's terminal outcome while the event still carries the closed
|
|
282
|
+
* agent (Story 3.1),
|
|
283
|
+
* 2. Drives ORCHESTRATION (Story 1.2): every surviving event sweeps the graph for
|
|
284
|
+
* tasks whose dependencies just became satisfied and spawns them. This is the
|
|
285
|
+
* only continuation path; there is no poll loop and no second `startWorkflow`, and
|
|
286
|
+
* 3. SETTLES the workflow (Story 1.3): once every declared task has a terminal
|
|
287
|
+
* outcome it flips to `completed`, and the first `failed` outcome flips it to
|
|
288
|
+
* `failed`, freezing the projected counts into the snapshot at that instant.
|
|
289
|
+
*
|
|
290
|
+
* Then it schedules a coalesced projection emit for the workflow. Continuation runs
|
|
291
|
+
* ALONGSIDE that emit, never behind it: the 60 ms window throttles display, and
|
|
292
|
+
* routing scheduling through it would add a window's dead time per graph edge.
|
|
293
|
+
*
|
|
294
|
+
* MUST stay synchronous and MUST NOT throw. `AgentManager.dispatch` invokes
|
|
295
|
+
* `subscriber.callback(event)` with no try/catch (agent-manager.ts:3992), so a throw
|
|
296
|
+
* here starves every later subscriber and propagates out of the caller's
|
|
297
|
+
* `await closeAgent(id)`. Making it `async` is the other half of the same bug:
|
|
298
|
+
* `dispatch` ignores the returned promise, so a rejection becomes an unhandled one.
|
|
206
299
|
*/
|
|
207
300
|
private onAgentEvent;
|
|
301
|
+
/**
|
|
302
|
+
* Spawn every task whose dependencies have just become satisfied. Takes the ALREADY
|
|
303
|
+
* RESOLVED `LiveWorkflow` rather than a workflow id, so it can never call
|
|
304
|
+
* `requireLive` and can never throw `WorkflowNotFoundError` synchronously into
|
|
305
|
+
* `AgentManager.dispatch`. Returns `void`, never a promise, and never throws.
|
|
306
|
+
*
|
|
307
|
+
* The ENTRY guard deliberately lives in the caller (`onAgentEvent`'s early return), not
|
|
308
|
+
* here: a workflow that is not `running` never reaches this method, so cancellation
|
|
309
|
+
* wins for free with no second entry guard to keep in sync. The re-read inside the
|
|
310
|
+
* detached loop is a different question - not "may I start?" but "is this still true
|
|
311
|
+
* after the last await?" - and it has no equivalent in the caller, which returned long
|
|
312
|
+
* before that loop runs.
|
|
313
|
+
*
|
|
314
|
+
* Readiness comes from `readyTaskNodes`, the single Story 1.1 rule. This method adds no
|
|
315
|
+
* second readiness rule, does not flip the workflow status, does not stamp
|
|
316
|
+
* `completedAt` and does not record an outcome for a task whose spawn failed:
|
|
317
|
+
* inventing a failure outcome here would change the projection's counts.
|
|
318
|
+
*/
|
|
319
|
+
private continueWorkflow;
|
|
320
|
+
/**
|
|
321
|
+
* Settle the workflow to a terminal status once its graph has finished. Takes the
|
|
322
|
+
* ALREADY RESOLVED `LiveWorkflow`, never a workflow id, so it can never call
|
|
323
|
+
* `requireLive` and can never throw `WorkflowNotFoundError` into
|
|
324
|
+
* `AgentManager.dispatch`.
|
|
325
|
+
*
|
|
326
|
+
* The rule is read THROUGH `graph.tasks`, not off `terminalOutcomes.values()`. With the
|
|
327
|
+
* ledger keyed on `live.childTaskIds` (gate 1.003 MINOR-4) an out-of-graph key can no
|
|
328
|
+
* longer enter the map at all, so this is a second layer rather than the only one; it is
|
|
329
|
+
* kept because it costs nothing and it is the layer that survives if the key ever becomes
|
|
330
|
+
* derivable from an event again. Mapping the declared tasks over the map also means a
|
|
331
|
+
* partially populated ledger reads as "not finished" rather than as "finished".
|
|
332
|
+
*
|
|
333
|
+
* Everything up to the first `await` is synchronous ON PURPOSE. `AgentManager.dispatch`
|
|
334
|
+
* is a plain synchronous loop invoked from inside `await closeAgent(...)`, so the status
|
|
335
|
+
* flip and the frozen counts are committed to memory before that expression resolves -
|
|
336
|
+
* which is what lets a caller read the terminal snapshot on the very next line with no
|
|
337
|
+
* polling. Only `persist` and `emit` are asynchronous.
|
|
338
|
+
*
|
|
339
|
+
* Children are never cancelled, closed, archived or interrupted here: an independent
|
|
340
|
+
* in-flight sibling keeps running after a failure.
|
|
341
|
+
*/
|
|
342
|
+
private settleWorkflow;
|
|
208
343
|
/**
|
|
209
344
|
* Coalesce a burst of child updates into ONE projection emit per settled window
|
|
210
345
|
* (the `agent-stream-coalescer.ts` discipline). The first event in a burst arms a
|
|
211
346
|
* timer; subsequent events within the window are absorbed. On fire, the current
|
|
212
347
|
* child states are projected and the updated snapshot is emitted through the
|
|
213
|
-
* existing `workflow.*` / HTTP / SSE channels
|
|
348
|
+
* existing `workflow.*` / HTTP / SSE channels - no new transport, no polling.
|
|
214
349
|
*/
|
|
215
350
|
private scheduleProjectionEmit;
|
|
216
351
|
/**
|
|
217
352
|
* Overlay LIVE derived progress onto a workflow's base snapshot. Counts, per-phase
|
|
218
353
|
* gate status, and per-phase completion are recomputed from the current child-agent
|
|
219
|
-
* states
|
|
354
|
+
* states - never read back from the persisted snapshot. Terminal workflows are
|
|
220
355
|
* returned unchanged (nothing left to project). The base snapshot's lifecycle
|
|
221
356
|
* fields (status, childAgentIds, timestamps, id/title) are preserved verbatim; only
|
|
222
357
|
* derived progress fields are overlaid, so this stays back-compat and additive.
|