pi-goal-list-loop-audit 0.38.38 → 0.38.40
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 +23 -0
- package/docs/INDEX.md +1 -1
- package/extensions/completion-summary.ts +70 -12
- package/extensions/length-continue.ts +42 -10
- package/package.json +1 -1
- package/prompts/goal-loop-continuation.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.40 — provider overflow errors count as starvation (2026-09-09)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **PR #48 (Bjynt, merged):** `isContextStarvedLengthStop` now detects explicit provider context-overflow errors (`exceed_context_size_error`, `exceeds the available context size`, `context window exceeded`, …) arriving as `stopReason="error"`, firing the starvation ladder + emergency compactor (≥90% usage gate kept). Case 1 refactor is behavior-identical. Wiring confirmed live via the raw session message's `errorMessage`.
|
|
8
|
+
- Follow-up noted on the PR: OpenAI-style `maximum context length` wording still falls through the matcher set.
|
|
9
|
+
|
|
10
|
+
### Verification
|
|
11
|
+
|
|
12
|
+
- Contributor's Case 2 tests + full `release:check` 0 failures; `tsc --noEmit` clean.
|
|
13
|
+
|
|
14
|
+
## 0.38.39 — Codex-like terminal summary (2026-09-09)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Terminal `[goal-event]` summary was not Codex-like (field 20260909_013733):** `•` detail bullets followed by dangling `—` trailer lines, every bullet trailing into `…`, a raw `/var/tmp/…` log path in chat, and no next action. The render is now one voice: uniform `•` bullets (approval/counts/record ride as bullets, record stays last), machine paths (`/tmp/…`, `/var/tmp/…`, `*.tgz`, receipt-only paren groups) stripped from chat bullets (archive keeps the full text), `+` joins the clause-boundary cut set, and `withoutStaleNext` is selective — audit-self-referential Next lines still drop but the first concrete next action survives as the closing bullet ahead of the non-do.
|
|
19
|
+
- **Claim-side guidance:** continuation prompt budgets each label value to ~90 chars, bans machine paths from the recap, and asks for one concrete `Next:` or none.
|
|
20
|
+
|
|
21
|
+
### Verification
|
|
22
|
+
|
|
23
|
+
- New pins: uniform-voice, machine-path strip, `+`-boundary cut, next-action ordering, record-last; full `release:check` 0 failures; `tsc --noEmit` clean.
|
|
24
|
+
- Evidence: `audit/CODEX-SUMMARY-2026-09-09.md`. Archive format and delivery/replay mechanics unchanged.
|
|
25
|
+
|
|
3
26
|
## 0.38.38 — elapsed-time duplication cut + PR #47 closed (2026-09-09)
|
|
4
27
|
|
|
5
28
|
### Fixed
|
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.40 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
|
|
@@ -86,7 +86,7 @@ export function isUsefulCompletionSummary(text: string | undefined): boolean {
|
|
|
86
86
|
* back to the word break, then to a hard cut only when the head holds
|
|
87
87
|
* no space past the halfway mark — a long token such as a commit hash
|
|
88
88
|
* must not eviscerate the whole value. Trailing punctuation is stripped
|
|
89
|
-
*
|
|
89
|
+
* `npm version…+ latest` cut inside a `+`-joined list). */
|
|
90
90
|
export function clipSummaryValue(value: string, limit: number): string {
|
|
91
91
|
const clean = value.replace(/\s+/g, " ").trim();
|
|
92
92
|
const capped = Number.isFinite(limit) ? Math.max(8, Math.floor(limit)) : 72;
|
|
@@ -95,16 +95,16 @@ export function clipSummaryValue(value: string, limit: number): string {
|
|
|
95
95
|
const floor = Math.max(16, Math.floor((capped - 1) * 0.4));
|
|
96
96
|
let boundary = -1;
|
|
97
97
|
for (let i = 0; i < head.length; i++) {
|
|
98
|
-
if (/[,;:·—–(\[]/.test(head[i]!)) boundary = i;
|
|
98
|
+
if (/[,;:·—–(+[\[]/.test(head[i]!)) boundary = i;
|
|
99
99
|
}
|
|
100
100
|
if (boundary >= floor) {
|
|
101
|
-
const cut = head.slice(0, boundary).replace(/[,;:·—–(\[\s]+$/u, "");
|
|
101
|
+
const cut = head.slice(0, boundary).replace(/[,;:·—–(+[\[\s]+$/u, "");
|
|
102
102
|
if (cut.length >= Math.min(floor, 16)) return `${cut}…`;
|
|
103
103
|
}
|
|
104
104
|
const space = head.lastIndexOf(" ");
|
|
105
105
|
const kept = (space > capped / 2 ? head.slice(0, space) : head)
|
|
106
106
|
.trimEnd()
|
|
107
|
-
.replace(/[,;:·—–(
|
|
107
|
+
.replace(/[,;:·—–(+\[]$/u, "");
|
|
108
108
|
return `${kept}…`;
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -152,6 +152,34 @@ export interface HumanCompletionBrief {
|
|
|
152
152
|
details: string[];
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/** v0.38.39 (field 20260909_013733): absolute machine paths read as a
|
|
156
|
+
* machine receipt in user chat (`Tests: … (/var/tmp/glla-….log…`). The
|
|
157
|
+
* chat brief strips `/tmp/…`, `/var/tmp/…`, and `*.tgz` tokens so bullets
|
|
158
|
+
* carry human-readable proof (counts, versions, repo-relative paths);
|
|
159
|
+
* the durable archive keeps the full text. Falls back to the original
|
|
160
|
+
* when stripping would empty the value. */
|
|
161
|
+
export function chatSafeDetailValue(value: string): string {
|
|
162
|
+
const withoutGroups = value.replace(/\([^()]*\)/g, (group) => {
|
|
163
|
+
// A parenthesized group that carries nothing but stripped tokens and
|
|
164
|
+
// receipt words is machine packaging — drop the whole group so no
|
|
165
|
+
// `( tarball )` husk survives. Groups with real words stay verbatim.
|
|
166
|
+
const inner = group
|
|
167
|
+
.slice(1, -1)
|
|
168
|
+
.replace(/(?:\/var)?\/tmp\/\S+/g, "")
|
|
169
|
+
.replace(/\S+\.tgz\b/g, "")
|
|
170
|
+
.replace(/\btarballs?\b|\blogs?\b/gi, "");
|
|
171
|
+
return /[a-z]/i.test(inner) ? group : "";
|
|
172
|
+
});
|
|
173
|
+
const stripped = withoutGroups
|
|
174
|
+
.replace(/(?:\/var)?\/tmp\/\S+/g, "")
|
|
175
|
+
.replace(/\btarballs?\s+\S+\.tgz\b/gi, "")
|
|
176
|
+
.replace(/\S+\.tgz\b/g, "")
|
|
177
|
+
.replace(/\(\s*\)/g, "")
|
|
178
|
+
.replace(/\s{2,}/g, " ")
|
|
179
|
+
.trim();
|
|
180
|
+
return stripped || value;
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
/** The human briefing: outcome first in its own words, then only the
|
|
156
184
|
* labels that carry real content (filler like `Unresolved: none` or
|
|
157
185
|
* `Changed: not recorded` is dropped, never shown). The durable archive
|
|
@@ -170,7 +198,9 @@ export function humanCompletionBrief(
|
|
|
170
198
|
const separator = line.indexOf(":");
|
|
171
199
|
if (separator < 0) continue;
|
|
172
200
|
const content = briefValueContent(line.slice(separator + 1));
|
|
173
|
-
|
|
201
|
+
// v0.38.39: machine paths are archive evidence, not chat evidence —
|
|
202
|
+
// strip before clipping so the budget applies to the human text.
|
|
203
|
+
if (content) details.push(`${line.slice(0, separator)}: ${clipSummaryValue(chatSafeDetailValue(content), valueBudget)}`);
|
|
174
204
|
}
|
|
175
205
|
return { outcome, details };
|
|
176
206
|
}
|
|
@@ -178,10 +208,27 @@ export function humanCompletionBrief(
|
|
|
178
208
|
/** v0.38.20: the agent's `Next:` recap line goes stale the moment the
|
|
179
209
|
* verdict lands — reprinting it next to the approval trailer reads as
|
|
180
210
|
* complete-before-verify (field 2026-09-04: `Next: detached auditor
|
|
181
|
-
* verdict decides.` printed above `— auditor … approved.`).
|
|
182
|
-
*
|
|
211
|
+
* verdict decides.` printed above `— auditor … approved.`).
|
|
212
|
+
* v0.38.39 (field 20260909_013733): the Codex close wants at most ONE
|
|
213
|
+
* concrete next action, so the filter is now selective instead of total:
|
|
214
|
+
* self-referential audit/process lines (`verdict decides`, `awaiting
|
|
215
|
+
* approval`, …) still drop, but the first concrete action survives as
|
|
216
|
+
* the closing bullet. The full six-label record stays in the archive. */
|
|
217
|
+
const STALE_NEXT_PATTERN = /auditor|verdict|approv|audit|settl|review/i;
|
|
183
218
|
export function withoutStaleNext(details: string[] | undefined): string[] {
|
|
184
|
-
|
|
219
|
+
const kept: string[] = [];
|
|
220
|
+
let actionKept = false;
|
|
221
|
+
for (const detail of details ?? []) {
|
|
222
|
+
if (!/^\s*Next\s*:/i.test(detail)) {
|
|
223
|
+
kept.push(detail);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (STALE_NEXT_PATTERN.test(detail)) continue;
|
|
227
|
+
if (actionKept) continue;
|
|
228
|
+
actionKept = true;
|
|
229
|
+
kept.push(detail);
|
|
230
|
+
}
|
|
231
|
+
return kept;
|
|
185
232
|
}
|
|
186
233
|
|
|
187
234
|
/** v0.38.20: the approval chat notify — outcome first, all bounded
|
|
@@ -202,13 +249,24 @@ export function buildApprovalChatLines(notice: {
|
|
|
202
249
|
`✓ done — ${notice.outcome}`,
|
|
203
250
|
// v0.38.37 (audit 2026-09-08): one verifiable-result bullet per
|
|
204
251
|
// informing detail — the Codex closing shape.
|
|
252
|
+
// v0.38.39: the approval/counts/record trailer rides as bullets too
|
|
253
|
+
// (field 20260909_013733 — the `—` tail read as a second voice); the
|
|
254
|
+
// record pointer stays last.
|
|
205
255
|
...withoutStaleNext(notice.details).map((detail) => `• ${detail}`),
|
|
206
|
-
notice.approval,
|
|
207
|
-
...(notice.counts ? [notice.counts] : []),
|
|
208
|
-
notice.record,
|
|
256
|
+
trailerBullet(notice.approval),
|
|
257
|
+
...(notice.counts ? [trailerBullet(notice.counts)] : []),
|
|
258
|
+
trailerBullet(notice.record),
|
|
209
259
|
];
|
|
210
260
|
}
|
|
211
261
|
|
|
262
|
+
/** v0.38.39 (field 20260909_013733): the trailer rides as bullets too —
|
|
263
|
+
* the Codex close is uniform `•` lines (`• Changed:` … `• record:`),
|
|
264
|
+
* never a `•` block followed by dangling `—` lines. The `— ` sigil is
|
|
265
|
+
* stripped at render; the input strings keep it for non-chat surfaces. */
|
|
266
|
+
function trailerBullet(line: string): string {
|
|
267
|
+
return `• ${line.replace(/^—\s*/, "")}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
212
270
|
/** v0.38.25: the audit-goal counts line — verdict proof ONLY, built from
|
|
213
271
|
* durable goal state. Raw run stats (turns/file-writes/bash calls) read as
|
|
214
272
|
* a machine receipt in user chat (field 2026-09-08 223522/223523); the
|
|
@@ -297,7 +355,7 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
|
|
|
297
355
|
...(input.extras ?? []),
|
|
298
356
|
],
|
|
299
357
|
recap,
|
|
300
|
-
transcriptLines: [...withoutStaleNext(brief.details).map((detail) => `• ${detail}`), input.approval],
|
|
358
|
+
transcriptLines: [...withoutStaleNext(brief.details).map((detail) => `• ${detail}`), trailerBullet(input.approval)],
|
|
301
359
|
countsLine,
|
|
302
360
|
outcome: brief.outcome,
|
|
303
361
|
approval: input.approval,
|
|
@@ -46,6 +46,10 @@ export interface AssistantLengthMessageLike {
|
|
|
46
46
|
usage?: {
|
|
47
47
|
output?: number;
|
|
48
48
|
};
|
|
49
|
+
/** Provider error message, when stopReason is "error". */
|
|
50
|
+
errorMessage?: string;
|
|
51
|
+
/** Error type from provider (e.g., "exceed_context_size_error"). */
|
|
52
|
+
errorType?: string;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export const LENGTH_CONTINUE_CONTEXT_STARVED_PERCENT = 90;
|
|
@@ -60,21 +64,49 @@ export const LENGTH_CONTINUE_CONTEXT_STARVED_MAX_OUTPUT = 8;
|
|
|
60
64
|
* here queues another 1-token request before pi's post-agent_end compaction
|
|
61
65
|
* check and delays the actual cure (field: darklord 2026-08-02, 198,116 /
|
|
62
66
|
* 198,179 total tokens of a 200,000 window, output=1 twice).
|
|
67
|
+
*
|
|
68
|
+
* v0.38.36: also detect explicit context-overflow provider errors
|
|
69
|
+
* (e.g., "exceed_context_size_error", "exceeds the available context size")
|
|
70
|
+
* which arrive as stopReason="error" with an errorMessage, not as
|
|
71
|
+
* stopReason="length". These are also context starvation — the request
|
|
72
|
+
* exceeded the model's context window before generation could start.
|
|
63
73
|
*/
|
|
64
74
|
export function isContextStarvedLengthStop(
|
|
65
75
|
message: AssistantLengthMessageLike | null | undefined,
|
|
66
76
|
contextUsage: ContextUsageLike | null | undefined,
|
|
67
77
|
): boolean {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
// Case 1: pi's context-safety clamp (stopReason="length" with tiny output)
|
|
79
|
+
if (message?.stopReason === "length") {
|
|
80
|
+
const output = message.usage?.output;
|
|
81
|
+
if (typeof output === "number" && Number.isFinite(output) && output <= LENGTH_CONTINUE_CONTEXT_STARVED_MAX_OUTPUT) {
|
|
82
|
+
const percent = typeof contextUsage?.percent === "number"
|
|
83
|
+
? contextUsage.percent
|
|
84
|
+
: typeof contextUsage?.tokens === "number" && typeof contextUsage?.contextWindow === "number" && contextUsage.contextWindow > 0
|
|
85
|
+
? (contextUsage.tokens / contextUsage.contextWindow) * 100
|
|
86
|
+
: null;
|
|
87
|
+
return percent !== null && Number.isFinite(percent) && percent >= LENGTH_CONTINUE_CONTEXT_STARVED_PERCENT;
|
|
88
|
+
}
|
|
89
|
+
return false; // Real overlong response, not context starvation
|
|
90
|
+
}
|
|
91
|
+
// Case 2: explicit provider context-overflow error (stopReason="error")
|
|
92
|
+
if (message?.stopReason === "error") {
|
|
93
|
+
const errMsg = (message.errorMessage ?? message.errorType ?? "").toLowerCase();
|
|
94
|
+
if (
|
|
95
|
+
errMsg.includes("exceed_context_size") ||
|
|
96
|
+
errMsg.includes("exceeds the available context") ||
|
|
97
|
+
errMsg.includes("exceeds the context window") ||
|
|
98
|
+
(errMsg.includes("exceeds the model") && errMsg.includes("context")) ||
|
|
99
|
+
(errMsg.includes("context window") && errMsg.includes("exceed"))
|
|
100
|
+
) {
|
|
101
|
+
const percent = typeof contextUsage?.percent === "number"
|
|
102
|
+
? contextUsage.percent
|
|
103
|
+
: typeof contextUsage?.tokens === "number" && typeof contextUsage?.contextWindow === "number" && contextUsage.contextWindow > 0
|
|
104
|
+
? (contextUsage.tokens / contextUsage.contextWindow) * 100
|
|
105
|
+
: null;
|
|
106
|
+
return percent !== null && Number.isFinite(percent) && percent >= LENGTH_CONTINUE_CONTEXT_STARVED_PERCENT;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
78
110
|
}
|
|
79
111
|
|
|
80
112
|
export function makeLengthContinueTracker(max: number = LENGTH_CONTINUE_MAX) {
|
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.40",
|
|
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",
|
|
@@ -197,4 +197,4 @@ The orchestrator's backstop is the stall watchdog: three consecutive turns with
|
|
|
197
197
|
|
|
198
198
|
`complete_goal` submits a nonterminal claim, not a completion verdict. While the detached audit is pending, do not say the goal is done/complete/approved or present a final success summary. Do not wait or poll. If a response is needed, give one brief pending-status sentence. GLLA posts the concrete outcome-first summary into chat after verified approval and durable archive, without needing another user prompt. Do not duplicate that summary. A rejected claim remains unfinished and repair work continues.
|
|
199
199
|
|
|
200
|
-
The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Cut values at clause boundaries, never mid-word; pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent.
|
|
200
|
+
The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Keep each label value to one line of ~90 chars so every chat bullet fits without a trailing …; cite human-readable proof (counts, versions, repo-relative paths, PR numbers) and never machine paths (/var/tmp logs, tarballs — those live in the archive only). Cut values at clause boundaries, never mid-word; write Next: as one concrete follow-up action or none (audit-self-referential lines like "verdict decides" are stripped at render); pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent.
|