@quintinshaw/pi-dynamic-workflows 1.9.0 → 1.9.2

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.
@@ -63,6 +63,14 @@ interface AgentRow {
63
63
  status: string;
64
64
  phase?: string;
65
65
  tokens?: number;
66
+ model?: string;
67
+ }
68
+
69
+ /** Short, human-friendly model label: drop the provider prefix for display. */
70
+ function shortModel(model: string | undefined): string | undefined {
71
+ if (!model) return undefined;
72
+ const slash = model.indexOf("/");
73
+ return slash > 0 ? model.slice(slash + 1) : model;
66
74
  }
67
75
 
68
76
  /** Reads run/phase/agent data from the manager, preferring live snapshots. */
@@ -127,7 +135,7 @@ export class NavigatorModel {
127
135
  if (!snap) return [];
128
136
  return snap.agents
129
137
  .filter((a) => (a.phase ?? "(no phase)") === phase)
130
- .map((a) => ({ id: a.id, label: a.label, status: a.status, phase: a.phase, tokens: a.tokens }));
138
+ .map((a) => ({ id: a.id, label: a.label, status: a.status, phase: a.phase, tokens: a.tokens, model: a.model }));
131
139
  }
132
140
 
133
141
  agentDetail(runId: string, agentId: number): WorkflowAgentSnapshot | undefined {
@@ -150,6 +158,7 @@ function persistedToSnapshot(p: PersistedRunState): WorkflowSnapshot {
150
158
  resultPreview:
151
159
  a.result == null ? undefined : String(typeof a.result === "string" ? a.result : JSON.stringify(a.result)),
152
160
  error: a.error,
161
+ model: a.model,
153
162
  })),
154
163
  agentCount: p.agents.length,
155
164
  runningCount: p.agents.filter((a) => a.status === "running").length,
