pi-goal-list-loop-audit 0.38.6 → 0.38.7
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
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.7 — session visibility: recovery banner + durable verdict tally (2026-09-03)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
Load-hold recovery banner: a consent-less cold load that engages the load hold now also paints objective + next pending task + verdict tally + resume command (`/goal`/`/list`/`/loop` by policy), all from durable disk state — the transcript is empty in exactly the sessions that need this. Fires once with the fresh hold; ledger `load_hold_recovery_banner`. Durable verdict tally (`auditorVerdictTally` via `auditVerdictLabel`): disapproval count + last-verdict age on the auditing status-line footer and the `/goal status` `Audits:` line, silent when history is empty — a reloaded session answers "are we progressing?" from stored verdicts.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
Reload invisibility: resumed-but-empty sessions no longer look goal-less, and capped/queued auditor sessions show stored-verdict evidence instead of a dead surface. Hold mechanics, hold text, auditor phase machine, and auditor card untouched. `tests/session-visibility.test.ts` (5 tests) pins tally classification, banner text, status-line tally, and a behavioral reload.
|
|
10
|
+
|
|
3
11
|
## 0.38.6 — over-cap starvation ladder: compact first, then 5 ordered recoveries (2026-09-03)
|
|
4
12
|
|
|
5
13
|
### Added
|
package/docs/INDEX.md
CHANGED
|
@@ -18,7 +18,7 @@ For shipped docs, the relevant entry points are:
|
|
|
18
18
|
failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
|
|
19
19
|
handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
|
|
20
20
|
shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
|
|
21
|
-
v0.35.14–v0.38.
|
|
21
|
+
v0.35.14–v0.38.7 continue through the supervisor freeze (`/glla pause`),
|
|
22
22
|
load hold, auditor picker parity, Windows launch fix, zombie-watchdog
|
|
23
23
|
subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
|
|
24
24
|
durable state-root selection, blank-until-resume auditor context, frozen
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "./goal-loop-core.js";
|
|
24
24
|
import { clearDispatchRecord, dispatchRecordExists } from "./goal-loop-dispatch.js";
|
|
25
25
|
import type { AuditDisplayProgress } from "./goal-loop-display.js";
|
|
26
|
-
import { fmtElapsed } from "./goal-loop-display.js";
|
|
26
|
+
import { auditorVerdictTally, fmtElapsed, formatVerdictTallySegment } from "./goal-loop-display.js";
|
|
27
27
|
import { AUDIT_FINDINGS_REL, HELD_ON_RESTORE, LOOP_AUDIT_MARKER, listAuditCollectTarget, projectAuditTarget } from "./goal-loop-forever.js";
|
|
28
28
|
import { buildLoopCompletionSummary, compactCompletionSummary, compactTerminalCompletionSummary } from "./completion-summary.js";
|
|
29
29
|
import { ProjectRollup, discoverGllaProjects, filterPremature, formatRollupJson, formatRollupTable, rollupProject } from "./goal-loop-stats.js";
|
|
@@ -381,8 +381,12 @@ async function cmdStatus(ctx: ExtensionContext): Promise<void> {
|
|
|
381
381
|
`Tokens: ${(g.usage?.tokensUsed ?? 0).toLocaleString()}${(g.usage?.tokensLimit ?? 0) > 0 ? ` / ${(g.usage!.tokensLimit).toLocaleString()}` : " (no cap — set Token limit in /glla settings)"}`,
|
|
382
382
|
...formatMainModelRecoveryStatus(state.mainModelRecovery, normalizeMainModelFallbackRefs(loadSettings(ctx.cwd).mainModelFallbacks)),
|
|
383
383
|
];
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
// v0.38.7: /goal status names disapprovals + last-verdict age, not just
|
|
385
|
+
// the approval count — a capped/queued session must show what unblocks.
|
|
386
|
+
const statusTally = auditorVerdictTally(g.auditHistory);
|
|
387
|
+
if (statusTally.total > 0) {
|
|
388
|
+
const tallyText = formatVerdictTallySegment(statusTally);
|
|
389
|
+
lines.push(`Audits: ${tallyText} (${statusTally.approvals} approved)`);
|
|
386
390
|
}
|
|
387
391
|
if (g.status === "auditing") {
|
|
388
392
|
lines.push(`Completion audit: ${isCompletionAuditRecoveryPending(g) ? `recovery pending — ${activeGoalSurfaceCommand("resume")} retries the stored claim` : flags.completionAuditInFlight && flags.latestAuditProgress?.label === "queued" ? "detached auditor queued" : flags.completionAuditInFlight ? "detached auditor running" : "awaiting lifecycle recovery"}`);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { truncateToWidth as tuiTruncateToWidth, visibleWidth as tuiVisibleWidth } from "@earendil-works/pi-tui";
|
|
14
14
|
|
|
15
15
|
import type { DurableDeferRecommendationInput, Goal, MainModelRecovery, State } from "./goal-loop-core.js";
|
|
16
|
-
import { buildDurableDeferRecommendation, compactDisplayText, formatMainModelRecoveryStatus, isMonitorGoal, isPersistenceDegraded, lastPersistenceFailure, sanitizeDisplayText, sanitizeProviderAuditReport, sanitizeProviderDisplayText, stripThinkBlocks } from "./goal-loop-core.js";
|
|
16
|
+
import { auditVerdictLabel, buildDurableDeferRecommendation, compactDisplayText, formatMainModelRecoveryStatus, isMonitorGoal, isPersistenceDegraded, lastPersistenceFailure, sanitizeDisplayText, sanitizeProviderAuditReport, sanitizeProviderDisplayText, stripThinkBlocks } from "./goal-loop-core.js";
|
|
17
17
|
|
|
18
18
|
export { isMonitorGoal };
|
|
19
19
|
import { HELD_ON_RESTORE, type LoopState } from "./goal-loop-forever.js";
|
|
@@ -432,6 +432,68 @@ function latestAuditFeedback(g: Goal): LatestAuditFeedback | undefined {
|
|
|
432
432
|
};
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
/** v0.38.7 (note.md Next: reload recovery + progress signal): durable
|
|
436
|
+
* verdict tally — disapproval count + last-verdict age from auditHistory.
|
|
437
|
+
* After a reload the in-memory auditor progress is gone; the stored
|
|
438
|
+
* verdicts are what answers "are we progressing?". Classification goes
|
|
439
|
+
* through auditVerdictLabel so a shield-blocked approval is never counted
|
|
440
|
+
* as a disapproval and an infra error is never counted as a verdict. */
|
|
441
|
+
export interface AuditorVerdictTally {
|
|
442
|
+
total: number;
|
|
443
|
+
approvals: number;
|
|
444
|
+
disapprovals: number;
|
|
445
|
+
lastAt: number | null;
|
|
446
|
+
lastLabel: string | null;
|
|
447
|
+
}
|
|
448
|
+
export function auditorVerdictTally(history: Goal["auditHistory"], now = Date.now()): AuditorVerdictTally {
|
|
449
|
+
void now;
|
|
450
|
+
const entries = Array.isArray(history) ? history : [];
|
|
451
|
+
let approvals = 0;
|
|
452
|
+
let disapprovals = 0;
|
|
453
|
+
for (const v of entries) {
|
|
454
|
+
const label = auditVerdictLabel(v);
|
|
455
|
+
if (label === "approved") approvals++;
|
|
456
|
+
else if (label === "disapproved") disapprovals++;
|
|
457
|
+
}
|
|
458
|
+
const last = entries[entries.length - 1];
|
|
459
|
+
const lastMs = last ? Date.parse(last.at) : Number.NaN;
|
|
460
|
+
return {
|
|
461
|
+
total: entries.length,
|
|
462
|
+
approvals,
|
|
463
|
+
disapprovals,
|
|
464
|
+
lastAt: last && Number.isFinite(lastMs) ? lastMs : null,
|
|
465
|
+
lastLabel: last ? auditVerdictLabel(last) : null,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/** Compact tally segment for the always-on surfaces. "" when no verdicts. */
|
|
469
|
+
export function formatVerdictTallySegment(t: AuditorVerdictTally, now = Date.now()): string {
|
|
470
|
+
if (t.total <= 0) return "";
|
|
471
|
+
const dis = t.disapprovals > 0 ? ` · ${t.disapprovals} disapproved` : "";
|
|
472
|
+
const age = t.lastAt !== null && t.lastLabel ? ` · last ${t.lastLabel} ${fmtElapsed(now - t.lastAt)} ago` : "";
|
|
473
|
+
return `${t.total} verdict${t.total === 1 ? "" : "s"}${dis}${age}`;
|
|
474
|
+
}
|
|
475
|
+
/** v0.38.7: the load-hold recovery banner — objective + next task + verdict
|
|
476
|
+
* tally + resume command, all from durable disk state (never transcript
|
|
477
|
+
* memory, which is empty in exactly the sessions that need this). */
|
|
478
|
+
export interface LoadHoldRecoverySummary {
|
|
479
|
+
objective?: string | null;
|
|
480
|
+
status?: string;
|
|
481
|
+
nextTask?: string | null;
|
|
482
|
+
tally: AuditorVerdictTally;
|
|
483
|
+
resumeCommand: string;
|
|
484
|
+
listWaiting?: number;
|
|
485
|
+
}
|
|
486
|
+
export function buildLoadHoldRecoveryLines(s: LoadHoldRecoverySummary, now = Date.now()): string[] {
|
|
487
|
+
const lines = [
|
|
488
|
+
`glla: recovered from disk — "${truncate((s.objective ?? "").trim() || "(no objective recorded)", 120)}" (${s.status ?? "held"})`,
|
|
489
|
+
];
|
|
490
|
+
lines.push(s.nextTask ? `next: ${truncate(s.nextTask, 100)}` : `next: no pending tasks recorded`);
|
|
491
|
+
const tally = formatVerdictTallySegment(s.tally, now);
|
|
492
|
+
lines.push(tally ? `audits: ${tally}` : `audits: none yet`);
|
|
493
|
+
if ((s.listWaiting ?? 0) > 0) lines.push(`list: ${s.listWaiting} waiting — /list to manage`);
|
|
494
|
+
lines.push(`run ${s.resumeCommand} to continue`);
|
|
495
|
+
return lines;
|
|
496
|
+
}
|
|
435
497
|
function activeAttention(g: Goal): ActiveAttention | undefined {
|
|
436
498
|
if (g.status !== "active" || !g.pauseReason) return undefined;
|
|
437
499
|
if (/regression shield/i.test(g.pauseReason)) {
|
|
@@ -954,7 +1016,12 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
|
|
|
954
1016
|
const quietSuffix = quietAge !== undefined ? ` · silent ${fmtElapsed(quietAge)}` : "";
|
|
955
1017
|
const next = ` · next: ${auditorNextTransition(phase)}`;
|
|
956
1018
|
const detachedSuffix = live ? "" : " · detached worker";
|
|
957
|
-
|
|
1019
|
+
// v0.38.7: durable verdict tally on the always-on footer — after a
|
|
1020
|
+
// reload there is no live auditor evidence, so the stored verdicts
|
|
1021
|
+
// (disapproval count + last-verdict age) answer "are we progressing?".
|
|
1022
|
+
const tallyText = formatVerdictTallySegment(auditorVerdictTally(g.auditHistory, now), now);
|
|
1023
|
+
const verdictSuffix = tallyText ? ` · ${tallyText}` : "";
|
|
1024
|
+
return `glla: ${host} · ${label}${quietSuffix}${next}${detachedSuffix}${verdictSuffix}${heldSuffix}`;
|
|
958
1025
|
}
|
|
959
1026
|
if (g.status === "paused") {
|
|
960
1027
|
// v0.28.22: the status line names the ACTIONABILITY, not the reason —
|
|
@@ -238,7 +238,7 @@ import {
|
|
|
238
238
|
textFingerprint,
|
|
239
239
|
pushCapped as pushRepetitionCapped,
|
|
240
240
|
} from "../goal-loop-repetition.js";
|
|
241
|
-
import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
|
|
241
|
+
import { auditorVerdictTally, buildLoadHoldRecoveryLines, buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
|
|
242
242
|
import { compactLoopCompletionSummary } from "../completion-summary.js";
|
|
243
243
|
import {
|
|
244
244
|
defaultAgentDir,
|
|
@@ -1864,6 +1864,34 @@ export function registerGoalRuntime(pi: ExtensionAPI): void {
|
|
|
1864
1864
|
"Loaded without starting: your goal/list/loop state is restored and shown below, but automation is HELD for your decision. /goal resume, /list resume, or /list next starts work; enable Auto-resume in /glla settings to restore load-time automation.",
|
|
1865
1865
|
"warning",
|
|
1866
1866
|
);
|
|
1867
|
+
// v0.38.7 (note.md Next: objectives seemingly lost on reload) —
|
|
1868
|
+
// the recovery banner: objective + next task + verdict tally +
|
|
1869
|
+
// resume command, all from durable disk state. The transcript is
|
|
1870
|
+
// empty in exactly the sessions that need this, so nothing here
|
|
1871
|
+
// may depend on in-memory progress. Fires with the fresh hold
|
|
1872
|
+
// (guarded above), never as a timer re-arm.
|
|
1873
|
+
const tasks = state.goal?.taskList?.tasks ?? [];
|
|
1874
|
+
const pendingTasks = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
|
|
1875
|
+
const tally = auditorVerdictTally(state.goal?.auditHistory);
|
|
1876
|
+
const resumeCommand = state.goal
|
|
1877
|
+
? (state.goal.policy === "list" ? "/list resume" : "/goal resume")
|
|
1878
|
+
: (state.list?.length ?? 0) > 0 ? "/list resume" : state.loop ? "/loop resume" : "/goal resume";
|
|
1879
|
+
const banner = buildLoadHoldRecoveryLines({
|
|
1880
|
+
objective: state.goal?.objective ?? ((state.list?.length ?? 0) > 0 ? `${state.list!.length} queued list items` : null),
|
|
1881
|
+
status: state.goal?.status ?? (state.loop ? "loop" : "held"),
|
|
1882
|
+
nextTask: pendingTasks[0]?.title ?? null,
|
|
1883
|
+
tally,
|
|
1884
|
+
resumeCommand,
|
|
1885
|
+
listWaiting: state.goal?.policy === "list" ? undefined : state.list?.length ?? 0,
|
|
1886
|
+
});
|
|
1887
|
+
appendLedger(ctx.cwd, "load_hold_recovery_banner", {
|
|
1888
|
+
goalId: state.goal?.id ?? null,
|
|
1889
|
+
status: state.goal?.status ?? null,
|
|
1890
|
+
pendingTasks: pendingTasks.length,
|
|
1891
|
+
totalVerdicts: tally.total,
|
|
1892
|
+
disapprovals: tally.disapprovals,
|
|
1893
|
+
});
|
|
1894
|
+
ctx.ui.notify(banner.join("\n"), "info");
|
|
1867
1895
|
}
|
|
1868
1896
|
} else if ((autoResume || explicitRecovery) && typeof state.loadHoldAt === "number") {
|
|
1869
1897
|
// v0.35.28 (issue #16): a hold persisted by a PREVIOUS process must
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.7",
|
|
4
4
|
"description": "Mission control for autonomous pi: interview-drafted goals, an audited task queue, and forever-loops (metric, spec, project-audit) that run for hours. A detached extension-less auditor process re-verifies every completion with raw evidence without holding the main pi turn; confirmed drafts, decision pauses and consent gates keep you in charge.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "dracon",
|