@hyperdrive.bot/paseo-server 0.3.41 → 0.3.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/server/server/agent/agent-manager.d.ts +15 -0
  2. package/dist/server/server/agent/agent-manager.js +142 -25
  3. package/dist/server/server/agent/mcp-server.js +6 -1
  4. package/dist/server/server/agent/providers/claude/background-task-tracker.d.ts +38 -1
  5. package/dist/server/server/agent/providers/claude/background-task-tracker.js +114 -9
  6. package/dist/server/server/agent/providers/claude/background-work-kinds.d.ts +95 -0
  7. package/dist/server/server/agent/providers/claude/background-work-kinds.js +73 -0
  8. package/dist/server/server/agent/providers/claude/pty-session-launcher.js +3 -0
  9. package/dist/server/server/agent/providers/claude/transport/pty.d.ts +17 -0
  10. package/dist/server/server/agent/providers/claude/transport/pty.js +51 -1
  11. package/dist/server/server/agent/providers/claude/transport/tmux.d.ts +74 -0
  12. package/dist/server/server/agent/providers/claude/transport/tmux.js +157 -0
  13. package/dist/server/server/agent/providers/claude/transport/types.d.ts +6 -0
  14. package/dist/server/server/agent/providers/opencode-agent.d.ts +7 -0
  15. package/dist/server/server/agent/providers/opencode-agent.js +51 -1
  16. package/dist/server/server/bootstrap.js +1 -0
  17. package/dist/server/server/session.js +1 -0
  18. package/dist/server/server/workflow/workflow-agent-resolution.d.ts +82 -0
  19. package/dist/server/server/workflow/workflow-agent-resolution.js +105 -0
  20. package/dist/server/server/workflow/workflow-manager.d.ts +155 -20
  21. package/dist/server/server/workflow/workflow-manager.js +439 -31
  22. package/dist/server/server/workflow/workflow-progress.d.ts +53 -0
  23. package/dist/server/server/workflow/workflow-progress.js +96 -0
  24. package/dist/server/server/workspace-directory.js +32 -14
  25. package/dist/server/web-ui/_expo/static/js/web/{index-cb251ddad56c08c3021036af3a43fc0f.js → index-2ff48a7009ad2309c577d5a43b925f69.js} +18 -18
  26. package/dist/server/web-ui/_expo/static/js/web/index-2ff48a7009ad2309c577d5a43b925f69.js.br +0 -0
  27. package/dist/server/web-ui/_expo/static/js/web/index-2ff48a7009ad2309c577d5a43b925f69.js.gz +0 -0
  28. package/dist/server/web-ui/_expo/static/js/web/{index-cb251ddad56c08c3021036af3a43fc0f.js.map.br → index-2ff48a7009ad2309c577d5a43b925f69.js.map.br} +0 -0
  29. package/dist/server/web-ui/_expo/static/js/web/{index-cb251ddad56c08c3021036af3a43fc0f.js.map.gz → index-2ff48a7009ad2309c577d5a43b925f69.js.map.gz} +0 -0
  30. package/dist/server/web-ui/index.html +1 -1
  31. package/dist/server/web-ui/index.html.br +0 -0
  32. package/dist/server/web-ui/index.html.gz +0 -0
  33. package/package.json +6 -6
  34. package/dist/server/web-ui/_expo/static/js/web/index-cb251ddad56c08c3021036af3a43fc0f.js.br +0 -0
  35. package/dist/server/web-ui/_expo/static/js/web/index-cb251ddad56c08c3021036af3a43fc0f.js.gz +0 -0
@@ -86,10 +86,63 @@ export declare class WorkflowCycleError extends Error {
86
86
  readonly unresolved: string[];
87
87
  constructor(unresolved: string[]);
88
88
  }
89
+ /**
90
+ * A graph that is malformed *before* dependencies are even considered: it declares no
91
+ * tasks at all, or it declares the same task id twice.
92
+ *
93
+ * Kept separate from {@link WorkflowCycleError} because neither defect is a dependency
94
+ * problem, and both callers (`session.ts` RPC, `mcp-server.ts` tool) forward
95
+ * `error.message` verbatim to a human - telling that human "dependency cycle" when the
96
+ * real defect is "you sent zero tasks" is the kind of message that costs an hour.
97
+ * `defect` is the stable discriminator; the message is the human-facing half.
98
+ */
99
+ export declare class WorkflowInvalidGraphError extends Error {
100
+ readonly defect: "empty-graph" | "duplicate-task-id";
101
+ constructor(defect: "empty-graph" | "duplicate-task-id", message: string);
102
+ }
89
103
  /**
90
104
  * Resolve the layered execution order for a graph. Prefer the graph's declared
91
105
  * `metadata.executionLayers`; otherwise compute layers from task dependencies.
92
106
  */
