infinity-harness 2.3.0 → 2.3.1
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 +21 -1
- package/extensions/infinity-harness/index.ts +28 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,10 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [2.3.
|
|
7
|
+
## [2.3.1] — 2026-08-24
|
|
8
|
+
|
|
9
|
+
*(2.3.0 was staged at the registry and never completed; 2.3.1 is that release plus the last two
|
|
10
|
+
fixes below, and is the first version of this work anyone can install.)*
|
|
8
11
|
|
|
9
12
|
Five bugs were reported against 2.1.0 by someone actually using the thing. Every one of them was
|
|
10
13
|
real, none of them could be seen by a test that mocks pi, and finding out why led to the change
|
|
@@ -106,6 +109,23 @@ that matters most in this release: **the suite now drives a real `pi` process.**
|
|
|
106
109
|
|
|
107
110
|
- **The phase picker did not offer RESEARCH** — a feature nobody could find.
|
|
108
111
|
|
|
112
|
+
- **A handoff in a one-shot `pi -p` run replaced the session out from under the instance that
|
|
113
|
+
asked for it**, so every handler afterwards touched a torn-down context and pi reported
|
|
114
|
+
"This extension ctx is stale after session replacement" on every turn. A headless run has no
|
|
115
|
+
next turn to hand anything to, so it does not hand off — and the extension now stops touching
|
|
116
|
+
pi the moment its session is shut down, whatever the reason.
|
|
117
|
+
|
|
118
|
+
- **A new phase counted as a stall.** The first failure of a phase was compared against the
|
|
119
|
+
fingerprint taken when the *previous* phase passed — identical, because nothing had happened
|
|
120
|
+
yet — so the run spent `retry` and `reframe` on the opening turn of every single phase, and
|
|
121
|
+
arrived at the rungs that matter with the cheap ones already gone. A stall is the agent
|
|
122
|
+
producing nothing when asked; a fresh brief has not asked yet.
|
|
123
|
+
|
|
124
|
+
- **The gate history counted every pass twice.** `runChecks` recorded the verdict and
|
|
125
|
+
`transitionPhase` recorded it again, so the history read `define:pass → define:pass`, which says
|
|
126
|
+
a phase had to be attempted twice — the opposite of what happened. Repeated *failures* are still
|
|
127
|
+
every one of them: that is the fact a human comes back to read.
|
|
128
|
+
|
|
109
129
|
### Changed
|
|
110
130
|
|
|
111
131
|
- The dashboard shows every subtask, not only the active task's. The widget has nine rows and a job
|
|
@@ -117,6 +117,17 @@ export default function (pi: ExtensionAPI): void {
|
|
|
117
117
|
let remoteDir: string | null = null;
|
|
118
118
|
let view: WidgetView = defaultView();
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Is this instance's session still the live one?
|
|
122
|
+
*
|
|
123
|
+
* After `ctx.newSession()` pi tears the old runtime down and rebinds
|
|
124
|
+
* extensions, but this closure and its registered handlers still exist. Any
|
|
125
|
+
* of them that touches `pi` or a captured `ctx` afterwards gets
|
|
126
|
+
* "This extension ctx is stale after session replacement" — which is what a
|
|
127
|
+
* handoff produced on every single turn until this flag existed.
|
|
128
|
+
*/
|
|
129
|
+
let sessionLive = true;
|
|
130
|
+
|
|
120
131
|
/**
|
|
121
132
|
* Is a continuous run armed?
|
|
122
133
|
*
|
|
@@ -307,6 +318,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
307
318
|
// this session stops driving and the replacement never arrives. One
|
|
308
319
|
// attempt, then carry on here — a run that continues in a fat session is
|
|
309
320
|
// far better than a run that stops.
|
|
321
|
+
// A one-shot `pi -p` run has no next turn to hand anything to: replacing
|
|
322
|
+
// its session mid-flight produces a stale-context error and nothing else.
|
|
323
|
+
if (ctx.mode !== "tui" && ctx.mode !== "rpc") return false;
|
|
324
|
+
|
|
310
325
|
if (handingOff) {
|
|
311
326
|
if (hasPendingHandoff(dir)) {
|
|
312
327
|
notify(ctx, "infinity-harness: the new session never started — continuing here.", "warning");
|
|
@@ -531,6 +546,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
531
546
|
* summarised. Anything the run cannot afford to forget belongs here.
|
|
532
547
|
*/
|
|
533
548
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
549
|
+
if (!sessionLive) return;
|
|
534
550
|
const dir = projectDir(ctx);
|
|
535
551
|
if (!isHarnessProject(dir)) return;
|
|
536
552
|
try {
|
|
@@ -544,6 +560,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
544
560
|
});
|
|
545
561
|
|
|
546
562
|
pi.on("session_tree", async (_event, ctx) => {
|
|
563
|
+
if (!sessionLive) return;
|
|
547
564
|
refreshWidget(ctx);
|
|
548
565
|
});
|
|
549
566
|
|
|
@@ -553,6 +570,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
553
570
|
* few calls costs little and keeps the plan honest.
|
|
554
571
|
*/
|
|
555
572
|
pi.on("context", async (event, ctx) => {
|
|
573
|
+
if (!sessionLive) return;
|
|
556
574
|
const dir = projectDir(ctx);
|
|
557
575
|
if (!isHarnessProject(dir)) return;
|
|
558
576
|
|
|
@@ -630,6 +648,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
630
648
|
});
|
|
631
649
|
|
|
632
650
|
pi.on("session_compact", async (event, ctx) => {
|
|
651
|
+
if (!sessionLive) return;
|
|
633
652
|
const dir = projectDir(ctx);
|
|
634
653
|
if (!isHarnessProject(dir)) return;
|
|
635
654
|
try {
|
|
@@ -658,6 +677,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
658
677
|
});
|
|
659
678
|
|
|
660
679
|
pi.on("turn_end", async (_event, ctx) => {
|
|
680
|
+
if (!sessionLive) return;
|
|
661
681
|
refreshWidget(ctx);
|
|
662
682
|
});
|
|
663
683
|
|
|
@@ -670,6 +690,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
670
690
|
* `/reload`, `/resume`, and pi being restarted.
|
|
671
691
|
*/
|
|
672
692
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
693
|
+
if (!sessionLive) return;
|
|
673
694
|
const dir = projectDir(ctx);
|
|
674
695
|
if (!isHarnessProject(dir)) return;
|
|
675
696
|
if (loopBusy || handingOff) return;
|
|
@@ -749,6 +770,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
749
770
|
* touch of the config would stop the harness configuring itself.
|
|
750
771
|
*/
|
|
751
772
|
pi.on("tool_call", async (event, ctx) => {
|
|
773
|
+
if (!sessionLive) return;
|
|
752
774
|
const dir = projectDir(ctx);
|
|
753
775
|
if (!isHarnessProject(dir)) return;
|
|
754
776
|
|
|
@@ -791,6 +813,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
791
813
|
});
|
|
792
814
|
|
|
793
815
|
pi.on("session_shutdown", async () => {
|
|
816
|
+
sessionLive = false;
|
|
794
817
|
if (remoteServer) {
|
|
795
818
|
try {
|
|
796
819
|
await remoteServer.close();
|
|
@@ -1392,7 +1415,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1392
1415
|
} catch (e) {
|
|
1393
1416
|
// A handoff that cannot happen must never end the run: fall back to
|
|
1394
1417
|
// carrying on in this session, which is the pre-2.3 behaviour.
|
|
1418
|
+
//
|
|
1419
|
+
// Unless the session is already gone — `newSession` can fail *after*
|
|
1420
|
+
// replacing the runtime, and reaching for the old `pi` then is the
|
|
1421
|
+
// "stale ctx" error rather than a recovery.
|
|
1395
1422
|
clearHandoff(dir);
|
|
1423
|
+
if (!sessionLive) return;
|
|
1396
1424
|
notify(ctx, `infinity-harness: could not start a new session — ${errMsg(e)}`, "warning");
|
|
1397
1425
|
pi.sendUserMessage(pending.kickoff, { deliverAs: "followUp" });
|
|
1398
1426
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinity-harness",
|
|
3
|
-
"version": "2.3.
|
|
4
|
-
"description": "A pi agent extension that runs a gated build pipeline unattended
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "A pi agent extension that runs a gated build pipeline unattended \u2014 enforces phases, validates with deterministic gates, and keeps working for hours or days without losing the plan.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|