@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 +1 -1
- package/src/index.ts +2 -2
- package/src/panel/fleet-widget.ts +13 -14
- package/src/panel/widget-rows.ts +8 -8
- package/src/tools/subagent.ts +4 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpipher/armory-fleet",
|
|
3
|
-
"version": "0.9.
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
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,
|
|
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"
|
|
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.
|
|
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
|
|
89
|
-
try { this.deps.ui.setWidget(WIDGET_KEY,
|
|
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
|
|
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.
|
|
111
|
+
this.clearWidget();
|
|
113
112
|
}
|
|
114
113
|
}
|
package/src/panel/widget-rows.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// src/panel/widget-rows.ts
|
|
2
|
-
// SPEC-5b-2 — pure render functions for the live widget (above editor)
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
-
}
|
package/src/tools/subagent.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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 {
|