@getpipher/armory-fleet 0.9.0 → 0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpipher/armory-fleet",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
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",
@@ -221,13 +221,17 @@ export async function spawnSubagent(opts: SpawnOptions): Promise<SpawnResult> {
221
221
  });
222
222
 
223
223
  // SPEC-5b-4: retain a narrow live-session handle on the run record so the panel can
224
- // steer/abort mid-flight. Cleared by finishRun in the same patch as the terminal status.
225
- opts.runRegistry.update(runId, { session: toLiveHandle(session) });
224
+ // steer/abort mid-flight. Wrap abort so the local `aborted` flag is set when the panel
225
+ // calls handle.abort() (not just the signal path) — otherwise finishRun reports
226
+ // "completed" instead of "aborted" on a user-initiated Stop. Cleared by finishRun.
227
+ let aborted = false;
228
+ const handle = toLiveHandle(session);
229
+ handle.abort = async () => { aborted = true; await session.abort(); };
230
+ opts.runRegistry.update(runId, { session: handle });
226
231
 
227
232
  const budget = createTurnBudget(maxTurns);
228
233
  let finalText = "";
229
234
  let tokenTotal = 0;
230
- let aborted = false;
231
235
  let turnIdx = -1;
232
236
 
233
237
  const onSignalAbort = (): void => { aborted = true; void session.abort(); };
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
- }
@@ -62,7 +62,7 @@ export function createSubagentTool(deps: SubagentToolDeps) {
62
62
  "Pass track:false only for trivial throwaway lookups that don't represent real work.",
63
63
  ],
64
64
  parameters: subagentParams,
65
- async execute(_toolCallId: string, params: SubagentInput, signal: AbortSignal, _onUpdate: unknown, ctx: any) {
65
+ async execute(_toolCallId: string, params: SubagentInput, signal: AbortSignal, _onUpdate: unknown, _ctx: any) {
66
66
  // SPEC-5a: background + schedule routing (Q1/Q2/Q5).
67
67
  if (params.background && params.schedule) {
68
68
  return { isError: true, content: [{ type: "text" as const, text: "A scheduled run is inherently background — pass only one of `background` or `schedule`, not both." }] };
@@ -117,11 +117,6 @@ export function createSubagentTool(deps: SubagentToolDeps) {
117
117
  parentCwd: deps.parentCwd,
118
118
  runLog: deps.runLog,
119
119
  signal,
120
- onEvent: (e) => {
121
- if (ctx?.ui?.setWidget && e.type === "turn_end") {
122
- ctx.ui.setWidget("fleet", [`▶ ${params.agent} · running`]);
123
- }
124
- },
125
120
  });
126
121
  const isError = res.status === "failed" || res.status === "aborted";
127
122
  return {