@getpipher/armory-fleet 0.10.1 → 0.10.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.10.1",
3
+ "version": "0.10.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
@@ -258,7 +258,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
258
258
  // SPEC-5b-1: per-session RunLog at .pi/fleet/conversations/ (separate from the
259
259
  // SPEC-5a phase journal at .pi/fleet/runs/ — different granularity, no filename collision).
260
260
  deps.runLog = new RunLog(join(dir, "conversations"));
261
- const reconciled = reconcileRuns(deps.runLog);
261
+ // v0.10.2: pass the in-memory RunRegistry so reconcile syncs it too — otherwise orphaned
262
+ // (process-gone) runs keep status:"running" in memory and the live widget shows a stale ▶ forever.
263
+ const reconciled = reconcileRuns(deps.runLog, { runRegistry: deps.runRegistry });
262
264
  if (reconciled.length > 0) {
263
265
  ctx.ui.notify(`reconciled ${reconciled.length} interrupted fleet run${reconciled.length > 1 ? "s" : ""} (marked aborted)`, "info");
264
266
  }
@@ -10,7 +10,9 @@ const STATUS_GLYPH: Record<RunMeta["status"], string> = {
10
10
 
11
11
  export function runsRow(r: RunMeta, getModelContextWindow?: (model: string) => number | undefined): string {
12
12
  const dur = r.endedAt ? fmtDuration(r.endedAt - r.startedAt) : "—";
13
- const tok = r.tokenTotal > 0 ? ` ${fmtTokens(r.tokenTotal)} tok` : "";
13
+ // SPEC-6-1 fix: "tok" is the final context snapshot (contextTokens), NOT cumulative
14
+ // tokenTotal — it pairs with the ctx% segment (same metric).
15
+ const tok = r.contextTokens != null && r.contextTokens > 0 ? ` ${fmtTokens(r.contextTokens)} tok` : "";
14
16
  const maxCtx = getModelContextWindow?.(r.model);
15
17
  const ctx = (r.contextTokens != null && maxCtx != null && maxCtx > 0) ? ` ${Math.round(r.contextTokens / maxCtx * 100)}%` : "";
16
18
  const cost = r.costTotal ? ` $${r.costTotal.toFixed(4)}` : "";
@@ -72,7 +72,10 @@ const STATUS_GLYPH: Record<WidgetRun["status"], string> = {
72
72
  function widgetLine(r: WidgetRun, now: number): string {
73
73
  const glyph = STATUS_GLYPH[r.status];
74
74
  const dur = typeof r.startedAt === "number" ? ` ${fmtDuration(now - r.startedAt)}` : "";
75
- const tok = r.tokenTotal ? ` ${fmtTokens(r.tokenTotal)} tok` : "";
75
+ // SPEC-6-1 fix: "tok" is the live context snapshot (contextTokens), NOT cumulative
76
+ // tokenTotal — it pairs with the ctx% segment (same metric). Showing tokenTotal here
77
+ // ballooned to 6.7M on long runs (cumulative re-sends) next to a 35% ctx, looking broken.
78
+ const tok = r.contextTokens != null ? ` ${fmtTokens(r.contextTokens)} tok` : "";
76
79
  const ctx = (r.contextTokens != null && r.maxContext != null && r.maxContext > 0) ? ` ${Math.round(r.contextTokens / r.maxContext * 100)}%` : "";
77
80
  const cost = r.costTotal ? ` $${r.costTotal.toFixed(4)}` : "";
78
81
 
@@ -2,19 +2,32 @@
2
2
  // SPEC-5b-1 — on pi boot, mark orphan RunLog runs (run:meta with no run:ended whose process
3
3
  // is gone) as aborted so the Runs tab doesn't show stale "running" rows across restarts.
4
4
  // Foreground orphans; bg/lifecycle orphans are already handled by scanResumeCandidates (SPEC-5a).
5
+ //
6
+ // v0.10.2 patch: reconcile now ALSO syncs the in-memory RunRegistry (opts.runRegistry). Before this,
7
+ // reconcile only wrote run:ended: aborted to the durable RunLog — the in-memory RunRegistry kept
8
+ // status:"running", so the live above-editor widget (filterActive keeps running|queued|paused)
9
+ // rendered a stale ▶ row that ticked forever for every orphaned (process-gone) run.
5
10
  import type { RunLog } from "./run-log.ts";
11
+ import type { RunRegistry } from "../engine/run-registry.ts";
6
12
 
7
13
  export interface ReconcileOpts {
8
14
  /** Orphans whose startedAt is older than (now - graceMs) are marked aborted. Default 60000. */
9
15
  graceMs?: number;
10
16
  /** Test injection. Default Date.now(). */
11
17
  now?: number;
18
+ /**
19
+ * v0.10.2: the in-memory RunRegistry to sync alongside the durable log. When set, each orphan
20
+ * reconciled in the log is also transitioned to status:"aborted" in memory so the live widget
21
+ * clears its stale ▶ row. Optional — existing callers that pass only a RunLog are unaffected.
22
+ */
23
+ runRegistry?: RunRegistry;
12
24
  }
13
25
 
14
26
  /** Returns the runIds it marked aborted. Idempotent: a run already ended is skipped. */
15
27
  export function reconcileRuns(log: RunLog, opts: ReconcileOpts = {}): string[] {
16
28
  const grace = opts.graceMs ?? 60_000;
17
29
  const now = opts.now ?? Date.now();
30
+ const reg = opts.runRegistry;
18
31
  const aborted: string[] = [];
19
32
  for (const meta of log.scanMeta()) {
20
33
  if (meta.status !== "running") continue;
@@ -23,6 +36,10 @@ export function reconcileRuns(log: RunLog, opts: ReconcileOpts = {}): string[] {
23
36
  type: "run:ended", runId: meta.runId, status: "aborted",
24
37
  endedAt: now, resultSummary: "process-gone", tokenTotal: meta.tokenTotal,
25
38
  });
39
+ // v0.10.2: sync the in-memory registry so the live widget (which reads runRegistry.list(),
40
+ // not the RunLog) clears the orphan's stale ▶ row. No-op when the run isn't in the registry
41
+ // (e.g. a cross-cwd orphan from another session — out of scope for this patch).
42
+ reg?.update(meta.runId, { status: "aborted", endedAt: now });
26
43
  aborted.push(meta.runId);
27
44
  }
28
45
  return aborted;