pi-goal-list-loop-audit 0.38.13 → 0.38.15

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.15 — pause anti-confabulation (2026-09-04)
4
+
5
+ ### Changed
6
+ `pause_goal` refuses pauses whose blocker claims a GLLA tool is missing (dispatch proves the batch landed) and tells the model to call it; quoting pi's `Tool X not found` is accepted as genuine outage. Runbook + new-tab forensics in `audit/PAUSE-ANTI-CONFABULATION-2026-09-04.md`.
7
+
8
+ ## 0.38.14 — human end-of-objective briefing (2026-09-04)
9
+
10
+ ### Changed
11
+ 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`.
12
+
3
13
  ## 0.38.13 — good completion summary (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.13 continue through the supervisor freeze (`/glla pause`),
21
+ v0.35.14–v0.38.15 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
@@ -116,6 +116,49 @@ export function compactCompletionSummary(text: string | undefined, maxValueLengt
116
116
  return parts.join(" · ");
117
117
  }
118
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
+
119
162
  /** Multi-line projection: one `Label: value` line per label with generous
120
163
  * word-bounded values. This is the user-facing `✓ done` block — six short
121
164
  * facts that stay scannable in chat. The single-line projection remains
@@ -236,6 +279,15 @@ export function compactTerminalCompletionSummary(
236
279
  return compactCompletionSummary(resolveCompletionSummary(facts, candidate).summary, maxValueLength);
237
280
  }
238
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
+
239
291
  /** Multi-line twin of compactTerminalCompletionSummary for the `✓ done`
240
292
  * chat notifies: same resolved facts, one `Label: value` line each. */
241
293
  export function terminalCompletionSummaryLines(
@@ -3056,6 +3056,28 @@ export function missingGllaTools(activeNames: readonly string[]): readonly GllaT
3056
3056
  return GLLA_TOOL_NAMES.filter((n) => !active.has(n));
3057
3057
  }
3058
3058
 
3059
+ /** pi's own wording when a model calls a tool pi cannot dispatch. A pause
3060
+ * reason quoting this is evidence of a genuine tool outage — the pause
3061
+ * must be accepted, never refused as confabulation. */
3062
+ export const PI_TOOL_NOT_FOUND_QUOTE = /tool\s+\S+\s+not found/i;
3063
+
3064
+ /** A pause reason that claims a GLLA tool does not exist in this session
3065
+ * (`no complete_goal tool`, `complete_goal is missing`, …). Ordinary
3066
+ * mentions of a tool (results, instructions, summaries) never match —
3067
+ * absence language is required. */
3068
+ export function claimedMissingGllaTool(reason: string): GllaToolName | null {
3069
+ const text = (reason ?? "").replace(/\s+/g, " ");
3070
+ if (!text) return null;
3071
+ for (const name of GLLA_TOOL_NAMES) {
3072
+ const absence = new RegExp(
3073
+ `\\bno\\s+${name}\\s+tool\\b|\\b${name}\\b[^.]{0,120}?\\b(missing|not found|not available|unavailable|doesn'?t exist|isn'?t available|is not available|no longer (?:available|exists|present))\\b`,
3074
+ "i",
3075
+ );
3076
+ if (absence.test(text)) return name;
3077
+ }
3078
+ return null;
3079
+ }
3080
+
3059
3081
  // -----------------------------------------------------------------
3060
3082
  // v0.25.0 — eager-continuation contract helpers
3061
3083
  // -----------------------------------------------------------------
@@ -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, terminalCompletionSummaryLines } from "../completion-summary.js";
243
+ import { compactCompletionSummary, compactTerminalCompletionSummary, isGenericCompletionSummary, missingCompletionSummaryLabels, terminalHumanBrief } from "../completion-summary.js";
244
244
  import {
245
245
  defaultAgentDir,
246
246
  resolveEffectiveSubagentModel,
@@ -1416,7 +1416,7 @@ async function retryStoredCompletionAudit(origin: CompletionAuditOrigin = "provi
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
1418
  // Computed pre-archive: archiveCurrentGoal clears state.goal.
1419
- const recapLines = terminalCompletionSummaryLines({
1419
+ const brief = terminalHumanBrief({
1420
1420
  goal: state.goal,
1421
1421
  status: "complete",
1422
1422
  stopReason: terminalReason,
@@ -1438,10 +1438,10 @@ async function retryStoredCompletionAudit(origin: CompletionAuditOrigin = "provi
1438
1438
  appendLedger(liveCtx.cwd, "goal_archive_failed_after_approval", { goalId, attemptId: claim.attemptId, origin });
1439
1439
  return;
1440
1440
  }
1441
- // v0.38.13: the chat notify carries the six-label block (one fact per
1442
- // line, word-bounded) instead of the single-line mash — the external
1443
- // notify keeps the compact single line (pager/sound safe).
1444
- liveCtx.ui.notify(`✓ done — auditor ${result.model} approved${approvalVia}.\n${recapLines.join("\n")}`, "info");
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");
1445
1445
  notifyExternal(liveCtx, `Goal complete (auditor approved, ${origin}): ${recap}`);
1446
1446
  return;
1447
1447
  }
@@ -114,6 +114,8 @@ import {
114
114
  readQueueFromDisk,
115
115
  deleteQueueItemFileResult,
116
116
  missingGllaTools,
117
+ claimedMissingGllaTool,
118
+ PI_TOOL_NOT_FOUND_QUOTE,
117
119
  runPersistStep,
118
120
  isPersistenceDegraded,
119
121
  lastPersistenceFailure,
@@ -250,7 +252,7 @@ import {
250
252
  pushCapped as pushRepetitionCapped,
251
253
  } from "../goal-loop-repetition.js";
252
254
  import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
253
- import { compactCompletionSummary, compactTerminalCompletionSummary, resolveCompletionSummary, terminalCompletionSummaryLines } from "../completion-summary.js";
255
+ import { compactCompletionSummary, compactTerminalCompletionSummary, resolveCompletionSummary, terminalHumanBrief } from "../completion-summary.js";
254
256
  import {
255
257
  defaultAgentDir,
256
258
  resolveEffectiveSubagentModel,
@@ -1216,15 +1218,16 @@ function registerAgentTools(pi: any): void {
1216
1218
  details: {},
1217
1219
  };
1218
1220
  }
1219
- const recapLines = terminalCompletionSummaryLines({
1221
+ const brief = terminalHumanBrief({
1220
1222
  goal: terminalGoal,
1221
1223
  status: "complete",
1222
1224
  stopReason: terminalReason,
1223
1225
  archivePath: path.relative(ctx.cwd, archivedGoalPath(ctx.cwd, terminalGoal.id)) || archivedGoalPath(ctx.cwd, terminalGoal.id),
1224
1226
  });
1225
- ctx.ui.notify(`✓ done without audit (user choice):\n${recapLines.join("\n")}`, "info");
1227
+ const briefBlock = [...brief.details, `— completed without audit (your choice).`].join("\n");
1228
+ ctx.ui.notify(`✓ done — ${brief.outcome}\n${briefBlock}`, "info");
1226
1229
  notifyExternal(ctx, `Goal complete without audit (user choice): ${recap}`);
1227
- return { content: [{ type: "text", text: `Goal marked complete without audit (user choice).\n\n${recapLines.join("\n")}` }], details: {} };
1230
+ return { content: [{ type: "text", text: `Goal marked complete without audit (user choice).\n\n${briefBlock}` }], details: {} };
1228
1231
  }
1229
1232
  scheduleContinuation(ctx, true);
1230
1233
  return {
@@ -1250,7 +1253,7 @@ function registerAgentTools(pi: any): void {
1250
1253
  archivePath: path.relative(ctx.cwd, archivedGoalPath(ctx.cwd, state.goal.id)) || archivedGoalPath(ctx.cwd, state.goal.id),
1251
1254
  }, state.goal.completionSummary);
1252
1255
  // Computed pre-archive: archiveCurrentGoal clears state.goal.
1253
- const recapLines = terminalCompletionSummaryLines({
1256
+ const brief = terminalHumanBrief({
1254
1257
  goal: state.goal,
1255
1258
  status: "complete",
1256
1259
  stopReason: terminalReason,
@@ -1271,7 +1274,7 @@ function registerAgentTools(pi: any): void {
1271
1274
  appendLedger(ctx.cwd, "goal_archive_failed_after_approval", { goalId: state.goal?.id, origin: "manual-verify", model: result.model });
1272
1275
  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: {} };
1273
1276
  }
1274
- ctx.ui.notify(`✓ done — auditor ${result.model} approved.\n${recapLines.join("\n")}`, "info");
1277
+ ctx.ui.notify(`✓ done — ${brief.outcome}\n${[...brief.details, `— auditor ${result.model} approved.`].join("\n")}`, "info");
1275
1278
  notifyExternal(ctx, `Goal complete (auditor approved): ${recap}`);
1276
1279
  return { content: [{ type: "text", text: `Goal approved by auditor ${result.model}.` }], details: {} };
1277
1280
  }
@@ -1926,6 +1929,20 @@ function registerAgentTools(pi: any): void {
1926
1929
  if (state.goal.status !== "active") {
1927
1930
  return { content: [{ type: "text", text: `Goal is already ${state.goal.status}; pause request ignored.` }], details: {} };
1928
1931
  }
1932
+ // v0.38.15: anti-confabulation — a pause whose blocker is "this
1933
+ // session has no <glla tool>" is refused when the tool path provably
1934
+ // works: this very pause_goal call dispatched through the same
1935
+ // registration batch (complete_goal registers first in that batch),
1936
+ // so the tool IS callable — the model is misreading its tool list
1937
+ // (field: new-tab 2026-09-04, 5×-compacted session, zero tool errors
1938
+ // in transcript or ledger). The model is told to call it now; a
1939
+ // quoted pi `Tool X not found` error is genuine-outage evidence and
1940
+ // the pause is accepted.
1941
+ const missingClaim = claimedMissingGllaTool(p.reason ?? "");
1942
+ if (missingClaim && !PI_TOOL_NOT_FOUND_QUOTE.test(p.reason ?? "")) {
1943
+ appendLedger(ctx.cwd, "pause_refused_tool_present", { goalId: state.goal.id, tool: missingClaim });
1944
+ return { content: [{ type: "text", text: `Not paused: \`${missingClaim}\` is registered in this session — this pause_goal call just dispatched through the same registration batch, so the tool path works. If \`${missingClaim}\` is missing from your visible tool list, that is a client-side gap: call \`${missingClaim}\` now${missingClaim === "complete_goal" ? " with your six-label recap and verification summary" : ""}. If pi itself answers with a \`Tool ${missingClaim} not found\` error, call pause_goal again quoting that exact error and the pause will be accepted.` }], details: {} };
1945
+ }
1929
1946
  const pauseCopy = providerErrorPresentation(p.reason, "recovery");
1930
1947
  const safePauseReason = pauseCopy.sensitive ? pauseCopy.display : p.reason;
1931
1948
  const safePauseAction = p.suggestedAction ? sanitizeProviderDisplayText(p.suggestedAction) : p.suggestedAction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.13",
3
+ "version": "0.38.15",
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",