trantor 0.18.71 → 0.18.72
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/.claude-plugin/plugin.json +1 -1
- package/lib/classify-failure.mjs +29 -1
- package/lib/state/derive.mjs +4 -5
- package/lib/state/driver.mjs +4 -6
- package/lib/state/store.mjs +3 -6
- package/lib/state/validate.mjs +8 -13
- package/lib/turn-policy.mjs +3 -2
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.72",
|
|
4
4
|
"description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
|
|
5
5
|
"mcpServers": {
|
|
6
6
|
"relay": {
|
package/lib/classify-failure.mjs
CHANGED
|
@@ -89,6 +89,10 @@ export function verdictFor(realExit, effExit, emptyOutput, ownText, emptyTurn =
|
|
|
89
89
|
: "classified success because exit 0 with CLI output";
|
|
90
90
|
if (realExit === 0 && effExit === 1) {
|
|
91
91
|
if (emptyOutput) return "classified empty-output because exit 0 with no output on either stream";
|
|
92
|
+
// #8778: the exit-0 escalation must not relabel a provider-side rejection as "auth" — codex
|
|
93
|
+
// prints the chatgpt-backend 401 and exits 0, so the real specimen rode this exact path.
|
|
94
|
+
const pr = providerRejected(String(ownText || ""));
|
|
95
|
+
if (pr) return `classified ${pr.reason} because ${pr.matched}`;
|
|
92
96
|
const m = AUTH_MARKER_RE.exec(String(ownText || ""));
|
|
93
97
|
return `classified auth because ${m ? m[0] : "auth marker"} in the CLI's own short output`;
|
|
94
98
|
}
|
|
@@ -96,6 +100,26 @@ export function verdictFor(realExit, effExit, emptyOutput, ownText, emptyTurn =
|
|
|
96
100
|
return `classified ${reason} because ${matched}`;
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
// ---- the provider-rejected 401 (#8778) ---------------------------------------------------------
|
|
104
|
+
// chatgpt.com/backend-api is the ChatGPT-SUBSCRIPTION backend codex hits while logged in with no
|
|
105
|
+
// API key stored, so a 401 naming it is OpenAI rejecting its own server-side key — the specimen's
|
|
106
|
+
// sk-svcacct key existed in no env file, keychain or process env on the machine.
|
|
107
|
+
const REQUEST_ID_RE = /request id:\s*([a-z0-9][a-z0-9-]*)/i;
|
|
108
|
+
|
|
109
|
+
/** The #8778 rule: a 401 whose url is the chatgpt.com/backend-api endpoint classifies
|
|
110
|
+
* provider-rejected — the verdict names the request id and steers to waiting or the OpenAI
|
|
111
|
+
* account, never a re-login. Returns null for any other shape, so a 401 from an API-key
|
|
112
|
+
* endpoint (or one naming no endpoint) stays "auth". */
|
|
113
|
+
export function providerRejected(errText) {
|
|
114
|
+
const t = String(errText || "");
|
|
115
|
+
if (!/\b401\b/.test(t) || !/chatgpt\.com\/backend-api/i.test(t)) return null;
|
|
116
|
+
const rid = (t.match(REQUEST_ID_RE) || [])[1] || "";
|
|
117
|
+
return {
|
|
118
|
+
reason: "provider-rejected",
|
|
119
|
+
matched: `401 from https://chatgpt.com/backend-api${rid ? ` (request id ${rid})` : ""} — OpenAI rejected its own subscription key server-side; wait or check the OpenAI account, logging in again will not help`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
99
123
|
/** Classify one failed turn. Returns { reason, matched } — matched is the evidence excerpt the
|
|
100
124
|
* runner logs, so the next misclassification is diagnosable from the seat log alone. `cut` is the
|
|
101
125
|
* runner's box/stall marker: 137 reads as the sweep's SIGKILL only under it — without the marker
|
|
@@ -118,9 +142,13 @@ export function classifyFailure(exit, errText, emptyOutput = false, emptyTurn =
|
|
|
118
142
|
// #5684: a provider BACKEND failure is not quota — it wants retry/swap, not a window wait.
|
|
119
143
|
// The specimen (#5683): codex's "unexpected status 404 Not Found … /responses/compact" was
|
|
120
144
|
// labelled "exhausted" and the operator was advised to wait out a window that did not exist.
|
|
121
|
-
// 401/403/429 deliberately fall through to the auth/exhausted branches below.
|
|
145
|
+
// 401/403/429 deliberately fall through to the provider-rejected/auth/exhausted branches below.
|
|
122
146
|
let m = t.match(/unexpected status (?:404|408|410|5\d\d)|internal server error|bad gateway|service unavailable|gateway time.?out|econnrefused|connection refused|socket hang ?up|network is unreachable/);
|
|
123
147
|
if (m) return { reason: "backend-error", matched: m[0] };
|
|
148
|
+
// #8778: ranked ABOVE the quota/auth patterns — the specimen text itself carries an "API key"
|
|
149
|
+
// phrase, and either pattern reading first would relabel the same provider-side rejection.
|
|
150
|
+
const pr = providerRejected(String(errText || ""));
|
|
151
|
+
if (pr) return pr;
|
|
124
152
|
// "reached your … limit" / "usage limit" catch the subscription CLIs (Claude's "You've reached
|
|
125
153
|
// your Fable 5 limit"), which say nothing about quota or credits and would otherwise read as a crash.
|
|
126
154
|
m = t.match(/quota|payment required|402|429|too many requests|rate.?limit|usage limit|exceeded your|reached your [^.\n]*limit|insufficient (?:credits?|funds)|out of (?:credits?|quota)/);
|
package/lib/state/derive.mjs
CHANGED
|
@@ -170,11 +170,10 @@ export function deriveState({ project, seat, card, worktree, handoffText, cardTi
|
|
|
170
170
|
now: Number.isInteger(now) ? now : Math.floor(Date.now() / 1000),
|
|
171
171
|
by: author,
|
|
172
172
|
files: deriveFiles(worktree),
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// empty — so the successor must still re-earn evidence before anything NEW moves to done.
|
|
173
|
+
// RECONSTRUCTION, not a live claim (#8068): a ✅ bullet records what the predecessor reported
|
|
174
|
+
// done, so the arrival-in-done gate must not fire on this path — CONTRACT-state.md, "Derive".
|
|
175
|
+
// Credit is still withheld (verified:false everywhere), so the successor re-earns evidence
|
|
176
|
+
// before anything NEW moves to done.
|
|
178
177
|
reconstructing: true,
|
|
179
178
|
};
|
|
180
179
|
|
package/lib/state/driver.mjs
CHANGED
|
@@ -277,12 +277,10 @@ export async function runStep({
|
|
|
277
277
|
// Per attempt, not per step (#7226): a cut on the first attempt belongs to that attempt's row.
|
|
278
278
|
let stepCut = cut === true;
|
|
279
279
|
|
|
280
|
-
// §7.3's
|
|
281
|
-
// as the observation
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// a malformed-output retry re-runs the CLI, and commits the first attempt already made are still
|
|
285
|
-
// this step's work — re-reading HEAD per attempt would silently drop them.
|
|
280
|
+
// Two seams, both in CONTRACT-state.md: (1) §7.3's ladder — a MALFORMED patch earns exactly one
|
|
281
|
+
// retry with the rejection as the observation; an EVIDENCE rejection is not retried in-turn,
|
|
282
|
+
// the code was wrong, not the grammar (#8068). (2) HEAD captured ONCE, outside the retry loop:
|
|
283
|
+
// the first attempt's commits are still this step's work (#8069).
|
|
286
284
|
const headAtStart = (D.gitHead || gitHead)(cwd);
|
|
287
285
|
|
|
288
286
|
for (;;) {
|
package/lib/state/store.mjs
CHANGED
|
@@ -91,12 +91,9 @@ export function gitHead(cwd) {
|
|
|
91
91
|
return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/** Paths changed by the COMMITS made since `sha`, NUL-separated (#8069). The
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* corner case: every seat is told to commit early and often so a cut cannot erase it, which made tier 1
|
|
98
|
-
* blind on the ordinary flow. A card that rewrote 8,000 lines across 4 commits recorded files:{}.
|
|
99
|
-
* Degrades to [] on any git error, exactly like the other readers — recovery never throws. */
|
|
94
|
+
/** Paths changed by the COMMITS made since `sha`, NUL-separated (#8069). The §4.8 status/diff pair
|
|
95
|
+
* measures the tree against HEAD, so a seat that commits (the ordinary flow) reads empty from
|
|
96
|
+
* both — CONTRACT-state.md, "Tier 1". Degrades to [] on any git error, like the other readers. */
|
|
100
97
|
export function gitCommittedSince(sha, cwd) {
|
|
101
98
|
if (!sha || !cwd) return [];
|
|
102
99
|
const out = git(["diff", "--name-only", "-z", `${sha}..HEAD`], cwd);
|
package/lib/state/validate.mjs
CHANGED
|
@@ -49,21 +49,16 @@ export function hasEvidence(state, item, ctx = {}) {
|
|
|
49
49
|
return paths.every(p => ({ ...(state.files[p] || {}), ...(ctxFiles[p] || {}) }).verified === true);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** The evidence gate for an item ARRIVING in `done`, by whichever op put it there (#8068). Returns
|
|
53
|
-
* rejection, or null when the item may land. `move → done`
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* only thing that differs, so the seat's rejection still names the op it actually wrote. */
|
|
52
|
+
/** The evidence gate for an item ARRIVING in `done`, by whichever op put it there (#8068). Returns
|
|
53
|
+
* the rejection, or null when the item may land. Both doors — `move → done` and
|
|
54
|
+
* `add … list:"done"` — lead through this one check (CONTRACT-state.md, "Schema and validation");
|
|
55
|
+
* only `at` differs, so the rejection names the op the seat actually wrote. */
|
|
57
56
|
function arrivalInDone(state, item, ctx, at) {
|
|
58
57
|
if (!item || hasEvidence(state, item, ctx)) return null;
|
|
59
|
-
// The one exception
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// CONTRACT-state.md, "a derived state carries no credit … the successor must re-earn its evidence
|
|
64
|
-
// before anything moves to done". The items arrive uncredited, so the successor still cannot ride
|
|
65
|
-
// route (b) on them. `ctx` is harness-supplied and a patch cannot write it (§3.0 write matrix),
|
|
66
|
-
// so a seat cannot set this for itself — which is the only reason a flag is safe here at all.
|
|
58
|
+
// The one exception is RECONSTRUCTION, not assertion (#8068): derive.mjs's ✅ bullets are records
|
|
59
|
+
// of what the predecessor reported done, and gating them would delete the handoff's content
|
|
60
|
+
// (#6528's failure). The skipped rule is kept another way (CONTRACT-state.md, "Derive"); ctx is
|
|
61
|
+
// harness-supplied, so a seat cannot set this flag for itself.
|
|
67
62
|
if (ctx.reconstructing === true) return null;
|
|
68
63
|
// The split that makes §4.8's one-retry bound a fact: a gate that ran and failed looks
|
|
69
64
|
// exactly like a gate that never ran, unless the driver says which happened.
|
package/lib/turn-policy.mjs
CHANGED
|
@@ -207,8 +207,9 @@ export function quotaResetAt(rows, now = Date.now()) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
/// Seats that park rather than retry. A backend error is the provider having a bad minute and the
|
|
210
|
-
/// ladder is exactly right for it; a spent plan
|
|
211
|
-
|
|
210
|
+
/// ladder is exactly right for it; a spent plan, a rejected key, or a provider-side key rejection
|
|
211
|
+
/// (#8778: the chatgpt-backend 401 on a seat whose login is valid) will not fix itself on a timer.
|
|
212
|
+
export const PARKING_REASONS = new Set(["exhausted", "auth", "provider-rejected"]);
|
|
212
213
|
|
|
213
214
|
/// #6228: the sender's home project, by the same "name suffix after the last colon" convention
|
|
214
215
|
/// every crew/orch identity is minted with (isRunnerSession in crew-runner.mjs uses the mirror
|