pi-agent-fleet 0.5.0 → 0.7.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/src/tools.ts CHANGED
@@ -4,15 +4,15 @@ import { Type } from "typebox";
4
4
  import { validateFleetSpec } from "./dag.js";
5
5
  import { insertWorkers } from "./insert.js";
6
6
  import { loadPreferences, mergeFleetConfig } from "./preferences.js";
7
- import { activeFleet, currentState, dagPreview, ensureCanvas, killFleet, prepareRelaunch, startLoop, statusText, stopCanvas, updateWidget } from "./controller.js";
7
+ import { activeFleet, currentState, dagPreview, ensureCanvas, killFleet, requestRelaunch, startLoop, statusText, stopCanvas, updateWidget } from "./controller.js";
8
8
  import { openInBrowser, listFleetRoots } from "./canvas.js";
9
9
  import { editConfig, editNode, type ConfigEditKey, type NodeEditKey } from "./edits.js";
10
- import { ensureFleetGitignore, fleetRootFor, isInsideGitRepo, persistFleetJson, writePlanFiles, writeWorkerPrompts } from "./fleet-store.js";
10
+ import { ensureFleetGitignore, fleetRootFor, isInsideGitRepo, writePlanFiles, writeWorkerPrompts } from "./fleet-store.js";
11
11
  import { recoverLatestFleet } from "./fleet-recovery.js";
12
- import { listModelRefs, resolveModelReference, validateFleetModels } from "./model-resolution.js";
12
+ import { listModelRefs, validateFleetModels } from "./model-resolution.js";
13
13
  import { runFleetDesign, slugifyFleetName } from "./planner.js";
14
14
  import { writeReport } from "./report.js";
15
- import { initFleetState, resetForRelaunch, writeState } from "./state.js";
15
+ import { initFleetState, writeState } from "./state.js";
16
16
  import { renderDag } from "./viz.js";
17
17
 
