pi-goal-list-loop-audit 0.27.1 → 0.27.2

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.
@@ -0,0 +1,72 @@
1
+ // pi-goal-list-loop-audit — v0.27.2
2
+ // extensions/length-continue.ts
3
+ //
4
+ // Folded-in auto-continue for output-token truncation (was the standalone
5
+ // pi-length-continue 0.1.0, now deprecated). When ONE assistant response
6
+ // exceeds the model's provider-side per-response output cap, pi ends the
7
+ // turn with stopReason "length" and idles — a dead stop on unattended rigs.
8
+ // The tracker decides when to re-trigger; goal.ts's agent_end handler wires
9
+ // it BEFORE all turn bookkeeping: a truncated turn is not a completed turn
10
+ // (no telemetry), not a stall (no no-tool nudge), and must not run the
11
+ // loop measure or the normal goal continuation on half a response.
12
+ //
13
+ // Guards (same as the standalone):
14
+ // - consecutive cap: after MAX back-to-back truncations, give up (once)
15
+ // instead of burning quota in a truncation ping-pong. Any normally
16
+ // finished turn resets the counter.
17
+ // - the caller skips when messages are already pending (a queued message
18
+ // triggers a turn anyway) and routes stale-handle errors to
19
+ // goStaleTerminal (pi#7154).
20
+
21
+ export const LENGTH_CONTINUE_MAX = 3;
22
+
23
+ export const LENGTH_CONTINUE_TEXT = [
24
+ "Your previous response was cut off at the model's per-response output token limit.",
25
+ "Continue EXACTLY where you stopped — finish the current artifact, then keep going.",
26
+ "Keep each individual response shorter from here: split large file writes into multiple smaller write/edit calls across turns instead of one giant response.",
27
+ ].join(" ");
28
+
29
+ export interface LengthContinueTick {
30
+ /** Send the continue message this round. */
31
+ fire: boolean;
32
+ /** The cap was just exceeded — notify the give-up exactly once. */
33
+ giveUpNow: boolean;
34
+ /** Current consecutive truncation streak (after this tick). */
35
+ consecutive: number;
36
+ }
37
+
38
+ export function makeLengthContinueTracker(max: number = LENGTH_CONTINUE_MAX) {
39
+ let consecutive = 0;
40
+ let gaveUp = false;
41
+ return {
42
+ tick(stopped: boolean): LengthContinueTick {
43
+ if (!stopped) {
44
+ consecutive = 0;
45
+ gaveUp = false;
46
+ return { fire: false, giveUpNow: false, consecutive: 0 };
47
+ }
48
+ consecutive++;
49
+ if (consecutive > max) {
50
+ const giveUpNow = !gaveUp;
51
+ gaveUp = true;
52
+ return { fire: false, giveUpNow, consecutive };
53
+ }
54
+ return { fire: true, giveUpNow: false, consecutive };
55
+ },
56
+ get consecutive(): number {
57
+ return consecutive;
58
+ },
59
+ };
60
+ }
61
+
62
+ // Session-level singleton — one tracker per extension runtime. The factory
63
+ // calls resetLengthContinue() so an extension reload starts clean.
64
+ let tracker = makeLengthContinueTracker();
65
+
66
+ export function tickLengthContinue(stopped: boolean): LengthContinueTick {
67
+ return tracker.tick(stopped);
68
+ }
69
+
70
+ export function resetLengthContinue(): void {
71
+ tracker = makeLengthContinueTracker();
72
+ }
@@ -86,6 +86,12 @@ import {
86
86
  writeGoalMd,
87
87
  missingGllaTools,
88
88
  } from "../goal-loop-core.js";
89
+ import {
90
+ LENGTH_CONTINUE_MAX,
91
+ LENGTH_CONTINUE_TEXT,
92
+ resetLengthContinue,
93
+ tickLengthContinue,
94
+ } from "../length-continue.js";
89
95
  import {
90
96
  isQuotaError,
91
97
  isSubagentQuotaResult,
@@ -527,6 +533,25 @@ function sendContinuation(goalId: string): void {
527
533
  }
528
534
  }
