@getpipher/armory-fleet 0.9.1 → 0.9.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpipher/armory-fleet",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "private": false,
5
5
  "description": "The armory suite's subagent orchestrator for the pi coding agent — a cross-harness, superpowers-native fleet where every agent is armory-native from birth.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -171,7 +171,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
171
171
  const fleetDir = (cwd: string) => join(cwd, ".pi", "fleet");
172
172
  const bgRuns = new BgRunsStore();
173
173
  const resultsInbox = new ResultsInbox();
174
- // SPEC-5b-2: the live widget (above editor) + FleetView (below editor) controller.
174
+ // SPEC-5b-2: the live widget (above editor) controller.
175
175
  // Display-only, independent of the /fleet panel; constructed per-session in session_start.
176
176
  let fleetWidget: FleetWidgetController | null = null;
177
177
  // The async runner's runLifecycle adapter: call the real runLifecycle with the worktree as the
@@ -275,7 +275,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
275
275
  if (cands.length > 0) {
276
276
  ctx.ui.notify(`${cands.length} interrupted fleet run${cands.length > 1 ? "s" : ""} — open /fleet to resume`, "info");
277
277
  }
278
- // SPEC-5b-2: live widget (above editor) + FleetView (below editor). Display-only, independent
278
+ // SPEC-5b-2: live widget (above editor). Display-only, independent
279
279
  // of the /fleet panel. getTheme is a live getter (EditorTheme gotcha). Disposed on session end.
