pi-goal-list-loop-audit 0.38.12 → 0.38.14
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,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.14 — human end-of-objective briefing (2026-09-04)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
Every `✓ done` chat notify is now a human briefing: outcome first in its own words, only informing labels after it (`none` / `not recorded` / empty filler dropped; `none — <content>` keeps the content). Word-boundary cuts, auditor/no-audit trailer, archive keeps the full six-label record. Runbook in `audit/HUMAN-BRIEF-2026-09-04.md`.
|
|
7
|
+
|
|
8
|
+
## 0.38.13 — good completion summary (2026-09-04)
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
The `✓ done` chat notify is now the six-label block (one `Label: value` line each, 240-char word-bounded values) instead of the single-line mash with mid-word cuts. New `clipSummaryValue` cuts at word boundaries everywhere (hard-cut only for spaceless tokens); widget card and external notifies keep the compact line. Fixed a post-archive null crash that silently swallowed the notify. Runbook in `audit/COMPLETION-SUMMARY-2026-09-04.md`.
|
|
12
|
+
|
|
3
13
|
## 0.38.12 — last-wins sessions + objectives (2026-09-04)
|
|
4
14
|
|
|
5
15
|
### Changed
|
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.14 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
|
|
@@ -77,6 +77,21 @@ export function isUsefulCompletionSummary(text: string | undefined): boolean {
|
|
|
77
77
|
* Missing labels are retained as `not recorded` so a compact notification
|
|
78
78
|
* cannot accidentally imply evidence that the durable recap did not contain.
|
|
79
79
|
*/
|
|
80
|
+
/** Cut a summary value at a word boundary, never mid-word (the
|
|
81
|
+
* Screenshot_20260903_204003/204005 complaint: `0 o…`, `qu…`, `belo…`).
|
|
82
|
+
* Falls back to a hard cut only when the head holds no space past the
|
|
83
|
+
* halfway mark — a long token such as a commit hash must not eviscerate
|
|
84
|
+
* the whole value. Short values pass through untouched (no `…`). */
|
|
85
|
+
export function clipSummaryValue(value: string, limit: number): string {
|
|
86
|
+
const clean = value.replace(/\s+/g, " ").trim();
|
|
87
|
+
const capped = Number.isFinite(limit) ? Math.max(8, Math.floor(limit)) : 72;
|
|
88
|
+
if (clean.length <= capped) return clean;
|
|
89
|
+
const head = clean.slice(0, capped - 1);
|
|
90
|
+
const space = head.lastIndexOf(" ");
|
|
91
|
+
const kept = (space > capped / 2 ? head.slice(0, space) : head).trimEnd();
|
|
92
|
+
return `${kept}…`;
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
export function compactCompletionSummary(text: string | undefined, maxValueLength = 72): string {
|
|
81
96
|
const source = completionSummaryBody(text ?? "").replace(/\s+/g, " ").trim();
|
|
82
97
|
if (!source) return "not recorded";
|
|
@@ -96,11 +111,78 @@ export function compactCompletionSummary(text: string | undefined, maxValueLengt
|
|
|
96
111
|
.sort((a, b) => a - b)[0] ?? source.length;
|
|
97
112
|
const rawValue = source.slice(valueStart, nextStart).trim();
|
|
98
113
|
const value = rawValue || "not recorded";
|
|
99
|
-
return `${name}: ${value
|
|
114
|
+
return `${name}: ${clipSummaryValue(value, limit)}`;
|
|
100
115
|
});
|
|
101
116
|
return parts.join(" · ");
|
|
102
117
|
}
|
|
103
118
|
|
|
119
|
+
/** A recap value informs the human only when it is not an empty / none /
|
|
120
|
+
* not-recorded placeholder. `None — <real content>` (a common agent habit)
|
|
121
|
+
* keeps just the content. Returns null for filler. */
|
|
122
|
+
export function briefValueContent(value: string): string | null {
|
|
123
|
+
const clean = value.replace(/\s+/g, " ").trim();
|
|
124
|
+
if (!clean) return null;
|
|
125
|
+
// System placeholder vocabulary: the `— explanation` is still a placeholder.
|
|
126
|
+
if (/^not recorded\b/i.test(clean)) return null;
|
|
127
|
+
// Agent habit: `none — <real content>` hides information behind filler.
|
|
128
|
+
const prefixed = /^none\s*[—–:\-]\s*(.+)$/i.exec(clean);
|
|
129
|
+
const body = (prefixed?.[1] ?? clean).trim();
|
|
130
|
+
if (/^(none|n\/a|nil|nothing)(\s+for\s+this\s+\w+)?\.?$/i.test(body)) return null;
|
|
131
|
+
return body;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface HumanCompletionBrief {
|
|
135
|
+
outcome: string;
|
|
136
|
+
details: string[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The human briefing: outcome first in its own words, then only the
|
|
140
|
+
* labels that carry real content (filler like `Unresolved: none` or
|
|
141
|
+
* `Changed: not recorded` is dropped, never shown). The durable archive
|
|
142
|
+
* keeps the full six-label record; this is the end-of-objective voice
|
|
143
|
+
* that informs the user at a glance. */
|
|
144
|
+
export function humanCompletionBrief(
|
|
145
|
+
text: string | undefined,
|
|
146
|
+
outcomeBudget = 140,
|
|
147
|
+
valueBudget = 120,
|
|
148
|
+
): HumanCompletionBrief {
|
|
149
|
+
const lines = completionSummaryLines(text, Math.max(outcomeBudget, valueBudget));
|
|
150
|
+
const rawOutcome = (lines[0] ?? "").replace(/^Outcome:\s*/, "");
|
|
151
|
+
const outcome = clipSummaryValue(briefValueContent(rawOutcome) ?? "done", outcomeBudget);
|
|
152
|
+
const details: string[] = [];
|
|
153
|
+
for (const line of lines.slice(1)) {
|
|
154
|
+
const separator = line.indexOf(":");
|
|
155
|
+
if (separator < 0) continue;
|
|
156
|
+
const content = briefValueContent(line.slice(separator + 1));
|
|
157
|
+
if (content) details.push(`${line.slice(0, separator)}: ${clipSummaryValue(content, valueBudget)}`);
|
|
158
|
+
}
|
|
159
|
+
return { outcome, details };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Multi-line projection: one `Label: value` line per label with generous
|
|
163
|
+
* word-bounded values. This is the user-facing `✓ done` block — six short
|
|
164
|
+
* facts that stay scannable in chat. The single-line projection remains
|
|
165
|
+
* for width-bound surfaces (TUI widget card, external notifies). */
|
|
166
|
+
export function completionSummaryLines(text: string | undefined, maxValueLength = 240): string[] {
|
|
167
|
+
const source = completionSummaryBody(text ?? "").replace(/\s+/g, " ").trim();
|
|
168
|
+
const lower = source.toLowerCase();
|
|
169
|
+
const positions = COMPLETION_SUMMARY_LABELS
|
|
170
|
+
.map((label) => ({ label, start: lower.indexOf(label.toLowerCase()) }))
|
|
171
|
+
.filter((entry) => entry.start >= 0);
|
|
172
|
+
return COMPLETION_SUMMARY_LABELS.map((label) => {
|
|
173
|
+
const name = label.slice(0, -1);
|
|
174
|
+
const current = positions.find((entry) => entry.label === label);
|
|
175
|
+
if (!source || !current) return `${name}: not recorded`;
|
|
176
|
+
const valueStart = current.start + label.length;
|
|
177
|
+
const nextStart = positions
|
|
178
|
+
.filter((entry) => entry.start > current.start)
|
|
179
|
+
.map((entry) => entry.start)
|
|
180
|
+
.sort((a, b) => a - b)[0] ?? source.length;
|
|
181
|
+
const rawValue = source.slice(valueStart, nextStart).trim();
|
|
182
|
+
return `${name}: ${clipSummaryValue(rawValue || "not recorded", maxValueLength)}`;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
104
186
|
function safeFact(value: unknown, fallback = "not recorded"): string {
|
|
105
187
|
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
106
188
|
return text || fallback;
|
|
@@ -197,6 +279,25 @@ export function compactTerminalCompletionSummary(
|
|
|
197
279
|
return compactCompletionSummary(resolveCompletionSummary(facts, candidate).summary, maxValueLength);
|
|
198
280
|
}
|
|
199
281
|
|
|
282
|
+
/** Brief twin of compactTerminalCompletionSummary for the `✓ done` chat
|
|
283
|
+
* notifies: same resolved facts, outcome + informing labels only. */
|
|
284
|
+
export function terminalHumanBrief(
|
|
285
|
+
facts: CompletionSummaryFacts,
|
|
286
|
+
candidate = facts.goal.completionSummary,
|
|
287
|
+
): HumanCompletionBrief {
|
|
288
|
+
return humanCompletionBrief(resolveCompletionSummary(facts, candidate).summary);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Multi-line twin of compactTerminalCompletionSummary for the `✓ done`
|
|
292
|
+
* chat notifies: same resolved facts, one `Label: value` line each. */
|
|
293
|
+
export function terminalCompletionSummaryLines(
|
|
294
|
+
facts: CompletionSummaryFacts,
|
|
295
|
+
candidate = facts.goal.completionSummary,
|
|
296
|
+
maxValueLength = 240,
|
|
297
|
+
): string[] {
|
|
298
|
+
return completionSummaryLines(resolveCompletionSummary(facts, candidate).summary, maxValueLength);
|
|
299
|
+
}
|
|
300
|
+
|
|
200
301
|
/**
|
|
201
302
|
* Loop terminal states use the same six-label user-facing contract. Loop
|
|
202
303
|
* state is converted to the smaller Goal-shaped fact set by the caller, so
|
|
@@ -240,7 +240,7 @@ import {
|
|
|
240
240
|
pushCapped as pushRepetitionCapped,
|
|
241
241
|
} from "../goal-loop-repetition.js";
|
|
242
242
|
import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
|
|
243
|
-
import { compactCompletionSummary, compactTerminalCompletionSummary, isGenericCompletionSummary, missingCompletionSummaryLabels } from "../completion-summary.js";
|
|
243
|
+
import { compactCompletionSummary, compactTerminalCompletionSummary, isGenericCompletionSummary, missingCompletionSummaryLabels, terminalHumanBrief } from "../completion-summary.js";
|
|
244
244
|
import {
|
|
245
245
|
defaultAgentDir,
|
|
246
246
|
resolveEffectiveSubagentModel,
|
|
@@ -1415,6 +1415,13 @@ async function retryStoredCompletionAudit(origin: CompletionAuditOrigin = "provi
|
|
|
1415
1415
|
stopReason: terminalReason,
|
|
1416
1416
|
archivePath: path.relative(liveCtx.cwd, archivedGoalPath(liveCtx.cwd, state.goal.id)) || archivedGoalPath(liveCtx.cwd, state.goal.id),
|
|
1417
1417
|
}, state.goal.completionSummary);
|
|
1418
|
+
// Computed pre-archive: archiveCurrentGoal clears state.goal.
|
|
1419
|
+
const brief = terminalHumanBrief({
|
|
1420
|
+
goal: state.goal,
|
|
1421
|
+
status: "complete",
|
|
1422
|
+
stopReason: terminalReason,
|
|
1423
|
+
archivePath: path.relative(liveCtx.cwd, archivedGoalPath(liveCtx.cwd, state.goal.id)) || archivedGoalPath(liveCtx.cwd, state.goal.id),
|
|
1424
|
+
}, state.goal.completionSummary);
|
|
1418
1425
|
const approvalVia = `${origin === "manual" ? " on /goal verify" : origin === "session-recovery" ? " after session recovery" : " on the provider retry"}${fallbackUsed ? " after an auditor-model fallback" : ""}`;
|
|
1419
1426
|
const archived = archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`);
|
|
1420
1427
|
if (!archived) {
|
|
@@ -1431,7 +1438,10 @@ async function retryStoredCompletionAudit(origin: CompletionAuditOrigin = "provi
|
|
|
1431
1438
|
appendLedger(liveCtx.cwd, "goal_archive_failed_after_approval", { goalId, attemptId: claim.attemptId, origin });
|
|
1432
1439
|
return;
|
|
1433
1440
|
}
|
|
1434
|
-
|
|
1441
|
+
// v0.38.14: the chat notify is the human briefing — outcome first,
|
|
1442
|
+
// filler labels dropped — while the external notify keeps the compact
|
|
1443
|
+
// single line (pager/sound safe).
|
|
1444
|
+
liveCtx.ui.notify(`✓ done — ${brief.outcome}\n${[...brief.details, `— auditor ${result.model} approved${approvalVia}.`].join("\n")}`, "info");
|
|
1435
1445
|
notifyExternal(liveCtx, `Goal complete (auditor approved, ${origin}): ${recap}`);
|
|
1436
1446
|
return;
|
|
1437
1447
|
}
|
|
@@ -250,7 +250,7 @@ import {
|
|
|
250
250
|
pushCapped as pushRepetitionCapped,
|
|
251
251
|
} from "../goal-loop-repetition.js";
|
|
252
252
|
import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
|
|
253
|
-
import { compactCompletionSummary, compactTerminalCompletionSummary, resolveCompletionSummary } from "../completion-summary.js";
|
|
253
|
+
import { compactCompletionSummary, compactTerminalCompletionSummary, resolveCompletionSummary, terminalHumanBrief } from "../completion-summary.js";
|
|
254
254
|
import {
|
|
255
255
|
defaultAgentDir,
|
|
256
256
|
resolveEffectiveSubagentModel,
|
|
@@ -1216,9 +1216,16 @@ function registerAgentTools(pi: any): void {
|
|
|
1216
1216
|
details: {},
|
|
1217
1217
|
};
|
|
1218
1218
|
}
|
|
1219
|
-
|
|
1219
|
+
const brief = terminalHumanBrief({
|
|
1220
|
+
goal: terminalGoal,
|
|
1221
|
+
status: "complete",
|
|
1222
|
+
stopReason: terminalReason,
|
|
1223
|
+
archivePath: path.relative(ctx.cwd, archivedGoalPath(ctx.cwd, terminalGoal.id)) || archivedGoalPath(ctx.cwd, terminalGoal.id),
|
|
1224
|
+
});
|
|
1225
|
+
const briefBlock = [...brief.details, `— completed without audit (your choice).`].join("\n");
|
|
1226
|
+
ctx.ui.notify(`✓ done — ${brief.outcome}\n${briefBlock}`, "info");
|
|
1220
1227
|
notifyExternal(ctx, `Goal complete without audit (user choice): ${recap}`);
|
|
1221
|
-
return { content: [{ type: "text", text: `Goal marked complete without audit (user choice).\n\n${
|
|
1228
|
+
return { content: [{ type: "text", text: `Goal marked complete without audit (user choice).\n\n${briefBlock}` }], details: {} };
|
|
1222
1229
|
}
|
|
1223
1230
|
scheduleContinuation(ctx, true);
|
|
1224
1231
|
return {
|
|
@@ -1243,6 +1250,13 @@ function registerAgentTools(pi: any): void {
|
|
|
1243
1250
|
stopReason: terminalReason,
|
|
1244
1251
|
archivePath: path.relative(ctx.cwd, archivedGoalPath(ctx.cwd, state.goal.id)) || archivedGoalPath(ctx.cwd, state.goal.id),
|
|
1245
1252
|
}, state.goal.completionSummary);
|
|
1253
|
+
// Computed pre-archive: archiveCurrentGoal clears state.goal.
|
|
1254
|
+
const brief = terminalHumanBrief({
|
|
1255
|
+
goal: state.goal,
|
|
1256
|
+
status: "complete",
|
|
1257
|
+
stopReason: terminalReason,
|
|
1258
|
+
archivePath: path.relative(ctx.cwd, archivedGoalPath(ctx.cwd, state.goal.id)) || archivedGoalPath(ctx.cwd, state.goal.id),
|
|
1259
|
+
}, state.goal.completionSummary);
|
|
1246
1260
|
const archived = archiveCurrentGoal(ctx, "complete", terminalReason);
|
|
1247
1261
|
if (!archived) {
|
|
1248
1262
|
// The archive helper preserves the live objective and emits the
|
|
@@ -1258,7 +1272,7 @@ function registerAgentTools(pi: any): void {
|
|
|
1258
1272
|
appendLedger(ctx.cwd, "goal_archive_failed_after_approval", { goalId: state.goal?.id, origin: "manual-verify", model: result.model });
|
|
1259
1273
|
return { content: [{ type: "text", text: "The auditor approved, but the terminal archive could not be persisted. The goal is paused; fix persistence, resume, and retry complete_goal." }], details: {} };
|
|
1260
1274
|
}
|
|
1261
|
-
ctx.ui.notify(`✓ done
|
|
1275
|
+
ctx.ui.notify(`✓ done — ${brief.outcome}\n${[...brief.details, `— auditor ${result.model} approved.`].join("\n")}`, "info");
|
|
1262
1276
|
notifyExternal(ctx, `Goal complete (auditor approved): ${recap}`);
|
|
1263
1277
|
return { content: [{ type: "text", text: `Goal approved by auditor ${result.model}.` }], details: {} };
|
|
1264
1278
|
}
|
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.14",
|
|
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",
|