529
535
 
536
+ // v0.27.2: send the truncation-continue nudge. Same guards as
537
+ // sendContinuation (stale api = terminal), independent of goal state —
538
+ // plain sessions truncate too.
539
+ function sendLengthContinue(ctx: ExtensionContext, consecutive: number): void {
540
+ if (!extensionApi || extensionApiStale) return;
541
+ try {
542
+ extensionApi.sendMessage({
543
+ customType: GOAL_EVENT_ENTRY,
544
+ content: LENGTH_CONTINUE_TEXT,
545
+ display: true,
546
+ }, { triggerTurn: true, deliverAs: "followUp" });
547
+ appendLedger(ctx.cwd, "length_continue_sent", { consecutive });
548
+ ctx.ui.notify(`Response hit the output-token cap — auto-continuing (${consecutive}/${LENGTH_CONTINUE_MAX})`, "warning");
549
+ } catch (err) {
550
+ appendLedger(ctx.cwd, "length_continue_send_failed", { consecutive, error: err instanceof Error ? err.message : String(err) });
551
+ if (isStaleApiError(err)) goStaleTerminal(ctx, "sendLengthContinue");
552
+ }
553
+ }
554
+
530
555
  function continuationPrompt(goal: Goal): string {
531
556
  // Read the .md file as the template, then substitute {{tokens}}.
532
557
  // For v0.1.0 we inline-substitute so we don't need fs at runtime.
@@ -3463,6 +3488,7 @@ function warnOnCommandCollision(ctx: ExtensionContext): void {
3463
3488
  export default function (pi: ExtensionAPI): void {
3464
3489
  extensionApi = pi;
3465
3490
  extensionApiStale = false; // a fresh factory run means a fresh runtime (reload path)
3491
+ resetLengthContinue(); // v0.27.2: fresh runtime, fresh truncation streak
3466
3492
  startHeartbeat();
3467
3493
  startUITicker();
3468
3494
  // Four top-level commands, that's all (v0.8.0 consolidation):
@@ -3778,6 +3804,23 @@ export default function (pi: ExtensionAPI): void {
3778
3804
  // continuation loop.
3779
3805
  if (isForeignCtx(ctx)) return;
3780
3806
  noteActivity(true);
3807
+ // v0.27.2: folded-in length-continue (standalone pi-length-continue is
3808
+ // deprecated). A response cut by the per-response output cap is NOT a
3809
+ // completed turn (no telemetry), NOT a stall (no no-tool nudge), and
3810
+ // must not run the loop measure or the normal goal continuation on half
3811
+ // a response — re-trigger immediately with split-smaller guidance and
3812
+ // skip ALL turn bookkeeping; the NEXT agent_end processes the run.
3813
+ // Works with no goal active (plain sessions truncate too).
3814
+ const lastA = [...(event.messages as any[])].reverse().find((m) => m.role === "assistant");
3815
+ const lc = tickLengthContinue(lastA?.stopReason === "length");
3816
+ if (lc.giveUpNow) {
3817
+ ctx.ui.notify(`glla: response hit the output-token cap ${LENGTH_CONTINUE_MAX}× in a row — stepping aside. Ask the model to split the work into smaller pieces.`, "warning");
3818
+ notifyExternal(ctx, "Response truncated 3× in a row — giving up auto-continue.");
3819
+ }
3820
+ if (lastA?.stopReason === "length") {
3821
+ if (lc.fire && !ctx.hasPendingMessages()) sendLengthContinue(ctx, lc.consecutive);
3822
+ return;
3823
+ }
3781
3824
  // v0.25.2: per-goal turn telemetry (/glla stats).
3782
3825
  if (state.goal && state.goal.status === "active") {
3783
3826
  const t = state.goal.telemetry ?? { turns: 0, fileWrites: 0, bashCalls: 0 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.27.1",
3
+ "version": "0.27.2",
4
4
  "description": "Goal. Loop. Audit. Done. — a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor — only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",