@@ -293,8 +302,9 @@ export function renderNavigator(
293
302
  lines.push(theme.bold(`${model.runName(state.runId)} › ${state.phase}`));
294
303
  agents.forEach((a, i) => {
295
304
  const icon = STATUS_ICON[a.status] ?? "?";
296
- const tok = a.tokens ? dim(` ${fmtTokens(a.tokens)}`) : "";
297
- lines.push(sel(i, `${icon} ${a.label}${tok}`));
305
+ const mdl = shortModel(a.model);
306
+ const meta = [mdl, a.tokens ? fmtTokens(a.tokens) : undefined].filter(Boolean).join(" · ");
307
+ lines.push(sel(i, `${icon} ${a.label}${meta ? dim(` ${meta}`) : ""}`));
298
308
  });
299
309
  } else if (state.kind === "detail" && state.runId && state.agentId != null) {
300
310
  const a = model.agentDetail(state.runId, state.agentId);
@@ -302,6 +312,7 @@ export function renderNavigator(
302
312
  if (a) {
303
313
  const body: string[] = [];
304
314
  body.push(dim("Status: ") + (a.status ?? ""));
315
+ if (a.model) body.push(dim("Model: ") + (shortModel(a.model) ?? ""));
305
316
  if (a.error) body.push(dim("Error: ") + a.error);
306
317
  body.push("", dim("Prompt:"));
307
318
  body.push(...wrap(a.prompt ?? "", width));
@@ -454,9 +465,20 @@ export function openWorkflowNavigator(
454
465
  if (id) ui.notify(manager.stop(id) ? `Stopped ${id}` : `Cannot stop ${id}`, "info");
455
466
  break;
456
467
  }
457
- case "restart":
458
- ui.notify("Restarting a single agent isn't supported yet", "warning");
468
+ case "restart": {
469
+ // Restart re-runs the whole workflow from scratch as a fresh
470
+ // background run (per-agent restart isn't meaningful — agents are
471
+ // driven by the script). The new run auto-delivers when it finishes.
472
+ const id = state.activeRunId(model);
473
+ const run = id ? manager.listRuns().find((r) => r.runId === id) : undefined;
474
+ if (!run?.script) {
475
+ ui.notify(id ? `Cannot restart ${id} (no script saved)` : "No run selected to restart", "warning");
476
+ break;
477
+ }
478
+ const { runId: newId } = manager.startInBackground(run.script, run.args);
479
+ ui.notify(`Restarted ${run.workflowName || "workflow"} as ${newId}`, "info");
459
480
  break;
481
+ }
460
482
  case "save": {
461
483
  const id = state.activeRunId(model);
462
484
  const run = id ? manager.listRuns().find((r) => r.runId === id) : undefined;
package/src/workflow.ts CHANGED
@@ -48,6 +48,8 @@ export interface SharedRuntime {
48
48
  export interface WorkflowRunOptions extends WorkflowAgentOptions {
49
49
  args?: unknown;
50
50
  agent?: Pick<WorkflowAgent, "run">;
51
+ /** The session's main model (provider/id), shown in /workflows for default agents. */
52
+ mainModel?: string;
51
53
  concurrency?: number;
52
54
  tokenBudget?: number | null;
53
55
  signal?: AbortSignal;
@@ -72,7 +74,14 @@ export interface WorkflowRunOptions extends WorkflowAgentOptions {
72
74
  onLog?: (message: string) => void;
73
75
  onPhase?: (title: string) => void;
74
76
  onAgentStart?: (event: { label: string; phase?: string; prompt: string; model?: string }) => void;
75
- onAgentEnd?: (event: { label: string; phase?: string; result: unknown; tokens?: number; worktree?: string }) => void;
77
+ onAgentEnd?: (event: {
78
+ label: string;
79
+ phase?: string;
80
+ result: unknown;
81
+ tokens?: number;
82
+ worktree?: string;
83
+ model?: string;
84
+ }) => void;
76
85
  onTokenUsage?: (usage: { input: number; output: number; total: number; cost: number }) => void;
77
86
  }
78
87
 
@@ -96,6 +105,12 @@ export interface AgentOptions<TSchemaDef extends TSchema | undefined = TSchema |
96
105
  label?: string;
97
106
  phase?: string;
98
107
  schema?: TSchemaDef;
108
+ /**
109
+ * Run this agent on a specific model (`provider/modelId` or a bare `modelId`).
110
+ * The workflow author chooses per-agent models per the routing policy in the
111
+ * tool guidelines (e.g. a lighter model for exploration, the main model for
112
+ * analysis). When omitted, the session's main model is used.
113
+ */
99
114
  model?: string;
100
115
  isolation?: "worktree";
101
116
  agentType?: string;
@@ -203,6 +218,10 @@ export async function runWorkflow<T = unknown>(
203
218
  const requestedLabel = agentOptions.label?.trim();
204
219
  // Precedence: explicit agentOptions.model > phase model (meta.phases[].model).
205
220
  const modelSpec = agentOptions.model ?? resolveModelForPhase(assignedPhase, routingConfig);
221
+ // For display in /workflows: the model this agent runs on — its explicit/phase
222
+ // spec, else the session's main model. The real resolved id overrides this via
223
+ // onModelResolved once the subagent session is created.
224
+ let displayModel = modelSpec ?? options.mainModel;
206
225
 
207
226
  // Deterministic resume key: assigned at lexical call time, before the limiter,
208
227
  // so parallel()/pipeline() fan-out is reproducible for a fixed script.
@@ -215,8 +234,8 @@ export async function runWorkflow<T = unknown>(
215
234
  if (cached && cached.hash === callHash) {
216
235
  shared.agentCount++;
217
236
  const label = requestedLabel || defaultAgentLabel(assignedPhase, shared.agentCount);
218
- options.onAgentStart?.({ label, phase: assignedPhase, prompt, model: modelSpec });
219
- options.onAgentEnd?.({ label, phase: assignedPhase, result: cached.result, tokens: 0 });
237
+ options.onAgentStart?.({ label, phase: assignedPhase, prompt, model: displayModel });
238
+ options.onAgentEnd?.({ label, phase: assignedPhase, result: cached.result, tokens: 0, model: displayModel });
220
239
  return cached.result;
221
240
  }
222
241
 
@@ -225,7 +244,7 @@ export async function runWorkflow<T = unknown>(
225
244
  const label = requestedLabel || defaultAgentLabel(assignedPhase, shared.agentCount);
226
245
  const timeout = agentOptions.timeoutMs ?? agentTimeoutMs;
227
246
 
228
- options.onAgentStart?.({ label, phase: assignedPhase, prompt, model: modelSpec });
247
+ options.onAgentStart?.({ label, phase: assignedPhase, prompt, model: displayModel });
229
248
 
230
249
  // Optional per-agent worktree isolation (deterministic name -> stable resume keys).
231
250
  let worktree: Worktree | undefined;
@@ -262,6 +281,9 @@ export async function runWorkflow<T = unknown>(
262
281
  instructions: buildAgentInstructions(assignedPhase, agentOptions),
263
282
  model: modelSpec,
264
283
  cwd: runCwd,
284
+ onModelResolved: (id: string) => {
285
+ displayModel = id;
286
+ },
265
287
  onUsage: (u: AgentUsage) => {
266
288
  usage = u;
267
289
  },
@@ -274,7 +296,7 @@ export async function runWorkflow<T = unknown>(
274
296
 
275
297
  const tokens = recordTokens(result);
276
298
  options.onAgentJournal?.({ index: callIndex, hash: callHash, result });
277
- options.onAgentEnd?.({ label, phase: assignedPhase, result, tokens, worktree: runCwd });
299
+ options.onAgentEnd?.({ label, phase: assignedPhase, result, tokens, worktree: runCwd, model: displayModel });
278
300
  return result;
279
301
  } catch (error) {
280
302
  if (options.signal?.aborted) throw error;