93
107
  export declare function resolveExecutionLayers(graph: TaskGraph): string[][];
108
+ /**
109
+ * Assert that a task graph is schedulable: it declares at least one task, no task id is
110
+ * declared twice, every declared dependency names a task that exists in the graph, and
111
+ * the dependency relation is acyclic. Throws {@link WorkflowInvalidGraphError} for the
112
+ * two shape defects and {@link WorkflowCycleError} for the two dependency defects, on
113
+ * the first violation; returns `void` on success.
114
+ *
115
+ * WHY the shape defects are rejected here rather than tolerated: both produce a workflow
116
+ * that can never reach a terminal status, which is the exact failure this validator
117
+ * exists to prevent. An empty graph makes `startWorkflow` flip to `running`, spawn zero
118
+ * children and return - and `settleWorkflow` is only ever reached from a child terminal
119
+ * event, so with no children no event ever fires and the workflow sits `running`
120
+ * forever. Duplicate ids are worse than useless: two children spawn for one task id and
121
+ * the first of them to close settles the whole workflow to `completed` while its twin is
122
+ * still live, recording intent as fact. Neither is stopped downstream - the wire schema
123
+ * (`WorkflowTaskGraphSchema.tasks`) has no `.min(1)` and no uniqueness constraint - so
124
+ * submission time is the only place they can be stopped. The definition layer already
125
+ * rejects both (`steps: z.array(...).min(1)` and `collectDuplicateStepIds` in
126
+ * `protocol/src/workflow/definition.ts`); this closes the same two holes on the
127
+ * raw-graph path, which is the one the wire and the CLI can still reach directly.
128
+ *
129
+ * Pure: no I/O, no snapshot reads, no `Date` API (see the determinism rules above).
130
+ *
131
+ * WHY this cannot delegate to `resolveExecutionLayers`: that function (above) returns
132
+ * `graph.metadata.executionLayers` verbatim and unvalidated whenever it is non-empty,
133
+ * and `WorkflowTaskGraphSchema.metadata.executionLayers` is a REQUIRED wire field
134
+ * (`packages/protocol/src/messages.ts`), so every real graph carries declared layers and
135
+ * takes that branch. The Kahn check inside `computeLayersFromDependencies` therefore
136
+ * never runs for real traffic. This validator reads `TaskNode.dependencies` only and
137
+ * ignores the declared layering entirely, so a graph that *claims* to be layerable is
138
+ * still rejected when its dependencies say otherwise.
139
+ *
140
+ * It also deliberately does NOT share `computeLayersFromDependencies`' escape hatch
141
+ * (`|| !byId.has(dep)`, which treats an out-of-graph dependency as already satisfied).
142
+ * That clause is correct for the projection, which must keep layering graphs stored
143
+ * before this validator existed, and wrong for scheduling, where an out-of-graph
144
+ * dependency can never be met. Rejecting is done here, one level up, instead.
145
+ */
146
+ export declare function assertSchedulableGraph(graph: TaskGraph): void;
94
147
  export {};
95
148
  //# sourceMappingURL=workflow-progress.d.ts.map
@@ -138,6 +138,23 @@ export class WorkflowCycleError extends Error {
138
138
  this.name = "WorkflowCycleError";
139
139
  }
140
140
  }
