trantor 0.18.16 → 0.18.17
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/bin/drill-surface.mjs +13 -5
- package/lib/balances.mjs +51 -9
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.17",
|
|
4
4
|
"description": "Trantor \u2014 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/bin/drill-surface.mjs
CHANGED
|
@@ -234,15 +234,23 @@ let predecessorSid = null;
|
|
|
234
234
|
step("S4 · handoff machine: warn → arm → fire → WRITTEN → successor claims → RECAPPED");
|
|
235
235
|
{
|
|
236
236
|
// A turn that uses a tool: the heartbeat is PostToolUse, so only tool use can arm the baton.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
237
|
+
// The heartbeat is PostToolUse: only a REAL tool call can arm the baton. Models sometimes
|
|
238
|
+
// answer without the tool (observed: run 2 of 3 on 2026-08-30 — 1-in-3 prompt fragility,
|
|
239
|
+
// not a seam), so ask, verify tool use in the transcript, and re-ask up to twice.
|
|
240
|
+
const findHandoff = () => {
|
|
241
241
|
try {
|
|
242
242
|
const f = readdirSync(join(bus, "handoffs")).find(x => x.startsWith(`${projectName}-`) && x.endsWith(".json"));
|
|
243
243
|
return f ? join(bus, "handoffs", f) : null;
|
|
244
244
|
} catch { return null; }
|
|
245
|
-
}
|
|
245
|
+
};
|
|
246
|
+
let handoffFile = null;
|
|
247
|
+
for (let attempt = 1; attempt <= 3 && !handoffFile; attempt++) {
|
|
248
|
+
const raw = await socketRequest({ id: "trantor:agent.prompt", method: "agent.prompt", params: {
|
|
249
|
+
target: pane, text: `You MUST call the Bash tool now and run exactly: pwd — do not answer without calling it. Then reply with just DONE-S4-${attempt}.` } });
|
|
250
|
+
if (JSON.parse(raw).result?.type !== "agent_prompted") { FAIL("S4 prompt accepted", raw.slice(0, 100)); break; }
|
|
251
|
+
handoffFile = await waitFor("handoff written", findHandoff, { timeoutMs: 120_000, everyMs: 2_000 });
|
|
252
|
+
if (!handoffFile) console.log(` ${D}attempt ${attempt}: no handoff yet — re-asking with the tool requirement${R}`);
|
|
253
|
+
}
|
|
246
254
|
if (!handoffFile) {
|
|
247
255
|
FAIL("the armed baton fired and WROTE a handoff at the turn boundary");
|
|
248
256
|
} else {
|
package/lib/balances.mjs
CHANGED
|
@@ -51,7 +51,15 @@ export const ADAPTERS = [
|
|
|
51
51
|
const win = (w, name) => (w && w.utilization != null)
|
|
52
52
|
? { name, usedPct: Math.round(w.utilization), resetsAt: w.resets_at || null, locked: w.locked_reason || null }
|
|
53
53
|
: null;
|
|
54
|
-
|
|
54
|
+
// Model-scoped weekly limits ride limits[] (kind "weekly_scoped", scope.model.display_name
|
|
55
|
+
// e.g. "Fable") — Orca shows these as their own segment ("36% used Fable") and so do we.
|
|
56
|
+
// Captured live 2026-08-30: session/weekly_all in limits[] duplicate five_hour/seven_day,
|
|
57
|
+
// so only the scoped entries add information.
|
|
58
|
+
const scoped = (Array.isArray(d.limits) ? d.limits : [])
|
|
59
|
+
.filter((l) => l && l.kind === "weekly_scoped" && l.percent != null && l.scope?.model?.display_name)
|
|
60
|
+
.map((l) => ({ name: String(l.scope.model.display_name), usedPct: Math.round(l.percent),
|
|
61
|
+
resetsAt: l.resets_at || null, locked: null, scoped: true }));
|
|
62
|
+
return { windows: [win(d.five_hour, "5h"), win(d.seven_day, "7d"), ...scoped].filter(Boolean) };
|
|
55
63
|
},
|
|
56
64
|
},
|
|
57
65
|
|
|
@@ -168,17 +176,51 @@ export async function fetchBalances(env = process.env, opts = {}) {
|
|
|
168
176
|
catch (e) { return { ...base, ok: false, error: String(e?.message || e) }; }
|
|
169
177
|
});
|
|
170
178
|
const rows = (await Promise.all(jobs)).filter(Boolean);
|
|
171
|
-
// Codex
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
179
|
+
// Codex: OpenAI publishes no public balance API, but the Codex CLI's OWN token can read the
|
|
180
|
+
// ChatGPT backend's usage windows — the same source Orca's footer reads (#5570; verified live
|
|
181
|
+
// 2026-08-30: plan_type + primary/secondary_window {used_percent, reset_at}). Evidence of
|
|
182
|
+
// configuration is the login artifact (~/.codex/auth.json), not an env key. The token never
|
|
183
|
+
// leaves the process; only percentages are reported. If the endpoint is unreachable, fall
|
|
184
|
+
// back to the honest subscription row this used to be — never an error row for a seat that
|
|
185
|
+
// bills flat.
|
|
186
|
+
// Same profile gate as every adapter (latent bug from the v6 static row, surfaced 2026-08-30:
|
|
187
|
+
// `!only ||` made a codex row appear with NO profile at all — "better empty than wrong").
|
|
188
|
+
if (only && (only.has("codex") || only.has("openai"))) {
|
|
175
189
|
try {
|
|
176
|
-
const { existsSync } = await import("node:fs");
|
|
190
|
+
const { existsSync, readFileSync } = await import("node:fs");
|
|
177
191
|
const { join } = await import("node:path");
|
|
178
192
|
const { homedir } = await import("node:os");
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
const authPath = join(homedir(), ".codex", "auth.json");
|
|
194
|
+
if (existsSync(authPath)) {
|
|
195
|
+
const base = { provider: "codex", label: "Codex", via: "codex login", ok: true };
|
|
196
|
+
try {
|
|
197
|
+
const tok = JSON.parse(readFileSync(authPath, "utf8"))?.tokens || {};
|
|
198
|
+
if (!tok.access_token) throw new Error("no codex token");
|
|
199
|
+
const r = await fetch("https://chatgpt.com/backend-api/wham/usage", {
|
|
200
|
+
headers: {
|
|
201
|
+
authorization: `Bearer ${tok.access_token}`,
|
|
202
|
+
"ChatGPT-Account-Id": tok.account_id || "",
|
|
203
|
+
"User-Agent": "codex-cli", "OpenAI-Beta": "codex-1", originator: "Codex Desktop",
|
|
204
|
+
},
|
|
205
|
+
signal: AbortSignal.timeout(8000),
|
|
206
|
+
});
|
|
207
|
+
if (!r.ok) throw new Error(`wham/usage ${r.status}`);
|
|
208
|
+
const d = await r.json();
|
|
209
|
+
const win = (w, fallbackName) => {
|
|
210
|
+
if (!w || w.used_percent == null) return null;
|
|
211
|
+
const secs = Number(w.limit_window_seconds) || 0;
|
|
212
|
+
const name = secs === 604800 ? "7d" : secs > 0 && secs <= 21600 ? "5h"
|
|
213
|
+
: secs > 0 ? `${Math.round(secs / 3600)}h` : fallbackName;
|
|
214
|
+
return { name, usedPct: Math.round(w.used_percent),
|
|
215
|
+
resetsAt: w.reset_at ? Number(w.reset_at) * 1000 : null, locked: null };
|
|
216
|
+
};
|
|
217
|
+
const windows = [win(d.rate_limit?.primary_window, "5h"), win(d.rate_limit?.secondary_window, "7d")].filter(Boolean);
|
|
218
|
+
if (!windows.length) throw new Error("no usage windows in response");
|
|
219
|
+
rows.push({ ...base, kind: "windows", plan: d.plan_type ? `ChatGPT ${d.plan_type}` : "OpenAI subscription", windows });
|
|
220
|
+
} catch {
|
|
221
|
+
rows.push({ ...base, kind: "subscription",
|
|
222
|
+
plan: "OpenAI subscription", note: "usage endpoint unreachable — flat subscription" });
|
|
223
|
+
}
|
|
182
224
|
}
|
|
183
225
|
} catch { /* no fs access → no row, never an error */ }
|
|
184
226
|
}
|