280
280
  fleetWidget = new FleetWidgetController({
281
281
  runRegistry: deps.runRegistry,
@@ -1,11 +1,15 @@
1
1
  // src/panel/fleet-widget.ts
2
- // SPEC-5b-2 — the live widget (above editor) + FleetView (below editor) controller.
2
+ // SPEC-5b-2 — the live widget (above editor) controller.
3
3
  //
4
4
  // Display-only (Q1=A): pi widgets render into a layout container; the editor keeps keyboard
5
5
  // focus. No input is routed here — /fleet is the action surface.
6
6
  //
7
+ // v0.9.2 (fix/spec-5b-2): the below-editor FleetView widget was removed — it duplicated this
8
+ // above-editor widget (same renderer, same rows) and the "navigable list" intent was never
9
+ // achievable via pi widgets. `/fleet` is the action surface; this one widget is the glance surface.
10
+ //
7
11
  // Lifecycle (Q5/Q6/Q7=A): visible only while ≥1 active run exists; hidden when idle (editor
8
- // reclaims both slots). A 1s setInterval re-renders for the live duration clock; it starts
12
+ // reclaims the slot). A 1s setInterval re-renders for the live duration clock; it starts
9
13
  // lazily on the first active render and clears when the fleet goes idle + on dispose.
10
14
  //
11
15
  // Independent of the /fleet panel: constructed at session_start in index.ts, persists whether
@@ -14,11 +18,10 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
14
18
  import type { RunRegistry } from "../engine/run-registry.ts";
15
19
  import type { BgRunsStore } from "./bg-runs-store.ts";
16
20
  import {
17
- toWidgetRun, toWidgetRunFromBg, renderWidgetLines, renderFleetViewLines,
21
+ toWidgetRun, toWidgetRunFromBg, renderWidgetLines,
18
22
  } from "./widget-rows.ts";
19
23
 
20
24
  const WIDGET_KEY = "fleet-active";
21
- const VIEW_KEY = "fleet-view";
22
25
 
23
26
  export interface FleetWidgetDeps {
24
27
  runRegistry: RunRegistry;
@@ -27,7 +30,7 @@ export interface FleetWidgetDeps {
27
30
  setWidget: (
28
31
  key: string,
29
32
  content: string[] | undefined,
30
- opts?: { placement?: "aboveEditor" | "belowEditor" },
33
+ opts?: { placement?: "aboveEditor" },
31
34
  ) => void;
32
35
  };
33
36
  /** Live theme getter (EditorTheme gotcha: never capture a factory theme arg). */
@@ -72,7 +75,7 @@ export class FleetWidgetController {
72
75
  const hasActive = active.some((r) => r.status === "running" || r.status === "queued" || r.status === "paused");
73
76
  if (!hasActive) {
74
77
  this.clearTimer();
75
- this.setBoth(undefined);
78
+ this.clearWidget();
76
79
  return;
77
80
  }
78
81
  this.ensureTimer();
@@ -80,14 +83,10 @@ export class FleetWidgetController {
80
83
  try {
81
84
  this.deps.ui.setWidget(WIDGET_KEY, renderWidgetLines(active, now));
82
85
  } catch { /* best-effort: a render failure never affects runs */ }
83
- try {
84
- this.deps.ui.setWidget(VIEW_KEY, renderFleetViewLines(active, now), { placement: "belowEditor" });
85
- } catch { /* best-effort */ }
86
86
  }
87
87
 
88
- private setBoth(content: string[] | undefined): void {
89
- try { this.deps.ui.setWidget(WIDGET_KEY, content); } catch { /* best-effort */ }
90
- try { this.deps.ui.setWidget(VIEW_KEY, content, { placement: "belowEditor" }); } catch { /* best-effort */ }
88
+ private clearWidget(): void {
89
+ try { this.deps.ui.setWidget(WIDGET_KEY, undefined); } catch { /* best-effort */ }
91
90
  }
92
91
 
93
92
  private ensureTimer(): void {
@@ -102,13 +101,13 @@ export class FleetWidgetController {
102
101
  }
103
102
  }
104
103
 
105
- /** Unsubscribe + clear timer + clear both widgets. Idempotent. */
104
+ /** Unsubscribe + clear timer + clear the widget. Idempotent. */
106
105
  dispose(): void {
107
106
  if (this.disposed) return;
108
107
  this.disposed = true;
109
108
  this.clearTimer();
110
109
  for (const u of this.unsubs) u();
111
110
  this.unsubs.length = 0;
112
- this.setBoth(undefined);
111
+ this.clearWidget();
113
112
  }
114
113
  }
@@ -1,7 +1,12 @@
1
1
  // src/panel/widget-rows.ts
2
- // SPEC-5b-2 — pure render functions for the live widget (above editor) + FleetView (below editor).
3
- // Both surfaces are display-only; plain strings (no theme) — theming, if wanted, is applied at the
4
- // setWidget boundary. Mirrors the runs-rows/fleet-items pure-renderer convention (unit-tested, no TUI).
2
+ // SPEC-5b-2 — pure render functions for the live widget (above editor).
3
+ // Display-only; plain strings (no theme) — theming, if wanted, is applied at the setWidget boundary.
4
+ // Mirrors the runs-rows/fleet-items pure-renderer convention (unit-tested, no TUI).
5
+ //
6
+ // v0.9.2 (fix/spec-5b-2): the below-editor FleetView widget was removed — it was a display-only
7
+ // mirror of this same renderer (same `widgetLine`, cap 8 vs 5), and the PRD §5 "navigable agent
8
+ // list below editor" intent was never achievable via pi widgets (editor keeps keyboard focus).
9
+ // `/fleet` is the navigable action surface; this one above-editor widget is the glance surface.
5
10
  import { fmtDuration } from "./rows.ts";
6
11
  import type { RunRecord } from "../engine/run-registry.ts";
7
12
  import type { BgRunStatus } from "./rows.ts";
@@ -72,8 +77,3 @@ export function renderWidgetLines(runs: WidgetRun[], now: number = Date.now()):
72
77
  return shown;
73
78
  }
74
79
 
75
- /** Below-editor FleetView: active-only list, cap 8, no overflow line (list form). */
76
- export function renderFleetViewLines(runs: WidgetRun[], now: number = Date.now()): string[] {
77
- const active = filterActive(runs);
78
- return active.slice(0, 8).map((r) => widgetLine(r, now));
79
- }
@@ -23,6 +23,7 @@ export const subagentParams = Type.Object({
23
23
  auto: Type.Optional(Type.Boolean({ description: "Only relevant with `lifecycle`. Tool-driven is always auto; this flag is forward-compat. Panel-driven uses --auto on /fleet-implement." })),
24
24
  background: Type.Optional(Type.Boolean({ description: "Fire without awaiting. The run goes to the async/bg pool on an isolated git worktree; this returns { runId, status: 'background' } immediately. Foreground (default) awaits the result." })),
25
25
  schedule: Type.Optional(Type.String({ description: 'Schedule the run instead of firing now: a cron string ("0 9 * * 1-5"), an interval ("30m"/"2h"), or a one-shot ISO datetime ("2026-07-25T14:00"). Returns { scheduleId, nextFire }. Session-scoped (fires only while pi is open); no catch-up.' })),
26
+ maxTurns: Type.Optional(Type.Number({ description: 'Per-run turn budget (default 20). Raise for complex multi-step tasks (e.g. 40) so the subagent doesn\'t hit the budget mid-task; lower for trivial lookups.' })),
26
27
  });
27
28
 
28
29
  export type SubagentInput = Static<typeof subagentParams>;
@@ -62,7 +63,7 @@ export function createSubagentTool(deps: SubagentToolDeps) {
62
63
  "Pass track:false only for trivial throwaway lookups that don't represent real work.",
63
64
  ],
64
65
  parameters: subagentParams,
65
- async execute(_toolCallId: string, params: SubagentInput, signal: AbortSignal, _onUpdate: unknown, ctx: any) {
66
+ async execute(_toolCallId: string, params: SubagentInput, signal: AbortSignal, _onUpdate: unknown, _ctx: any) {
66
67
  // SPEC-5a: background + schedule routing (Q1/Q2/Q5).
67
68
  if (params.background && params.schedule) {
68
69
  return { isError: true, content: [{ type: "text" as const, text: "A scheduled run is inherently background — pass only one of `background` or `schedule`, not both." }] };
@@ -87,6 +88,7 @@ export function createSubagentTool(deps: SubagentToolDeps) {
87
88
  skillsOverride: o.skills, backendOverride: o.backend,
88
89
  registry: deps.registry, todoSync: deps.todoSync, runRegistry: deps.runRegistry, lock: deps.lock,
89
90
  backendRegistry: deps.backendRegistry, parentModel: deps.parentModel, parentCwd: deps.parentCwd, runLog: deps.runLog, signal,
91
+ maxTurns: params.maxTurns,
90
92
  }),
91
93
  };
92
94
  const res = await runLifecycle(params.task, params.lifecycle, {
@@ -117,11 +119,7 @@ export function createSubagentTool(deps: SubagentToolDeps) {
117
119
  parentCwd: deps.parentCwd,
118
120
  runLog: deps.runLog,
119
121
  signal,
120
- onEvent: (e) => {
121
- if (ctx?.ui?.setWidget && e.type === "turn_end") {
122
- ctx.ui.setWidget("fleet", [`▶ ${params.agent} · running`]);
123
- }
124
- },
122
+ maxTurns: params.maxTurns,
125
123
  });
126
124
  const isError = res.status === "failed" || res.status === "aborted";
127
125
  return {