141
+ /**
142
+ * A graph that is malformed *before* dependencies are even considered: it declares no
143
+ * tasks at all, or it declares the same task id twice.
144
+ *
145
+ * Kept separate from {@link WorkflowCycleError} because neither defect is a dependency
146
+ * problem, and both callers (`session.ts` RPC, `mcp-server.ts` tool) forward
147
+ * `error.message` verbatim to a human - telling that human "dependency cycle" when the
148
+ * real defect is "you sent zero tasks" is the kind of message that costs an hour.
149
+ * `defect` is the stable discriminator; the message is the human-facing half.
150
+ */
151
+ export class WorkflowInvalidGraphError extends Error {
152
+ constructor(defect, message) {
153
+ super(message);
154
+ this.defect = defect;
155
+ this.name = "WorkflowInvalidGraphError";
156
+ }
157
+ }
141
158
  /**
142
159
  * Resolve the layered execution order for a graph. Prefer the graph's declared
143
160
  * `metadata.executionLayers`; otherwise compute layers from task dependencies.
@@ -175,4 +192,83 @@ function computeLayersFromDependencies(tasks) {
175
192
  }
176
193
  return layers;
177
194
  }
195
+ /**
196
+ * Assert that a task graph is schedulable: it declares at least one task, no task id is
197
+ * declared twice, every declared dependency names a task that exists in the graph, and
198
+ * the dependency relation is acyclic. Throws {@link WorkflowInvalidGraphError} for the
199
+ * two shape defects and {@link WorkflowCycleError} for the two dependency defects, on
200
+ * the first violation; returns `void` on success.
201
+ *
202
+ * WHY the shape defects are rejected here rather than tolerated: both produce a workflow
203
+ * that can never reach a terminal status, which is the exact failure this validator
204
+ * exists to prevent. An empty graph makes `startWorkflow` flip to `running`, spawn zero
205
+ * children and return - and `settleWorkflow` is only ever reached from a child terminal
206
+ * event, so with no children no event ever fires and the workflow sits `running`
207
+ * forever. Duplicate ids are worse than useless: two children spawn for one task id and
208
+ * the first of them to close settles the whole workflow to `completed` while its twin is
209
+ * still live, recording intent as fact. Neither is stopped downstream - the wire schema
210
+ * (`WorkflowTaskGraphSchema.tasks`) has no `.min(1)` and no uniqueness constraint - so
211
+ * submission time is the only place they can be stopped. The definition layer already
212
+ * rejects both (`steps: z.array(...).min(1)` and `collectDuplicateStepIds` in
213
+ * `protocol/src/workflow/definition.ts`); this closes the same two holes on the
214
+ * raw-graph path, which is the one the wire and the CLI can still reach directly.
215
+ *
216
+ * Pure: no I/O, no snapshot reads, no `Date` API (see the determinism rules above).
217
+ *
218
+ * WHY this cannot delegate to `resolveExecutionLayers`: that function (above) returns
219
+ * `graph.metadata.executionLayers` verbatim and unvalidated whenever it is non-empty,
220
+ * and `WorkflowTaskGraphSchema.metadata.executionLayers` is a REQUIRED wire field
221
+ * (`packages/protocol/src/messages.ts`), so every real graph carries declared layers and
222
+ * takes that branch. The Kahn check inside `computeLayersFromDependencies` therefore
223
+ * never runs for real traffic. This validator reads `TaskNode.dependencies` only and
224
+ * ignores the declared layering entirely, so a graph that *claims* to be layerable is
225
+ * still rejected when its dependencies say otherwise.
226
+ *
227
+ * It also deliberately does NOT share `computeLayersFromDependencies`' escape hatch
228
+ * (`|| !byId.has(dep)`, which treats an out-of-graph dependency as already satisfied).
229
+ * That clause is correct for the projection, which must keep layering graphs stored
230
+ * before this validator existed, and wrong for scheduling, where an out-of-graph
231
+ * dependency can never be met. Rejecting is done here, one level up, instead.
232
+ */
233
+ export function assertSchedulableGraph(graph) {
234
+ // Pass 0 - shape. Runs before anything reads `dependencies`, because a graph this
235
+ // malformed has no dependency verdict worth reporting.
236
+ if (graph.tasks.length === 0) {
237
+ throw new WorkflowInvalidGraphError("empty-graph", "Workflow task graph declares no tasks: a workflow must declare at least one task.");
238
+ }
239
+ // Built by hand rather than `new Map(graph.tasks.map(...))` precisely so the collision
240
+ // is observable: the Map constructor silently keeps the LAST entry for a repeated key,
241
+ // which is how a duplicate id survives every downstream check.
242
+ const byId = new Map();
243
+ for (const task of graph.tasks) {
244
+ if (byId.has(task.id)) {
245
+ throw new WorkflowInvalidGraphError("duplicate-task-id", `Workflow task graph declares duplicate task id: ${task.id}`);
246
+ }
247
+ byId.set(task.id, task);
248
+ }
249
+ // Pass 1 - dangling dependencies. Runs FIRST so a dependency on a task that does not
250
+ // exist is never misreported as a cycle. First offender in graph order, for a
251
+ // deterministic message.
252
+ for (const task of graph.tasks) {
253
+ for (const dep of task.dependencies) {
254
+ if (!byId.has(dep)) {
255
+ throw new WorkflowCycleError([`${task.id} -> ${dep}`]);
256
+ }
257
+ }
258
+ }
259
+ // Pass 2 - cycles. Kahn-style sweep over `dependencies` only; no out-of-graph escape,
260
+ // which is safe precisely because pass 1 proved every dependency is in-graph.
261
+ const resolved = new Set();
262
+ let remaining = graph.tasks;
263
+ while (remaining.length > 0) {
264
+ const layer = remaining.filter((task) => task.dependencies.every((dep) => resolved.has(dep)));
265
+ if (layer.length === 0) {
266
+ throw new WorkflowCycleError(remaining.map((task) => task.id));
267
+ }
268
+ for (const task of layer) {
269
+ resolved.add(task.id);
270
+ }
271
+ remaining = remaining.filter((task) => !resolved.has(task.id));
272
+ }
273
+ }
178
274
  //# sourceMappingURL=workflow-progress.js.map