18
18
  export function textResult(text: string, details: Record<string, unknown> = {}) {
@@ -58,6 +58,8 @@ export function registerFleetTools(pi: ExtensionAPI): void {
58
58
  model: Type.Optional(Type.String({ description: "Fleet-wide default model" })),
59
59
  effort: Type.Optional(EffortSchema),
60
60
  warn_cost_usd: Type.Optional(Type.Number()),
61
+ max_cost_usd: Type.Optional(Type.Number()),
62
+ worker_extensions: Type.Optional(Type.Array(Type.String())),
61
63
  loop: Type.Optional(Type.Object({
62
64
  gate: Type.Union([Type.Literal("reviewer"), Type.Literal("none")]),
63
65
  max_iterations: Type.Number(),
@@ -88,7 +90,7 @@ export function registerFleetTools(pi: ExtensionAPI): void {
88
90
  const state = initFleetState(v.spec);
89
91
  await ensureFleetGitignore(ctx.cwd);
90
92
  await writePlanFiles(fleetRoot, v.spec, state);
91
- const active = activeFleet.current = { spec: v.spec, fleetRoot, state, killSwitch: { killed: false }, pauseSwitch: { paused: false }, running: false, costWarned: false, sessions: new Map(), killedNodes: new Set(), widgetVisible: false };
93
+ const active = activeFleet.current = { spec: v.spec, fleetRoot, state, killSwitch: { killed: false }, pauseSwitch: { paused: false }, running: false, costWarned: false, sessions: new Map(), killedNodes: new Set(), relaunchRequests: new Set(), widgetVisible: false };
92
94
  updateWidget(ctx, active);
93
95
 
94
96
  const dag = await dagPreview(v.spec, undefined, fleetRoot);
@@ -242,7 +244,7 @@ export function registerFleetTools(pi: ExtensionAPI): void {
242
244
  pi.registerTool({
243
245
  name: "fleet_relaunch",
244
246
  label: "Fleet Relaunch",
245
- description: "Relaunch a failed node and any blocked downstream dependents. Optionally override the worker model for this run.",
247
+ description: "Relaunch a failed node and any blocked downstream dependents. Works while the fleet is running (queued for the next scheduler pass) and after it stops. Optionally override the worker model for this run.",
246
248
  promptSnippet: "Relaunch a failed fleet node.",
247
249
  parameters: Type.Object({
248
250
  node_id: Type.String({ description: "Worker id to relaunch" }),
@@ -251,30 +253,12 @@ export function registerFleetTools(pi: ExtensionAPI): void {
251
253
  async execute(_id, params, _signal, _onUpdate, ctx) {
252
254
  const active = activeFleet.current;
253
255
  if (!active) return textResult("no fleet planned yet");
254
- if (active.running) return textResult("fleet is running");
255
256
  const fleet = active;
256
257
  await currentState(fleet);
257
258
  if (fleet.state.status === "completed") return textResult("fleet completed, nothing to relaunch");
258
- const worker = fleet.spec.workers.find((w) => w.id === params.node_id);
259
- if (!worker) return textResult(`unknown node "${params.node_id}"`);
260
- const node = fleet.state.nodes[params.node_id];
261
- const relaunchable: ReadonlySet<string> = new Set(["failed", "contract_failed", "killed"]);
262
- if (!node || !relaunchable.has(node.status)) {
263
- return textResult(`node "${params.node_id}" status ${node?.status ?? "missing"} cannot be relaunched; must be failed, contract_failed, or killed`);
264
- }
265
- if (params.model) {
266
- const resolved = resolveModelReference(ctx.modelRegistry, params.model);
267
- if (!resolved.ok) return textResult(resolved.error);
268
- const canonical = `${resolved.model.provider}/${resolved.model.id}`;
269
- fleet.spec.workers = fleet.spec.workers.map((w) => w.id === params.node_id ? { ...w, model: canonical } : w);
270
- await persistFleetJson(fleet);
271
- }
272
- fleet.state = resetForRelaunch(fleet.state, fleet.spec, params.node_id);
273
- await writeState(fleet.fleetRoot, fleet.state);
274
- await writeWorkerPrompts(fleet);
275
- prepareRelaunch(fleet, params.node_id);
276
- void startLoop(fleet, ctx, false, true);
277
- return textResult(`fleet relaunch requested for ${params.node_id}`);
259
+ const result = await requestRelaunch(fleet, params.node_id, params.model, ctx.modelRegistry);
260
+ if (result.startNow) void startLoop(fleet, ctx, false, true);
261
+ return textResult(result.message);
278
262
  },
279
263
  });
280
264
 
@@ -353,11 +337,11 @@ export function registerFleetTools(pi: ExtensionAPI): void {
353
337
  pi.registerTool({
354
338
  name: "fleet_edit",
355
339
  label: "Fleet Edit",
356
- description: "Edit the active fleet: a pending or relaunchable node's model, effort, or task — or fleet config (max_concurrent, warn_cost_usd, model, effort) when node_id is omitted. Changes persist to fleet.json and apply immediately. Edits to running, completed, or blocked nodes are refused; failed, contract_failed, and killed nodes can be edited so relaunch uses the updated task.",
340
+ description: "Edit the active fleet: a pending or relaunchable node's model, effort, or task — or fleet config (max_concurrent, warn_cost_usd, max_cost_usd, model, effort) when node_id is omitted. Changes persist to fleet.json and apply immediately. Edits to running or completed nodes are refused; pending, blocked, failed, contract_failed, and killed nodes can be edited (blocked nodes have not started — nothing to invalidate).",
357
341
  promptSnippet: "Edit a pending fleet node or fleet config.",
358
342
  parameters: Type.Object({
359
343
  node_id: Type.Optional(Type.String({ description: "Worker id to edit; omit for fleet config edits" })),
360
- key: Type.String({ description: "Node keys: model, effort, task. Config keys: max_concurrent, warn_cost_usd, model, effort" }),
344
+ key: Type.String({ description: "Node keys: model, effort, task. Config keys: max_concurrent, warn_cost_usd, max_cost_usd, model, effort" }),
361
345
  value: Type.String({ description: "New value" }),
362
346
  }),
363
347
  async execute(_id, params, _signal, _onUpdate, ctx) {
package/src/types.ts CHANGED
@@ -44,6 +44,11 @@ export interface FleetConfig {
44
44
  model?: string;
45
45
  effort?: ThinkingLevelName;
46
46
  warn_cost_usd?: number;
47
+ /** hard cap — fleet is killed when total estimated cost reaches it */
48
+ max_cost_usd?: number;
49
+ /** extension path fragments to load inside worker sessions (default: none).
50
+ * Needed for extension-provided model providers, e.g. ["opencode-pi"]. */
51
+ worker_extensions?: string[];
47
52
  loop?: LoopConfig;
48
53
  }
49
54
 
@@ -112,6 +117,10 @@ export interface FleetState {
112
117
  lgtm_streak: number;
113
118
  paused: boolean;
114
119
  iterations: IterationSnapshot[];
120
+ /** liveness signal written by the running controller */
121
+ pid?: number;
122
+ /** ISO timestamp liveness signal written by the running controller */
123
+ heartbeat_at?: string;
115
124
  }
116
125
 
117
126
  export const WORKER_TYPE_TOOLS: Record<WorkerType, string[]> = {