pi-goal-list-loop-audit 0.28.28 → 0.28.29
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/extensions/loops/goal.ts +54 -24
- package/package.json +1 -1
package/extensions/loops/goal.ts
CHANGED
|
@@ -482,35 +482,65 @@ const COMPACTION_GRACE_MS = 3 * 60_000;
|
|
|
482
482
|
// brake (1m, 2m, 4m, 8m, 16m cap). A successful turn resets both.
|
|
483
483
|
const ERROR_RETRY_LADDER_MS = [5_000, 15_000, 45_000, 90_000, 180_000];
|
|
484
484
|
let errorBrakeStreak = 0;
|
|
485
|
-
const
|
|
486
|
-
|
|
485
|
+
const SEND_REARM_LEDGER_MILESTONES_MS = [2 * 60_000, 5 * 60_000, 10 * 60_000];
|
|
486
|
+
// v0.28.29: escalation is TIME-based and ACTIVITY-gated. A busy session is
|
|
487
|
+
// NORMAL — the user conversing, or one long subagent turn — and the old
|
|
488
|
+
// flat-50ms × 6000-count rule misread 5 minutes of busy as "wedged" and
|
|
489
|
+
// paused the goal (the polis field report). Escalate only after 15 minutes
|
|
490
|
+
// of failed sends AND no session activity in the last 5 minutes (a wedged
|
|
491
|
+
// queue shows no events at all; a busy one streams constantly).
|
|
492
|
+
const SEND_REARM_ESCALATE_AFTER_MS = 15 * 60_000;
|
|
493
|
+
const SEND_REARM_ESCALATE_SILENT_MS = 5 * 60_000;
|
|
494
|
+
let continuationRearmSince = 0;
|
|
495
|
+
let loopRearmSince = 0;
|
|
496
|
+
let continuationRearmMilestone = 0;
|
|
497
|
+
let loopRearmMilestone = 0;
|
|
498
|
+
|
|
499
|
+
/** v0.28.29: busy-retry cadence backs off — 50ms for the first beats
|
|
500
|
+
* (instant pickup right after a turn ends), then 250ms, 1s, 5s, 15s, 30s
|
|
501
|
+
* cap. agent_end reschedules independently, so the slow tail costs nothing
|
|
502
|
+
* in the common case; it only caps the ledger/CPU spam of a long busy stretch. */
|
|
503
|
+
function sendRearmDelayMs(streak: number): number {
|
|
504
|
+
if (streak <= 4) return 50;
|
|
505
|
+
if (streak <= 8) return 250;
|
|
506
|
+
if (streak <= 12) return 1_000;
|
|
507
|
+
if (streak === 13) return 5_000;
|
|
508
|
+
if (streak === 14) return 15_000;
|
|
509
|
+
return 30_000;
|
|
510
|
+
}
|
|
487
511
|
|
|
488
512
|
function accountSendRearm(ctx: ExtensionContext, kind: "continuation" | "loop"): void {
|
|
489
513
|
const streak = kind === "continuation" ? ++continuationRearmStreak : ++loopRearmStreak;
|
|
490
514
|
if (streak === 1) {
|
|
515
|
+
if (kind === "continuation") { continuationRearmSince = Date.now(); continuationRearmMilestone = 0; } else { loopRearmSince = Date.now(); loopRearmMilestone = 0; }
|
|
491
516
|
appendLedger(ctx.cwd, "send_rearm_start", { kind });
|
|
492
517
|
return;
|
|
493
518
|
}
|
|
494
|
-
|
|
495
|
-
|
|
519
|
+
const since = kind === "continuation" ? continuationRearmSince : loopRearmSince;
|
|
520
|
+
const elapsed = Date.now() - since;
|
|
521
|
+
const milestone = kind === "continuation" ? continuationRearmMilestone : loopRearmMilestone;
|
|
522
|
+
if (milestone < SEND_REARM_LEDGER_MILESTONES_MS.length && elapsed >= SEND_REARM_LEDGER_MILESTONES_MS[milestone]!) {
|
|
523
|
+
if (kind === "continuation") continuationRearmMilestone++; else loopRearmMilestone++;
|
|
524
|
+
appendLedger(ctx.cwd, "send_rearm_storm", { kind, streak, minutes: Math.round(elapsed / 60000) });
|
|
496
525
|
}
|
|
497
|
-
if (
|
|
498
|
-
if (kind === "continuation") continuationRearmStreak = 0; else loopRearmStreak = 0;
|
|
526
|
+
if (elapsed >= SEND_REARM_ESCALATE_AFTER_MS && Date.now() - lastActivityAt >= SEND_REARM_ESCALATE_SILENT_MS) {
|
|
527
|
+
if (kind === "continuation") { continuationRearmStreak = 0; continuationRearmSince = 0; } else { loopRearmStreak = 0; loopRearmSince = 0; }
|
|
499
528
|
escalateSendRearmStorm(ctx, kind);
|
|
500
529
|
}
|
|
501
530
|
}
|
|
502
531
|
|
|
503
532
|
function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "loop"): void {
|
|
504
|
-
// Same loud-terminal shape as escalateStallNow (v0.24.7):
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
const mins = Math.round(
|
|
508
|
-
|
|
533
|
+
// Same loud-terminal shape as escalateStallNow (v0.24.7). v0.28.29: this
|
|
534
|
+
// only fires on a REAL wedge now (15m of failed sends + 5m of zero
|
|
535
|
+
// session activity) — busy-but-alive sessions never reach it.
|
|
536
|
+
const mins = Math.round(SEND_REARM_ESCALATE_AFTER_MS / 60000);
|
|
537
|
+
const silent = Math.round(SEND_REARM_ESCALATE_SILENT_MS / 60000);
|
|
538
|
+
appendLedger(ctx.cwd, "send_rearm_escalated", { kind, afterMinutes: mins, silentMinutes: silent });
|
|
509
539
|
if (kind === "loop" && isLoopActive()) {
|
|
510
540
|
clearLoopTimer();
|
|
511
|
-
state.loop = { ...state.loop!, active: false, stopReason: `send-retry storm: ${mins}m of
|
|
541
|
+
state.loop = { ...state.loop!, active: false, stopReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session is wedged. Restart pi, then /loop start again.` };
|
|
512
542
|
persistState(ctx);
|
|
513
|
-
ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m). Restart pi and /loop start.`, "warning");
|
|
543
|
+
ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m, session silent ${silent}m). Restart pi and /loop start.`, "warning");
|
|
514
544
|
notifyExternal(ctx, "Loop stopped: send-retry storm.");
|
|
515
545
|
return;
|
|
516
546
|
}
|
|
@@ -518,10 +548,10 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
|
|
|
518
548
|
updateGoal({
|
|
519
549
|
status: "paused",
|
|
520
550
|
pauseKind: "error",
|
|
521
|
-
pauseReason: `send-retry storm: ${mins}m of
|
|
522
|
-
pauseSuggestedAction: "The session
|
|
551
|
+
pauseReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session never went idle for the continuation`,
|
|
552
|
+
pauseSuggestedAction: "The session produced no events while the send retried (wedged queue). Restart pi, then /goal resume.",
|
|
523
553
|
}, ctx);
|
|
524
|
-
ctx.ui.notify(`Goal paused: send-retry storm (${mins}m). Restart pi, then /goal resume.`, "warning");
|
|
554
|
+
ctx.ui.notify(`Goal paused: send-retry storm (${mins}m, session silent ${silent}m). Restart pi, then /goal resume.`, "warning");
|
|
525
555
|
notifyExternal(ctx, "Goal paused: send-retry storm.");
|
|
526
556
|
}
|
|
527
557
|
}
|
|
@@ -736,10 +766,10 @@ function sendContinuation(goalId: string): void {
|
|
|
736
766
|
return;
|
|
737
767
|
}
|
|
738
768
|
if (!ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
739
|
-
// v0.28.5 (E3): count + ledger + escalate the re-arm storm.
|
|
740
769
|
accountSendRearm(ctx, "continuation");
|
|
741
770
|
continuationScheduledFor = goalId;
|
|
742
|
-
|
|
771
|
+
// v0.28.29: backing-off cadence (was flat 50ms — 6,000 spins in 5m).
|
|
772
|
+
continuationTimer = setTimeout(() => sendContinuation(goalId), sendRearmDelayMs(continuationRearmStreak));
|
|
743
773
|
continuationTimer.unref?.();
|
|
744
774
|
return;
|
|
745
775
|
}
|
|
@@ -750,7 +780,7 @@ function sendContinuation(goalId: string): void {
|
|
|
750
780
|
content: continuationPrompt(state.goal!),
|
|
751
781
|
display: false,
|
|
752
782
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
753
|
-
continuationRearmStreak = 0; // v0.28.5 (E3): a landed send clears the storm
|
|
783
|
+
continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): a landed send clears the storm
|
|
754
784
|
appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
|
|
755
785
|
} catch (err) {
|
|
756
786
|
appendLedger(ctx.cwd, "goal_continuation_send_failed", { goalId, error: err instanceof Error ? err.message : String(err) });
|
|
@@ -2011,8 +2041,8 @@ function sendLoopTurn(): void {
|
|
|
2011
2041
|
if (!isLoopActive() || !extensionApi) return;
|
|
2012
2042
|
const ctx = freshCtx();
|
|
2013
2043
|
if (!ctx || !ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
2014
|
-
if (ctx) accountSendRearm(ctx, "loop");
|
|
2015
|
-
loopTimer = setTimeout(() => sendLoopTurn(),
|
|
2044
|
+
if (ctx) accountSendRearm(ctx, "loop");
|
|
2045
|
+
loopTimer = setTimeout(() => sendLoopTurn(), sendRearmDelayMs(loopRearmStreak)); // v0.28.29: backing-off cadence
|
|
2016
2046
|
loopTimer.unref?.();
|
|
2017
2047
|
return;
|
|
2018
2048
|
}
|
|
@@ -2059,7 +2089,7 @@ function sendLoopTurn(): void {
|
|
|
2059
2089
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
2060
2090
|
// v0.26.1: the send path is ledgered — the hegemon zombie spun 619
|
|
2061
2091
|
// refires with zero visibility into whether sends were landing.
|
|
2062
|
-
loopRearmStreak = 0; // v0.28.5 (E3): a landed turn clears the storm
|
|
2092
|
+
loopRearmStreak = 0; loopRearmSince = 0; // v0.28.5 (E3): a landed turn clears the storm
|
|
2063
2093
|
appendLedger(ctx.cwd, "loop_turn_sent", { iteration: loop.iteration });
|
|
2064
2094
|
} catch (err) {
|
|
2065
2095
|
// stale API — next agent_end reschedules (but if none comes, the
|
|
@@ -4702,8 +4732,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4702
4732
|
// v0.28.24: a compaction is LEGITIMATE busy time — reset the send-rearm
|
|
4703
4733
|
// storm streaks (π-web nearly escalated a "send-retry storm" pause during
|
|
4704
4734
|
// a 3.5-minute compact) and open the post-compaction stall grace.
|
|
4705
|
-
continuationRearmStreak = 0;
|
|
4706
|
-
loopRearmStreak = 0;
|
|
4735
|
+
continuationRearmStreak = 0; continuationRearmSince = 0;
|
|
4736
|
+
loopRearmStreak = 0; loopRearmSince = 0;
|
|
4707
4737
|
compactionGraceUntil = Date.now() + COMPACTION_GRACE_MS;
|
|
4708
4738
|
const settle = setTimeout(() => {
|
|
4709
4739
|
const c = freshCtx();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.29",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. \u2014 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 \u2014 only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|