@phi-code-admin/phi-code 0.93.2 → 0.94.0
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/extensions/phi/orchestrator.ts +46 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.94.0] - 2026-07-12
|
|
4
|
+
|
|
5
|
+
### Fixed — three defects the run telemetry caught live
|
|
6
|
+
|
|
7
|
+
The .phi/runs.jsonl telemetry (0.93.0) paid for itself on its first outing: it
|
|
8
|
+
showed /fix shots being killed mid-work by the flat 10-min phase cap (601s run,
|
|
9
|
+
empty phases, lost REPRO-CMD handoff), oracles going blind after those aborts,
|
|
10
|
+
and escalations burning ~10 min re-confirming a reproduction the driver itself
|
|
11
|
+
had just executed.
|
|
12
|
+
|
|
13
|
+
- **Per-phase timeout** (OrchestratorPhase.timeoutMs): the /fix single shot now
|
|
14
|
+
gets 25 min (measured: real shots run 8-18 min). The FIRST phase of a generic
|
|
15
|
+
run also gains the standard retry-on-fallback before being skipped (it was
|
|
16
|
+
skip-direct).
|
|
17
|
+
- **Oracle fallback reproduction**: when the shot's REPRO-CMD handoff was lost
|
|
18
|
+
but the conventional repro_issue.py exists, the oracle runs it instead of
|
|
19
|
+
finishing UNVERIFIED blind.
|
|
20
|
+
- **Escalation skips REPRODUCE**: the oracle just executed the red reproduction;
|
|
21
|
+
/fix now goes straight to LOCALIZE -> FIX -> VERIFY with the red run seeded,
|
|
22
|
+
saving ~10 min under tight budgets.
|
|
23
|
+
|
|
24
|
+
Full suite green (1472).
|
|
25
|
+
|
|
3
26
|
## [0.93.2] - 2026-07-12
|
|
4
27
|
|
|
5
28
|
### Fixed
|
|
@@ -307,6 +307,11 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
307
307
|
// 0..4 for the five base phases (explore..review); undefined for synthetic
|
|
308
308
|
// phases (the review fix + re-review). Used to checkpoint resume position.
|
|
309
309
|
baseIndex?: number;
|
|
310
|
+
// Per-phase timeout override (generic driver only). Defaults to
|
|
311
|
+
// MAX_PHASE_DURATION_MS. Measured need: a /fix single shot legitimately
|
|
312
|
+
// runs 8–18 min — the flat 10 min cap killed it mid-work and lost its
|
|
313
|
+
// REPRO-CMD handoff (telemetry: 601s run, phases empty, UNVERIFIED).
|
|
314
|
+
timeoutMs?: number;
|
|
310
315
|
}
|
|
311
316
|
|
|
312
317
|
let phaseQueue: OrchestratorPhase[] = [];
|
|
@@ -1360,8 +1365,18 @@ Tag the note with relevant keywords for vector search.
|
|
|
1360
1365
|
fixContext.oracleRan = true;
|
|
1361
1366
|
const cwd = ctx.cwd || process.cwd();
|
|
1362
1367
|
const sandbox = getSessionSandbox(cwd);
|
|
1363
|
-
|
|
1368
|
+
let reproCmd =
|
|
1364
1369
|
fixContext.state.failingTest?.trim() || fixContext.state.reproCommand?.trim() || fixContext.reproFromShot;
|
|
1370
|
+
// Conventional fallback: the shot is instructed to write `repro_issue.py`.
|
|
1371
|
+
// If its handoff was lost (phase timeout/abort) but the file exists, the
|
|
1372
|
+
// oracle can still run it instead of going blind (telemetry-measured gap).
|
|
1373
|
+
if (!reproCmd && existsSync(join(cwd, "repro_issue.py"))) {
|
|
1374
|
+
reproCmd = "python repro_issue.py";
|
|
1375
|
+
ctx.ui.notify(
|
|
1376
|
+
`\n🧷 Oracle fallback: found \`repro_issue.py\` — using \`${reproCmd}\` as the reproduction.`,
|
|
1377
|
+
"info",
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1365
1380
|
const suiteCmd = sandbox.recipe.test?.trim();
|
|
1366
1381
|
|
|
1367
1382
|
// A shot that changed NOTHING has nothing to verify — "UNVERIFIED" would be
|
|
@@ -1409,13 +1424,16 @@ Tag the note with relevant keywords for vector search.
|
|
|
1409
1424
|
);
|
|
1410
1425
|
return true;
|
|
1411
1426
|
}
|
|
1412
|
-
// escalate: the pipeline inherits the RED run as its concrete failing
|
|
1427
|
+
// escalate: the pipeline inherits the RED run as its concrete failing
|
|
1428
|
+
// state. REPRODUCE is SKIPPED — the oracle just executed the reproduction
|
|
1429
|
+
// and it is red; re-confirming it would burn ~10 min re-running what the
|
|
1430
|
+
// driver already ran (measured: escalations were dying on outer budgets).
|
|
1413
1431
|
ctx.ui.notify(
|
|
1414
|
-
`\n🔺 **/fix escalating
|
|
1432
|
+
`\n🔺 **/fix escalating** — ${decision.diagnostic}. The reproduction is already established (the oracle just ran it); going straight to LOCALIZE → FIX → VERIFY.`,
|
|
1415
1433
|
"warning",
|
|
1416
1434
|
);
|
|
1417
|
-
phaseQueue.push(...buildDebugPhases(decision.failing));
|
|
1418
|
-
return false; // fall through: sendNextGenericPhase
|
|
1435
|
+
phaseQueue.push(...buildDebugPhases(decision.failing).slice(1));
|
|
1436
|
+
return false; // fall through: sendNextGenericPhase dispatches LOCALIZE
|
|
1419
1437
|
}
|
|
1420
1438
|
|
|
1421
1439
|
// Multi-candidate arbitration — the oracle disposes. Apply each captured
|
|
@@ -1522,6 +1540,7 @@ Tag the note with relevant keywords for vector search.
|
|
|
1522
1540
|
if (warning) ctx.ui.notify(`\n⚠️ ${warning}`, "warning");
|
|
1523
1541
|
setTimeout(() => pi.sendUserMessage(phase.instruction, { deliverAs: "followUp" }), 500);
|
|
1524
1542
|
if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
|
|
1543
|
+
const phaseBudget = phase.timeoutMs ?? MAX_PHASE_DURATION_MS;
|
|
1525
1544
|
phaseTimeoutId = setTimeout(() => {
|
|
1526
1545
|
if (orchestrationActive && phasePending) {
|
|
1527
1546
|
phasePending = false;
|
|
@@ -1536,7 +1555,7 @@ Tag the note with relevant keywords for vector search.
|
|
|
1536
1555
|
phase.useFallback = true;
|
|
1537
1556
|
phaseQueue.unshift(phase);
|
|
1538
1557
|
ctx.ui.notify(
|
|
1539
|
-
`\n⏰ **Phase timed out** (${
|
|
1558
|
+
`\n⏰ **Phase timed out** (${Math.round(phaseBudget / 60000)} min) — retrying once on the fallback model.`,
|
|
1540
1559
|
"warning",
|
|
1541
1560
|
);
|
|
1542
1561
|
} else {
|
|
@@ -1545,7 +1564,7 @@ Tag the note with relevant keywords for vector search.
|
|
|
1545
1564
|
}
|
|
1546
1565
|
sendNextGenericPhase(ctx);
|
|
1547
1566
|
}
|
|
1548
|
-
},
|
|
1567
|
+
}, phaseBudget);
|
|
1549
1568
|
});
|
|
1550
1569
|
}
|
|
1551
1570
|
|
|
@@ -1743,6 +1762,7 @@ Tag the note with relevant keywords for vector search.
|
|
|
1743
1762
|
ctx.ui.notify(`\n${first.label} → \`${modelId}\``, "info");
|
|
1744
1763
|
if (warning) ctx.ui.notify(`\n⚠️ ${warning}`, "warning");
|
|
1745
1764
|
if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
|
|
1765
|
+
const firstBudget = first.timeoutMs ?? MAX_PHASE_DURATION_MS;
|
|
1746
1766
|
phaseTimeoutId = setTimeout(() => {
|
|
1747
1767
|
if (orchestrationActive && phasePending) {
|
|
1748
1768
|
phasePending = false;
|
|
@@ -1752,11 +1772,24 @@ Tag the note with relevant keywords for vector search.
|
|
|
1752
1772
|
} catch {
|
|
1753
1773
|
/* best effort */
|
|
1754
1774
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1775
|
+
// Same policy as every other phase: one retry on the fallback
|
|
1776
|
+
// model before skipping (the old skip-direct lost the whole run
|
|
1777
|
+
// when the very first phase was slow).
|
|
1778
|
+
if (!first.retried) {
|
|
1779
|
+
first.retried = true;
|
|
1780
|
+
first.useFallback = true;
|
|
1781
|
+
phaseQueue.unshift(first);
|
|
1782
|
+
ctx.ui.notify(
|
|
1783
|
+
`\n⏰ **First phase timed out** (${Math.round(firstBudget / 60000)} min) — retrying once on the fallback model.`,
|
|
1784
|
+
"warning",
|
|
1785
|
+
);
|
|
1786
|
+
} else {
|
|
1787
|
+
skippedPhases++;
|
|
1788
|
+
ctx.ui.notify(`\n⏰ **First phase timed out again** — skipping.`, "warning");
|
|
1789
|
+
}
|
|
1757
1790
|
sendNextGenericPhase(ctx);
|
|
1758
1791
|
}
|
|
1759
|
-
},
|
|
1792
|
+
}, firstBudget);
|
|
1760
1793
|
setTimeout(() => pi.sendUserMessage(first.instruction, { deliverAs: "followUp" }), 200);
|
|
1761
1794
|
});
|
|
1762
1795
|
}
|
|
@@ -2319,6 +2352,9 @@ With no runnable check at all, the result is honestly labelled UNVERIFIED.`,
|
|
|
2319
2352
|
await ensurePlansDir();
|
|
2320
2353
|
fixContext = { state, oracleRan: false };
|
|
2321
2354
|
const shot = genericPhase("shot", "🎯 Phase 1 — SINGLE SHOT", "code", "code", singleShotInstruction(state));
|
|
2355
|
+
// A real single shot legitimately runs 8–18 min (measured on the
|
|
2356
|
+
// baselines); the flat 10 min phase cap was killing it mid-work.
|
|
2357
|
+
shot.timeoutMs = 25 * 60 * 1000;
|
|
2322
2358
|
startGenericOrchestration(
|
|
2323
2359
|
"fix",
|
|
2324
2360
|
[shot],
|