@@ -44,6 +44,28 @@ export function workspaceIdsOnCheckout(workspaces, cwd) {
44
44
  .filter((workspace) => !workspace.archivedAt && resolve(workspace.cwd) === resolvedCwd)
45
45
  .map((workspace) => workspace.workspaceId);
46
46
  }
47
+ /**
48
+ * Build the bucket-derivation input for one agent snapshot.
49
+ *
50
+ * Exists so every call site in this file feeds `deriveAgentStateBucket` the
51
+ * SAME shape. It previously did not: two call sites hand-built the object and
52
+ * both omitted `activeChildCount`, so server-derived workspace buckets could
53
+ * never see armed background work while the app-side derivation could. The same
54
+ * agent then bucketed as "done" in the workspace list and "running" on its
55
+ * kanban card. One helper, one shape, no drift.
56
+ */
57
+ function toBucketInput(agent) {
58
+ return {
59
+ status: agent.status,
60
+ pendingPermissionCount: agent.pendingPermissions?.length ?? 0,
61
+ requiresAttention: agent.requiresAttention,
62
+ attentionReason: agent.attentionReason ?? null,
63
+ // Monitors, crons and backgrounded shells still alive on this agent. Live
64
+ // SUBAGENTS are not counted here — they arrive as their own snapshots and
65
+ // are attributed to the delegation root in applyAgentBucketContributions.
66
+ activeChildCount: agent.activeBackgroundTaskCount ?? 0,
67
+ };
68
+ }
47
69
  export class WorkspaceDirectory {
48
70
  constructor(deps) {
49
71
  this.deps = deps;
@@ -166,15 +188,13 @@ export class WorkspaceDirectory {
166
188
  continue;
167
189
  }
168
190
  workspaceAgent = parentAgent;
169
- bucket = "running";
191
+ // A live subagent means the ROOT's own turn is not in flight — it is
192
+ // waiting on delegated work that will hand control back by itself.
193
+ // That is "pending", not "running"; see agent-state-bucket.ts.
194
+ bucket = "pending";
170
195
  }
171
196
  else {
172
- bucket = deriveAgentStateBucket({
173
- status: agent.status,
174
- pendingPermissionCount: agent.pendingPermissions?.length ?? 0,
175
- requiresAttention: agent.requiresAttention,
176
- attentionReason: agent.attentionReason ?? null,
177
- });
197
+ bucket = deriveAgentStateBucket(toBucketInput(agent));
178
198
  }
179
199
  const workspaceId = workspaceAgent.workspaceId;
180
200
  if (!workspaceId) {
@@ -210,7 +230,10 @@ export class WorkspaceDirectory {
210
230
  existing.status = bucket;
211
231
  }
212
232
  const entries = terminalEntriesByWorkspaceId.get(workspaceId) ?? [];
213
- entries.push({ bucket, changedAtIso: new Date(activity.changedAt).toISOString() });
233
+ entries.push({
234
+ bucket,
235
+ changedAtIso: new Date(activity.changedAt).toISOString(),
236
+ });
214
237
  terminalEntriesByWorkspaceId.set(workspaceId, entries);
215
238
  }
216
239
  return terminalEntriesByWorkspaceId;
@@ -271,12 +294,7 @@ export class WorkspaceDirectory {
271
294
  findNewestTimestampInBucket(contributingAgents, terminalEntries, winningBucket) {
272
295
  const agentTimestamps = contributingAgents
273
296
  .filter((agent) => {
274
- const derived = deriveAgentStateBucket({
275
- status: agent.status,
276
- pendingPermissionCount: agent.pendingPermissions?.length ?? 0,
277
- requiresAttention: agent.requiresAttention,
278
- attentionReason: agent.attentionReason ?? null,
279
- });
297
+ const derived = deriveAgentStateBucket(toBucketInput(agent));
280
298
  return derived === winningBucket;
281
299
  })
282
300
  .map((agent) => {