pi-goal-list-loop-audit 0.39.21 → 0.39.22
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/CHANGELOG.md +14 -0
- package/docs/INDEX.md +1 -1
- package/docs/RUNTIME-INVENTORY.md +3 -3
- package/extensions/goal-loop-display.ts +35 -9
- package/extensions/loops/goal-ui.ts +23 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.39.22 — stalled-audit UI: dead audits named on every surface (2026-10-10)
|
|
6
|
+
|
|
7
|
+
### Audit visibility
|
|
8
|
+
|
|
9
|
+
- Past one hour of worker silence with no in-budget tool, a quiet audit
|
|
10
|
+
escalates to **stalled** on every surface: the detailed card drops the
|
|
11
|
+
live-worker claim (`no worker responds`) and offers the retry
|
|
12
|
+
(`/goal resume retries the claim`); the compact glance card and footer
|
|
13
|
+
agree (`AUDIT STALLED`, error tone, ✖ glyph) instead of claiming
|
|
14
|
+
`detached worker` / `No action needed`. The quiet watcher fires its own
|
|
15
|
+
one-shot stall notice.
|
|
16
|
+
- Elapsed ages read in days past 24h (`15d 00h`, not `360h 00m`).
|
|
17
|
+
- Display-only: no retry or recovery behavior changed.
|
|
18
|
+
|
|
5
19
|
## 0.39.21 — 5h retry-window policy, stall-proof test cleanup (2026-10-09)
|
|
6
20
|
|
|
7
21
|
### Recovery policy
|
package/docs/INDEX.md
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
## Versions and shipped content
|
|
32
32
|
|
|
33
33
|
[CHANGELOG.md](../CHANGELOG.md) records the release history from
|
|
34
|
-
v0.35.14–v0.39.
|
|
34
|
+
v0.35.14–v0.39.22 and unreleased work. `/glla version` reports the loaded version
|
|
35
35
|
and registry comparison. A checkout can contain changes not yet published.
|
|
36
36
|
|
|
37
37
|
The package includes this documentation, [examples](../examples/),
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Generated by `node scripts/generate-inventory.mjs`. Run with `--check` to detect drift. Counts describe files and literal registrations, not executed coverage.
|
|
4
4
|
|
|
5
5
|
- Extension/script files: 109
|
|
6
|
-
- Extension/script lines:
|
|
7
|
-
- Top-level test files:
|
|
6
|
+
- Extension/script lines: 67535
|
|
7
|
+
- Top-level test files: 379
|
|
8
8
|
- Ambient bridge names: 179 (audit baseline: 179)
|
|
9
9
|
- Commands: `/glla`, `/goal`, `/list`, `/loop`, `/review`
|
|
10
10
|
- Tools: `audit_project_increment`, `block_project_requirement`, `claim_project_task`, `complete_goal`, `complete_task`, `list_activate`, `list_add`, `list_status`, `pause_goal`, `plan_project_increment`, `propose_goal_draft`, `propose_loop_draft`, `propose_loop_refine`, `propose_project_refinement`, `propose_project_requirements`, `propose_task_list`, `record_goal_judgment`, `resume_goal`, `unblock_project_requirement`, `update_task_batch`, `update_task_status`, `wait_for_background`
|
|
@@ -17,7 +17,7 @@ Generated by `node scripts/generate-inventory.mjs`. Run with `--check` to detect
|
|
|
17
17
|
| [extensions/loops/goal-tools.ts](../extensions/loops/goal-tools.ts) | 4227 |
|
|
18
18
|
| [extensions/loops/goal-activation.ts](../extensions/loops/goal-activation.ts) | 3880 |
|
|
19
19
|
| [extensions/goal-commands.ts](../extensions/goal-commands.ts) | 3614 |
|
|
20
|
-
| [extensions/goal-loop-display.ts](../extensions/goal-loop-display.ts) |
|
|
20
|
+
| [extensions/goal-loop-display.ts](../extensions/goal-loop-display.ts) | 2884 |
|
|
21
21
|
| [extensions/goal-loop-auditor-process.ts](../extensions/goal-loop-auditor-process.ts) | 2685 |
|
|
22
22
|
| [extensions/loops/goal-auditor-hooks.ts](../extensions/loops/goal-auditor-hooks.ts) | 2654 |
|
|
23
23
|
| [extensions/goal-heartbeat.ts](../extensions/goal-heartbeat.ts) | 2104 |
|
|
@@ -58,7 +58,11 @@ export function fmtElapsed(ms: number): string {
|
|
|
58
58
|
// liveness signal — minute-only granularity looks frozen on a 1s tick.
|
|
59
59
|
if (m < 60) return `${m}m ${String(s % 60).padStart(2, "0")}s`;
|
|
60
60
|
const h = Math.floor(m / 60);
|
|
61
|
-
return `${h}h ${String(m % 60).padStart(2, "0")}m`;
|
|
61
|
+
if (h < 24) return `${h}h ${String(m % 60).padStart(2, "0")}m`;
|
|
62
|
+
// v0.39.22: day granularity past 24h — a 15-day stalled audit reads
|
|
63
|
+
// "15d 00h", not "360h 00m". Only affects renders past a day.
|
|
64
|
+
const d = Math.floor(h / 24);
|
|
65
|
+
return `${d}d ${String(h % 24).padStart(2, "0")}h`;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
export function fmtTokens(n: number): string {
|
|
@@ -801,11 +805,17 @@ export interface AuditDisplayProgress {
|
|
|
801
805
|
sessionPath?: string;
|
|
802
806
|
}
|
|
803
807
|
|
|
804
|
-
type AuditorDisplayPhase = "queued" | "running" | "quiet" | "blocked" | "awaiting-verdict";
|
|
808
|
+
type AuditorDisplayPhase = "queued" | "running" | "quiet" | "stalled" | "blocked" | "awaiting-verdict";
|
|
805
809
|
/** v0.35.15: exported — the parent-side quiet watcher (goal-ui ticker) uses
|
|
806
810
|
* the same threshold for the one-shot proactive notify so the notification
|
|
807
811
|
* and the status chip can never disagree about when "quiet" begins. */
|
|
808
812
|
export const AUDITOR_QUIET_MS = 3 * 60_000;
|
|
813
|
+
/** v0.39.22 (field 2026-10-10: seo goal quiet 15 days, worker dead):
|
|
814
|
+
* quiet past this bound with no worker activity and no in-budget tool is
|
|
815
|
+
* a dead worker, not a slow one. An hour of total silence exceeds any
|
|
816
|
+
* legitimate inter-tool gap; the stalled presentation names the death and
|
|
817
|
+
* offers the retry (/goal resume) instead of only the discard. */
|
|
818
|
+
export const AUDITOR_STALLED_MS = 60 * 60_000;
|
|
809
819
|
/** v0.35.15: how long the "silent Xm then resumed" fact stays in the
|
|
810
820
|
* detailed auditor card after a quiet stretch ends — long enough to be seen,
|
|
811
821
|
* short enough not to become permanent noise. */
|
|
@@ -911,6 +921,7 @@ export function auditorDisplayPhase(g: Goal, audit: AuditDisplayProgress | null
|
|
|
911
921
|
toolAgeMs < audit.toolTimeoutMs
|
|
912
922
|
)
|
|
913
923
|
return "running";
|
|
924
|
+
if (age > AUDITOR_STALLED_MS) return "stalled";
|
|
914
925
|
return "quiet";
|
|
915
926
|
}
|
|
916
927
|
// Same lifecycle scope as above: a stale goal snapshot that still
|
|
@@ -959,6 +970,7 @@ function auditorPhaseLabel(phase: AuditorDisplayPhase): string {
|
|
|
959
970
|
case "queued": return "queued";
|
|
960
971
|
case "running": return "running";
|
|
961
972
|
case "quiet": return "quiet";
|
|
973
|
+
case "stalled": return "stalled";
|
|
962
974
|
case "blocked": return "blocked";
|
|
963
975
|
case "awaiting-verdict": return "awaiting completion review";
|
|
964
976
|
}
|
|
@@ -973,6 +985,7 @@ export function auditorPhaseGlyph(phase: AuditorDisplayPhase): string {
|
|
|
973
985
|
case "queued": return "⋯"; // waiting to start
|
|
974
986
|
case "running": return "▶"; // worker alive
|
|
975
987
|
case "quiet": return "◌"; // silent — possibly stuck
|
|
988
|
+
case "stalled": return "✖"; // silent past the death bound — worker dead
|
|
976
989
|
case "blocked": return "⛔"; // infra failure / needs intervention
|
|
977
990
|
case "awaiting-verdict": return "✓"; // worker done, verdict pending
|
|
978
991
|
}
|
|
@@ -1508,11 +1521,11 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
|
|
|
1508
1521
|
// v0.35.15: leading phase glyph + draining activity meter — a glance
|
|
1509
1522
|
// answers "is the audit alive?" without reading the sentence.
|
|
1510
1523
|
const phaseText = `auditor ${auditorPhaseGlyph(phase)} ${observed}`;
|
|
1511
|
-
const color = phase === "blocked" || phase === "quiet" ? "warning" : live ? "success" : "accent";
|
|
1524
|
+
const color = phase === "stalled" ? "error" : phase === "blocked" || phase === "quiet" ? "warning" : live ? "success" : "accent";
|
|
1512
1525
|
const activityMeter = auditorActivityMeter(audit, phase, now);
|
|
1513
1526
|
const label = live
|
|
1514
1527
|
? `${paint(theme, "success", phaseText)} ${paint(theme, "accent", activityMeter)} ${activityBadge("AUDITOR · DETACHED · LIVE", now, theme)}`
|
|
1515
|
-
: `${paint(theme, color, phaseText)} ${paint(theme, color === "warning"
|
|
1528
|
+
: `${paint(theme, color, phaseText)} ${paint(theme, color === "warning" || color === "error" ? color : "dim", activityMeter)}`;
|
|
1516
1529
|
// The persistent footer is the liveness surface only: host, phase, and
|
|
1517
1530
|
// freshness. The auditor card above already owns the transition hint
|
|
1518
1531
|
// ("next:"), the worker attribution ("detached worker"), and the
|
|
@@ -1521,7 +1534,7 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
|
|
|
1521
1534
|
const quietAge = phase === "quiet" ? auditorActivityAge(audit, now) : undefined;
|
|
1522
1535
|
const quietSuffix = quietAge !== undefined ? ` · silent ${fmtElapsed(quietAge)}` : "";
|
|
1523
1536
|
const compactPrefix = extras?.compactAuditCard
|
|
1524
|
-
? `${paint(theme, color, durableLabel ?? (toolWait ? `AUDIT TOOL WAIT · ${sanitizeDisplayText(audit!.currentTool!)} ${fmtElapsed(now - audit!.currentToolStartedAt!)} / ${fmtElapsed(audit!.toolTimeoutMs!)}` : phase === "running" ? "AUDIT RUNNING" : phase === "quiet" ? "AUDIT QUIET — may be stuck" : phase === "blocked" ? "AUDIT BLOCKED" : phase === "queued" ? "AUDIT STARTING" : "AUDIT REVIEW"))} · `
|
|
1537
|
+
? `${paint(theme, color, durableLabel ?? (toolWait ? `AUDIT TOOL WAIT · ${sanitizeDisplayText(audit!.currentTool!)} ${fmtElapsed(now - audit!.currentToolStartedAt!)} / ${fmtElapsed(audit!.toolTimeoutMs!)}` : phase === "running" ? "AUDIT RUNNING" : phase === "quiet" ? "AUDIT QUIET — may be stuck" : phase === "stalled" ? "AUDIT STALLED — worker dead" : phase === "blocked" ? "AUDIT BLOCKED" : phase === "queued" ? "AUDIT STARTING" : "AUDIT REVIEW"))} · `
|
|
1525
1538
|
: "";
|
|
1526
1539
|
const activityAge = auditorActivityAge(audit, now);
|
|
1527
1540
|
const freshness = extras?.compactAuditCard && phase !== "quiet" && !toolWait
|
|
@@ -1983,12 +1996,13 @@ function heldLoopLines(l: LoopState, now: number, theme?: DisplayTheme, width?:
|
|
|
1983
1996
|
}
|
|
1984
1997
|
|
|
1985
1998
|
/** Phase colour for the auditing card's lead row. Blocked/quiet are
|
|
1986
|
-
* warnings; a verdict that landed or live worker evidence is success;
|
|
1999
|
+
* warnings, stalled is an error; a verdict that landed or live worker evidence is success;
|
|
1987
2000
|
* queued/running-without-evidence stay neutral accent — a hung provider
|
|
1988
2001
|
* must never wear success (v0.34.39). Colour always pairs with the phase
|
|
1989
2002
|
* words (paint wraps the label, never replaces it), so a themeless
|
|
1990
2003
|
* renderer loses colour but never meaning. */
|
|
1991
2004
|
function auditorPhaseTone(phase: AuditorDisplayPhase, live: boolean): DisplayColor {
|
|
2005
|
+
if (phase === "stalled") return "error";
|
|
1992
2006
|
if (phase === "blocked" || phase === "quiet") return "warning";
|
|
1993
2007
|
if (phase === "awaiting-verdict" || (phase === "running" && live)) return "success";
|
|
1994
2008
|
return "accent";
|
|
@@ -2009,6 +2023,7 @@ function auditorCardModelRef(audit: AuditDisplayProgress | null | undefined, cla
|
|
|
2009
2023
|
* row and the closing line can never name different actions. */
|
|
2010
2024
|
function auditorNextAction(phase: AuditorDisplayPhase): string {
|
|
2011
2025
|
if (phase === "quiet") return "/goal cancel discards the claim";
|
|
2026
|
+
if (phase === "stalled") return "/goal resume retries the claim";
|
|
2012
2027
|
if (phase === "blocked") return "/goal resume retries the claim";
|
|
2013
2028
|
if (phase === "awaiting-verdict") return "completion review applying";
|
|
2014
2029
|
if (phase === "queued") return "worker starting";
|
|
@@ -2082,7 +2097,11 @@ function auditingCardBlock(g: Goal, audit: AuditDisplayProgress | null | undefin
|
|
|
2082
2097
|
const staleSeg = activity === undefined && durable?.stale && durable.idleMs !== undefined
|
|
2083
2098
|
? ` · no progress ${fmtElapsed(durable.idleMs)}`
|
|
2084
2099
|
: "";
|
|
2085
|
-
|
|
2100
|
+
// v0.39.22: a stalled audit asserts no live worker — printing
|
|
2101
|
+
// "detached worker" would claim a process that has not spoken for over
|
|
2102
|
+
// an hour. The `auditor:` anchor stays; only the worker segment changes.
|
|
2103
|
+
const workerSeg = phase === "stalled" ? "no worker responds" : "detached worker";
|
|
2104
|
+
const lead = [`├─ auditor: ${paint(theme, auditorPhaseTone(phase, phaseLive), effectivePhaseLabel)}${detail} · ${workerSeg} · ${ageSeg}${staleSeg}`];
|
|
2086
2105
|
// Lead row 2: current tool + time budget, effective model + thinking.
|
|
2087
2106
|
// Absent facts are omitted, never invented. The tool observation lives
|
|
2088
2107
|
// ONLY here (it moved out of the tail) so the card keeps its
|
|
@@ -2161,7 +2180,9 @@ function auditingCardBlock(g: Goal, audit: AuditDisplayProgress | null | undefin
|
|
|
2161
2180
|
tail.push(`${i === 0 ? "├─" : "│ "} ${paint(theme, "dim", observation)}`);
|
|
2162
2181
|
});
|
|
2163
2182
|
const last = auditorLastActivity(audit, now);
|
|
2164
|
-
if (phase === "
|
|
2183
|
+
if (phase === "stalled") {
|
|
2184
|
+
tail.push(`└─ ${paint(theme, "error", `auditor stalled ${fmtElapsed(activity ?? 0)} — no worker activity, claim durable; /goal resume retries, /goal cancel discards`)}`);
|
|
2185
|
+
} else if (phase === "quiet") {
|
|
2165
2186
|
const quietMs = activity ?? 0;
|
|
2166
2187
|
tail.push(`└─ ${paint(theme, "warning", `auditor quiet ${fmtElapsed(quietMs)}${last} — may be stuck; /goal cancel discards the claim`)}`);
|
|
2167
2188
|
} else if (phase === "blocked") {
|
|
@@ -2306,7 +2327,12 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
2306
2327
|
if (extras?.compactAuditCard) {
|
|
2307
2328
|
const phase = auditorDisplayPhase(g, audit, now);
|
|
2308
2329
|
const durable = auditLifecycleProjection(g.pendingCompletion, { now });
|
|
2309
|
-
|
|
2330
|
+
// v0.39.22: stalled needs recovery like quiet/blocked — without it
|
|
2331
|
+
// the glance path falls through to "Audit review pending · detached
|
|
2332
|
+
// worker" + "No action needed", preserving the can't-tell gap on
|
|
2333
|
+
// the production surface. With it, the glance reuses the detailed
|
|
2334
|
+
// lead (stalled · no worker responds) and its retry action row.
|
|
2335
|
+
const needsRecovery = auditRecoveryPending(g) || phase === "quiet" || phase === "stalled" || phase === "blocked" || durable?.phase === "settling";
|
|
2310
2336
|
const elapsed = auditorElapsedMs(audit, now);
|
|
2311
2337
|
const finished = audit?.toolCalls?.filter(call => call.finishedAt !== undefined).length;
|
|
2312
2338
|
const age = auditorActivityAge(audit, now);
|
|
@@ -914,6 +914,8 @@ function scheduleUIRefresh(): void {
|
|
|
914
914
|
// suppresses the notify (it is automatic machinery), never the recording.
|
|
915
915
|
let auditorQuietSince: number | null = null;
|
|
916
916
|
let auditorQuietNotified = false;
|
|
917
|
+
let auditorStallSince: number | null = null;
|
|
918
|
+
let auditorStallNotified = false;
|
|
917
919
|
let lastAuditorQuietStretch: { ms: number; endedAt: number } | null = null;
|
|
918
920
|
|
|
919
921
|
/** Drive the quiet watcher once per UI tick. Returns a warning message to
|
|
@@ -929,6 +931,8 @@ export function __auditorQuietWatchTick(now = Date.now()): string | null {
|
|
|
929
931
|
auditorQuietSince = null;
|
|
930
932
|
auditorQuietNotified = false;
|
|
931
933
|
}
|
|
934
|
+
auditorStallSince = null;
|
|
935
|
+
auditorStallNotified = false;
|
|
932
936
|
return null;
|
|
933
937
|
}
|
|
934
938
|
const phase = auditorDisplayPhase(g, latestAuditProgress, now);
|
|
@@ -945,6 +949,23 @@ export function __auditorQuietWatchTick(now = Date.now()): string | null {
|
|
|
945
949
|
}
|
|
946
950
|
return null;
|
|
947
951
|
}
|
|
952
|
+
// v0.39.22: a stalled worker gets its own one-shot notify — the quiet
|
|
953
|
+
// notice fired an hour ago and reads as routine by now. The claim stays
|
|
954
|
+
// durable either way; the notify names the retry, not just the discard.
|
|
955
|
+
if (phase === "stalled") {
|
|
956
|
+
if (auditorStallSince === null) {
|
|
957
|
+
auditorStallSince = typeof latestAuditProgress?.lastActivityAt === "number"
|
|
958
|
+
? latestAuditProgress.lastActivityAt
|
|
959
|
+
: now;
|
|
960
|
+
}
|
|
961
|
+
if (!auditorStallNotified && !supervisorPaused(state)) {
|
|
962
|
+
auditorStallNotified = true;
|
|
963
|
+
return `glla: the detached auditor is STALLED — no worker activity for ${fmtElapsed(now - auditorStallSince)} and no tool running. The claim is durable; /goal resume retries it, /goal cancel discards it.`;
|
|
964
|
+
}
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
if (auditorStallSince !== null) auditorStallSince = null;
|
|
968
|
+
auditorStallNotified = false;
|
|
948
969
|
// Left the quiet phase: record the ended stretch for the footer summary.
|
|
949
970
|
if (auditorQuietSince !== null) {
|
|
950
971
|
const ms = now - auditorQuietSince;
|
|
@@ -1212,6 +1233,8 @@ export function __testOnlyAuditorQuietWatchTick(
|
|
|
1212
1233
|
export function __testOnlyResetAuditorQuietWatch(): void {
|
|
1213
1234
|
auditorQuietSince = null;
|
|
1214
1235
|
auditorQuietNotified = false;
|
|
1236
|
+
auditorStallSince = null;
|
|
1237
|
+
auditorStallNotified = false;
|
|
1215
1238
|
lastAuditorQuietStretch = null;
|
|
1216
1239
|
}
|
|
1217
1240
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.22",
|
|
4
4
|
"description": "Durable work supervision for Pi: turn outcomes into goals, audited queues and spec-driven builds, recover interruptions, and verify completion against evidence.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "dracon",
|