pi-goal-list-loop-audit 0.38.6 → 0.38.8
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 +16 -0
- package/docs/INDEX.md +1 -1
- package/extensions/goal-commands.ts +7 -3
- package/extensions/goal-loop-display.ts +82 -4
- package/extensions/loops/goal-activation.ts +29 -1
- package/extensions/loops/goal-ui.ts +10 -5
- package/extensions/settings-menu.ts +10 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.8 — TUI information-density pass (2026-09-03)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
Widget `audits:` row (verdict tally, width-truncated, silent when empty); verdict tally suffix on all paused status lines via `pausedStatusSuffix`; per-tab row counts in the settings menu (`settingsTabLabel`, pure). Starvation ladder reflowed to one recovery per line, same content and phrases.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
Glance-density gaps: parked sessions read as waiting-with-history instead of dead; ladder recoveries scannable; settings tabs locate content. Display-only — no automation, hold, auditor, or menu-structure changes. `tests/tui-density.test.ts` (5 tests) pins all four surfaces plus an 80-column widget fit; before/after captures in `audit/TUI-DENSITY-2026-09-03.md`.
|
|
10
|
+
|
|
11
|
+
## 0.38.7 — session visibility: recovery banner + durable verdict tally (2026-09-03)
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
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.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
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.
|
|
18
|
+
|
|
3
19
|
## 0.38.6 — over-cap starvation ladder: compact first, then 5 ordered recoveries (2026-09-03)
|
|
4
20
|
|
|
5
21
|
### 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.8 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)) {
|
|
@@ -861,10 +923,14 @@ function pausedStatusSuffix(g: Goal, state: State, extras: WidgetExtras | undefi
|
|
|
861
923
|
const queueLabel = (state.list?.length ?? 0) > 0 ? `${state.list!.length} queued` : "queue empty";
|
|
862
924
|
const lifecycleText = lifecycle.replace(/^lifecycle: /, "");
|
|
863
925
|
const repair = g.repairTarget ? ` · replan required: ${truncate(g.repairTarget.objective.replace(/\s+/g, " "), 100)}` : "";
|
|
926
|
+
// v0.38.8: parked goals carry their verdict tally — a paused session
|
|
927
|
+
// with 2 disapprovals reads as "waiting with history", not dead.
|
|
928
|
+
const tallyText = formatVerdictTallySegment(auditorVerdictTally(g.auditHistory, now), now);
|
|
929
|
+
const verdict = tallyText ? ` · ${tallyText}` : "";
|
|
864
930
|
if (queueFirst) {
|
|
865
|
-
return ` · ${queueLabel} · ${lifecycleText.replace(` · ${queueLabel}`, "")} · ${transition}${repair}`;
|
|
931
|
+
return ` · ${queueLabel} · ${lifecycleText.replace(` · ${queueLabel}`, "")} · ${transition}${repair}${verdict}`;
|
|
866
932
|
}
|
|
867
|
-
return ` · ${lifecycleText} · ${transition}${repair}`;
|
|
933
|
+
return ` · ${lifecycleText} · ${transition}${repair}${verdict}`;
|
|
868
934
|
}
|
|
869
935
|
|
|
870
936
|
/**
|
|
@@ -954,7 +1020,12 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
|
|
|
954
1020
|
const quietSuffix = quietAge !== undefined ? ` · silent ${fmtElapsed(quietAge)}` : "";
|
|
955
1021
|
const next = ` · next: ${auditorNextTransition(phase)}`;
|
|
956
1022
|
const detachedSuffix = live ? "" : " · detached worker";
|
|
957
|
-
|
|
1023
|
+
// v0.38.7: durable verdict tally on the always-on footer — after a
|
|
1024
|
+
// reload there is no live auditor evidence, so the stored verdicts
|
|
1025
|
+
// (disapproval count + last-verdict age) answer "are we progressing?".
|
|
1026
|
+
const tallyText = formatVerdictTallySegment(auditorVerdictTally(g.auditHistory, now), now);
|
|
1027
|
+
const verdictSuffix = tallyText ? ` · ${tallyText}` : "";
|
|
1028
|
+
return `glla: ${host} · ${label}${quietSuffix}${next}${detachedSuffix}${verdictSuffix}${heldSuffix}`;
|
|
958
1029
|
}
|
|
959
1030
|
if (g.status === "paused") {
|
|
960
1031
|
// v0.28.22: the status line names the ACTIONABILITY, not the reason —
|
|
@@ -1357,6 +1428,13 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
1357
1428
|
: 48;
|
|
1358
1429
|
const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), objBudget)} ${paint(theme, "dim", "·")} ${segsText}`;
|
|
1359
1430
|
const lines = [head];
|
|
1431
|
+
// v0.38.8: durable verdict tally as a first-class card row — the widget
|
|
1432
|
+
// is the glance surface, and stored verdicts are the progress evidence
|
|
1433
|
+
// when no auditor is live. Silent when history is empty.
|
|
1434
|
+
const headTally = formatVerdictTallySegment(auditorVerdictTally(g.auditHistory, now), now);
|
|
1435
|
+
if (headTally) {
|
|
1436
|
+
lines.push(`├─ ${paint(theme, "dim", `audits: ${truncate(headTally, Math.max(20, (width ?? 80) - 12))}`)}`);
|
|
1437
|
+
}
|
|
1360
1438
|
if (g.repairTarget) {
|
|
1361
1439
|
lines.push(`├─ ${paint(theme, "warning", `REPLAN REQUIRED · original target: ${truncate(g.repairTarget.objective.replace(/\s+/g, " "), Math.max(30, (width ?? 80) - 28))}`)}`);
|
|
1362
1440
|
const repairStep = g.repairTarget.replanPromptedAt
|
|
@@ -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
|
|
@@ -997,11 +997,16 @@ export function shouldCompactFirstNudge(percent: number | null | undefined): boo
|
|
|
997
997
|
export function buildStarvationLadderMessage(input: { percent?: number | null; streak?: number } = {}): string {
|
|
998
998
|
const at = typeof input.percent === "number" && Number.isFinite(input.percent)
|
|
999
999
|
? `${input.percent.toFixed(1)}%` : "nearly full";
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
`
|
|
1004
|
-
`
|
|
1000
|
+
// v0.38.8: one recovery per line — the single-paragraph version buried
|
|
1001
|
+
// the ladder. Same content, scannable shape.
|
|
1002
|
+
return [
|
|
1003
|
+
`glla: output-token stop was context starvation (tiny output at ${at} context) — yielding to pi auto-compaction instead of re-sending.`,
|
|
1004
|
+
`If compaction fails or context is already over cap, in order:`,
|
|
1005
|
+
`(1) run /compact again — GLLA already trimmed repeat payloads to a checkpoint, so the retry may now fit;`,
|
|
1006
|
+
`(2) switch to a larger-context model, then /compact (GLLA auto-rotates its fallback chain after a failed compact-and-retry);`,
|
|
1007
|
+
`(3) /new, then /goal resume — the goal, tasks, ledger and audits are durable on disk, and the post-compact resync re-anchors the fresh session with no summarization needed.`,
|
|
1008
|
+
`Automatic turns stay parked until a real compaction lands.`,
|
|
1009
|
+
].join("\n");
|
|
1005
1010
|
}
|
|
1006
1011
|
function noteContextStarvedYield(): { streak: number; shouldRefuse: boolean } {
|
|
1007
1012
|
const now = Date.now();
|
|
@@ -58,6 +58,10 @@ export type SettingsSectionId =
|
|
|
58
58
|
| "stall-brakes"
|
|
59
59
|
| "other";
|
|
60
60
|
|
|
61
|
+
/** v0.38.8: tab label with its row count — pure so tests pin the shape. */
|
|
62
|
+
export function settingsTabLabel(label: string, rowCount: number): string {
|
|
63
|
+
return rowCount > 0 ? `${label} (${rowCount})` : label;
|
|
64
|
+
}
|
|
61
65
|
export const SETTINGS_SECTIONS: readonly { id: SettingsSectionId; label: string }[] = [
|
|
62
66
|
{ id: "keep-going", label: "Keep-going" },
|
|
63
67
|
{ id: "main-agent", label: "Main agent" },
|
|
@@ -766,11 +770,15 @@ export class SettingsMenuComponent implements Component {
|
|
|
766
770
|
|
|
767
771
|
// v0.28.19: color-only tabs (user call: "dropping the brackets") —
|
|
768
772
|
// active = accent + bold, inactive = dim. No bracket chrome.
|
|
773
|
+
// v0.38.8: per-tab row counts — the menu holds 7 sections and the
|
|
774
|
+
// count says where the settings live at a glance.
|
|
775
|
+
const tabLabel = (s: { id: SettingsSectionId; label: string }): string =>
|
|
776
|
+
settingsTabLabel(s.label, this.rows.filter((r) => r.section === s.id).length);
|
|
769
777
|
lines.push(
|
|
770
778
|
SETTINGS_SECTIONS.map((s, i) =>
|
|
771
779
|
i === this.activeSectionIdx
|
|
772
|
-
? this.theme.fg("accent", this.theme.bold(s
|
|
773
|
-
: this.theme.fg("dim", s
|
|
780
|
+
? this.theme.fg("accent", this.theme.bold(tabLabel(s)))
|
|
781
|
+
: this.theme.fg("dim", tabLabel(s)),
|
|
774
782
|
).join(" "),
|
|
775
783
|
);
|
|
776
784
|
|
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.8",
|
|
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",
|