tokenmaxxing 1.81.0 → 1.83.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.
@@ -0,0 +1,442 @@
1
+ import { closeSync, openSync, readdirSync, readFileSync, readSync, realpathSync, statSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import type { Subprocess } from "bun";
4
+ import { sortBy, uniq } from "es-toolkit";
5
+ import { z } from "zod";
6
+ import { PI_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
7
+ import { observeCodex } from "../lib/codex.ts";
8
+ import { evaluateAndMaybeSwap, type SwapDecision } from "../lib/decide.ts";
9
+ import { withLock } from "../lib/lock.ts";
10
+ import { errorMessage, log } from "../lib/log.ts";
11
+ import { claudePool, codexPaths, expandTilde, optionalEnv, paths, piPaths, type PiPool } from "../lib/paths.ts";
12
+ import { pickPiSeat, piMovers } from "../lib/pi.ts";
13
+ import { ensurePiStoreHome, piStoreUsable } from "../lib/piauth.ts";
14
+ import { thresholdBars, usableAt } from "../lib/picker.ts";
15
+ import { clearPresence, livingPresences } from "../lib/presence.ts";
16
+ import { teeObservation } from "../lib/sample.ts";
17
+ import { countdownWait, exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough, SEAT_POLL_MS, SEAT_RETRY_MS } from "../lib/supervise.ts";
18
+ import { saveTermios } from "../lib/tty.ts";
19
+ import { loadAccounts, loadConfig, releaseWaitClaim } from "../lib/state.ts";
20
+ import { gatedFamilies, modelFromFlag } from "../lib/usage.ts";
21
+ import { ErrnoSchema, JsonTextSchema, type Account, type ModelInfo } from "../lib/types.ts";
22
+
23
+ const SUBCOMMANDS = new Set(["install", "remove", "uninstall", "update", "list", "config", "auth"]);
24
+
25
+ const UNMANAGED_FLAGS = new Set(["-h", "--help", "-v", "--version", "-p", "--print", "-r", "--resume", "--no-session", "--api-key", "--export", "--list-models"]);
26
+
27
+ const VALUE_FLAGS = new Set([
28
+ "--provider", "--model", "--system-prompt", "--append-system-prompt", "-n", "--name", "--session-dir",
29
+ "--models", "-t", "--tools", "-xt", "--exclude-tools", "--thinking", "-e", "--extension", "--skill",
30
+ "--prompt-template", "--theme", "--use-theme", "--mode", "--tui-mode",
31
+ ]);
32
+
33
+ const BOOLEAN_FLAGS = new Set([
34
+ "-nt", "--no-tools", "-nbt", "--no-builtin-tools", "-ne", "--no-extensions", "-ns", "--no-skills",
35
+ "-np", "--no-prompt-templates", "--no-themes", "-nc", "--no-context-files", "--verbose",
36
+ "-a", "--approve", "-na", "--no-approve", "--offline",
37
+ ]);
38
+
39
+ type PiArgs = {
40
+ provider: string | null;
41
+ model: string | null;
42
+ sessionDir: string | null;
43
+ sessionId: string | null;
44
+ session: string | null;
45
+ fork: boolean;
46
+ continueLatest: boolean;
47
+ approve: boolean | null;
48
+ rest: string[];
49
+ flags: string[];
50
+ };
51
+
52
+ export function analyzePiArgs(argv: string[]): PiArgs | null {
53
+ if (argv[0] !== undefined && SUBCOMMANDS.has(argv[0])) return null;
54
+ const out: PiArgs = { provider: null, model: null, sessionDir: null, sessionId: null, session: null, fork: false, continueLatest: false, approve: null, rest: [], flags: [] };
55
+ for (let i = 0; i < argv.length; i++) {
56
+ const a = argv[i]!;
57
+ if (a === "--") {
58
+ out.rest.push(...argv.slice(i));
59
+ break;
60
+ }
61
+ if (UNMANAGED_FLAGS.has(a)) return null;
62
+ if (a === "-c" || a === "--continue") {
63
+ out.continueLatest = true;
64
+ continue;
65
+ }
66
+ if (a === "--session-id" || a === "--session") {
67
+ const value = argv[++i];
68
+ if (value === undefined) return null;
69
+ if (a === "--session-id") out.sessionId = value;
70
+ else out.session = value;
71
+ continue;
72
+ }
73
+ out.rest.push(a);
74
+ if (a === "--fork") {
75
+ const value = argv[++i];
76
+ if (value === undefined) return null;
77
+ out.rest.push(value);
78
+ out.fork = true;
79
+ continue;
80
+ }
81
+ if (VALUE_FLAGS.has(a)) {
82
+ const value = argv[i + 1];
83
+ if (value === undefined) return null;
84
+ i++;
85
+ out.rest.push(value);
86
+ out.flags.push(a, value);
87
+ if (a === "--mode" && value !== "text") return null;
88
+ if (a === "--provider") out.provider = value;
89
+ else if (a === "--model") out.model = value;
90
+ else if (a === "--session-dir") out.sessionDir = value;
91
+ continue;
92
+ }
93
+ if (BOOLEAN_FLAGS.has(a)) {
94
+ out.flags.push(a);
95
+ if (a === "-a" || a === "--approve") out.approve = true;
96
+ else if (a === "-na" || a === "--no-approve") out.approve = false;
97
+ continue;
98
+ }
99
+ if (a.startsWith("--")) {
100
+ const next = argv[i + 1];
101
+ if (!a.includes("=") && next !== undefined && !next.startsWith("-") && !next.startsWith("@")) {
102
+ i++;
103
+ out.rest.push(next);
104
+ out.flags.push(a, next);
105
+ } else out.flags.push(a);
106
+ continue;
107
+ }
108
+ if (a.startsWith("-")) return null;
109
+ }
110
+ const selectors = [out.session != null, out.continueLatest, out.sessionId != null || out.fork].filter(Boolean).length;
111
+ return selectors > 1 ? null : out;
112
+ }
113
+
114
+ const PiSettingsSchema = z.looseObject({
115
+ defaultProvider: z.string().optional().catch(undefined),
116
+ sessionDir: z.string().optional().catch(undefined),
117
+ defaultProjectTrust: z.string().optional().catch(undefined),
118
+ });
119
+ type PiSettings = z.infer<typeof PiSettingsSchema>;
120
+ type PiSettingsScopes = { global: PiSettings; project: PiSettings; projectTrusted: boolean };
121
+
122
+ const PiTrustSchema = z.record(z.string(), z.unknown());
123
+
124
+ function readPiJson<T extends z.ZodType>(file: string, schema: T): z.output<T> | null {
125
+ let text: string;
126
+ try {
127
+ text = readFileSync(file, "utf8");
128
+ } catch (e) {
129
+ if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return null;
130
+ throw e;
131
+ }
132
+ const parsed = schema.safeParse(JsonTextSchema.safeParse(text.startsWith("") ? text.slice(1) : text).data);
133
+ if (!parsed.success) log("pisupervisor.pi_file_unreadable", { file });
134
+ return parsed.success ? parsed.data : null;
135
+ }
136
+
137
+ function projectTrusted(args: PiArgs, global: PiSettings): boolean {
138
+ if (args.approve != null) return args.approve;
139
+ const trust = readPiJson(join(piPaths.home, "trust.json"), PiTrustSchema) ?? {};
140
+ let dir = realpathSync(process.cwd());
141
+ while (true) {
142
+ const decision = trust[dir];
143
+ if (decision === true || decision === false) return decision;
144
+ const parent = dirname(dir);
145
+ if (parent === dir) return global.defaultProjectTrust === "always";
146
+ dir = parent;
147
+ }
148
+ }
149
+
150
+ function piSettings(args: PiArgs): PiSettingsScopes {
151
+ const global = readPiJson(join(piPaths.home, "settings.json"), PiSettingsSchema) ?? {};
152
+ return {
153
+ global,
154
+ project: readPiJson(join(process.cwd(), ".pi", "settings.json"), PiSettingsSchema) ?? {},
155
+ projectTrusted: projectTrusted(args, global),
156
+ };
157
+ }
158
+
159
+ function poolOf(args: PiArgs, settings: PiSettingsScopes): PiPool | null {
160
+ const slash = args.model?.indexOf("/") ?? -1;
161
+ const defaultProvider = (settings.projectTrusted ? settings.project.defaultProvider : undefined) ?? settings.global.defaultProvider;
162
+ const modelProvider = args.model == null ? defaultProvider : slash > 0 ? args.model.slice(0, slash) : undefined;
163
+ const provider = (args.provider ?? modelProvider)?.toLowerCase();
164
+ return provider === "anthropic" ? "claude" : provider === "openai-codex" ? "codex" : null;
165
+ }
166
+
167
+ function sessionModel(args: PiArgs): ModelInfo | null {
168
+ const id = args.model?.split(":")[0] ?? null;
169
+ return modelFromFlag(id == null ? null : id.slice(id.indexOf("/") + 1));
170
+ }
171
+
172
+ function sessionDirs(args: PiArgs, settings: PiSettingsScopes): string[] {
173
+ const custom = [args.sessionDir, optionalEnv("PI_CODING_AGENT_SESSION_DIR"), settings.project.sessionDir, settings.global.sessionDir]
174
+ .filter((d): d is string => d != null && d !== "")
175
+ .map((d) => resolve(expandTilde(d)));
176
+ const cwd = process.cwd();
177
+ const trimmed = cwd.startsWith("/") || cwd.startsWith("\\") ? cwd.slice(1) : cwd;
178
+ return uniq([...custom, join(piPaths.home, "sessions", `--${trimmed.replaceAll("/", "-").replaceAll("\\", "-").replaceAll(":", "-")}--`)]);
179
+ }
180
+
181
+ const SessionHeaderSchema = z.looseObject({ type: z.literal("session"), id: z.string(), cwd: z.string() });
182
+
183
+ function sessionHeader(path: string): z.infer<typeof SessionHeaderSchema> | null {
184
+ let fd: number;
185
+ try {
186
+ fd = openSync(path, "r");
187
+ } catch {
188
+ return null;
189
+ }
190
+ try {
191
+ const buf = Buffer.alloc(16 * 1024);
192
+ const text = buf.subarray(0, readSync(fd, buf, 0, buf.length, 0)).toString("utf8");
193
+ const end = text.indexOf("\n");
194
+ return SessionHeaderSchema.safeParse(JsonTextSchema.safeParse(end === -1 ? text : text.slice(0, end)).data).data ?? null;
195
+ } finally {
196
+ closeSync(fd);
197
+ }
198
+ }
199
+
200
+ function sessionFiles(dir: string): string[] {
201
+ try {
202
+ return readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
203
+ } catch {
204
+ return [];
205
+ }
206
+ }
207
+
208
+ function localSessions(dir: string): string[] {
209
+ const cwd = resolve(process.cwd());
210
+ const dated = sessionFiles(dir).flatMap((f) => {
211
+ try {
212
+ return [{ path: join(dir, f), mtime: statSync(join(dir, f)).mtimeMs }];
213
+ } catch {
214
+ return [];
215
+ }
216
+ });
217
+ return sortBy(dated, [(x) => -x.mtime]).flatMap(({ path }) => {
218
+ const header = sessionHeader(path);
219
+ return header != null && resolve(header.cwd) === cwd ? [header.id] : [];
220
+ });
221
+ }
222
+
223
+ function sessionIdFor(args: PiArgs, dir: string): string | null {
224
+ if (args.sessionId != null) return args.sessionId;
225
+ if (args.continueLatest) return localSessions(dir)[0] ?? crypto.randomUUID();
226
+ if (args.session == null) return crypto.randomUUID();
227
+ const arg = args.session;
228
+ if (arg.includes("/") || arg.includes("\\") || arg.endsWith(".jsonl")) return null;
229
+ const ids = localSessions(dir);
230
+ if (ids.includes(arg)) return arg;
231
+ const matches = ids.filter((s) => s.startsWith(arg));
232
+ return matches.length === 1 ? matches[0]! : null;
233
+ }
234
+
235
+ function sessionExists(dirs: string[], sid: string): boolean {
236
+ return dirs.some((dir) => sessionFiles(dir).some((f) => f.endsWith(`_${sid}.jsonl`)));
237
+ }
238
+
239
+ type Launch = { args: PiArgs; pool: PiPool; dirs: string[]; sid: string };
240
+
241
+ function managedLaunch(argv: string[]): Launch | null {
242
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true || process.env.TOKENMAXXING_PROBE || process.env[UNMANAGED_ENV]) return null;
243
+ const args = analyzePiArgs(argv);
244
+ if (args == null) return null;
245
+ const settings = piSettings(args);
246
+ const pool = poolOf(args, settings);
247
+ if (pool == null) {
248
+ log("pisupervisor.unpooled", {
249
+ provider: args.provider,
250
+ model: args.model,
251
+ projectTrusted: settings.projectTrusted,
252
+ defaultProvider: (settings.projectTrusted ? settings.project.defaultProvider : undefined) ?? settings.global.defaultProvider,
253
+ });
254
+ return null;
255
+ }
256
+ const dirs = sessionDirs(args, settings);
257
+ const sid = sessionIdFor(args, dirs[0]!);
258
+ if (sid == null) {
259
+ log("pisupervisor.session_unresolved", { session: args.session });
260
+ return null;
261
+ }
262
+ return { args, pool, dirs, sid };
263
+ }
264
+
265
+ const RESUME_PROMPT =
266
+ "tokenmaxxing resumed this session on an account with quota headroom. Continue the task from where the previous turn left off. If the previous turn ended waiting on the user, restate what you need and wait. The move restarted pi, which ended every command this session was still running. Relaunch what the task still needs.";
267
+
268
+ function validWanted(pool: PiPool, wanted: string | null, id: string): Account | null {
269
+ if (wanted == null) return null;
270
+ const a = loadAccounts(piMovers[pool].pool).accounts.find((x) => x.id === wanted);
271
+ if (!a || a.needsReauth === true || !piStoreUsable(pool, a.id)) return null;
272
+ if (pool === "codex" && livingPresences(codexPaths.presenceDir).some((p) => p.accountId === a.id && p.id !== id)) return null;
273
+ return a;
274
+ }
275
+
276
+ type Watch = { decided: boolean; move: SwapDecision | null };
277
+
278
+ async function watchSeat(input: { pool: PiPool; seatId: string; id: string; families: string[] | null; overriddenUntil: number }): Promise<Watch> {
279
+ try {
280
+ const now = Date.now();
281
+ const cfg = loadConfig();
282
+ const mover = piMovers[input.pool];
283
+ const account = loadAccounts(mover.pool).accounts.find((a) => a.id === input.seatId);
284
+ if (!account) return { decided: false, move: null };
285
+ const observed = input.pool === "claude" ? teeObservation(account) : await observeCodex(account, cfg, now, { probe: true, refresh: true, holder: input.id });
286
+ const unmeasured = input.pool === "codex" && (observed == null || now - observed.at > cfg.policy.usagePollTtlMs);
287
+ if (unmeasured) log("pisupervisor.seat_unmeasured", { seat: account.id.slice(0, 8), usageAt: observed?.at });
288
+ const current = observed ? { ...account, windows: observed.windows } : account;
289
+ const until = usableAt(current, { now, thresholds: thresholdBars(cfg), currentId: account.id, families: input.families, seats: null });
290
+ if (until <= now || until <= input.overriddenUntil) return { decided: unmeasured, move: null };
291
+ const decision = await evaluateAndMaybeSwap(mover, now, true, null, { seatId: account.id, ...(input.families ? { sessionFamilies: input.families } : {}), waiterId: input.id });
292
+ log("pisupervisor.seat_exhausted", { pool: input.pool, seat: account.id.slice(0, 8), until, reason: decision.reason, account: decision.account?.id.slice(0, 8), waitUntil: decision.waitUntil });
293
+ return { decided: true, move: decision.account && (decision.swapped || decision.waitUntil !== undefined) ? decision : null };
294
+ } catch (e) {
295
+ log("pisupervisor.seat_watch_error", { err: errorMessage(e) });
296
+ return { decided: true, move: null };
297
+ }
298
+ }
299
+
300
+ export async function runPiSupervisor(argv: string[]): Promise<number> {
301
+ if (loopGuardTripped("pi")) return 1;
302
+ let real: string;
303
+ try {
304
+ real = resolveRealBin(PI_BIN);
305
+ } catch (e) {
306
+ log("pisupervisor.resolve_failed", { err: errorMessage(e) });
307
+ throw e;
308
+ }
309
+ const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
310
+
311
+ let child: Subprocess | null = null;
312
+ let terminating = false;
313
+ let cleanup = async (): Promise<void> => {};
314
+ process.on("SIGTERM", () => {
315
+ terminating = true;
316
+ if (child) {
317
+ child.kill("SIGTERM");
318
+ return;
319
+ }
320
+ cleanup().finally(() => {
321
+ if (child) child.kill("SIGTERM");
322
+ else process.exit(143);
323
+ });
324
+ });
325
+
326
+ const launch = managedLaunch(argv);
327
+ if (launch == null) return runPassthrough({ real, argv, env: childEnv, onSpawn: (p) => { child = p; } });
328
+ const { args, pool, dirs, sid } = launch;
329
+
330
+ const id = `pi-${crypto.randomUUID()}`;
331
+ const mover = piMovers[pool];
332
+ const presenceDir = pool === "claude" ? paths.presenceDir : codexPaths.presenceDir;
333
+ const releaseClaim = async (): Promise<void> => {
334
+ if (pool !== "claude") return;
335
+ try {
336
+ await withLock(claudePool.lockFile, () => {
337
+ releaseWaitClaim(id);
338
+ });
339
+ } catch (e) {
340
+ log("pisupervisor.claim_release_failed", { err: errorMessage(e) });
341
+ }
342
+ };
343
+ cleanup = async () => {
344
+ clearPresence({ dir: presenceDir, id });
345
+ await releaseClaim();
346
+ };
347
+
348
+ const model = sessionModel(args);
349
+ const families = pool === "claude" ? gatedFamilies(model, loadConfig().policy.switchModels) : null;
350
+ const firstArgs = ["--session-id", sid, ...args.rest];
351
+ let launchArgs = firstArgs;
352
+
353
+ const savedTermios = saveTermios();
354
+ process.on("SIGINT", () => {});
355
+ process.on("SIGHUP", () => {});
356
+ const say = (terminal: string) => {
357
+ process.stderr.write(terminal);
358
+ };
359
+
360
+ let respawns = 0;
361
+ let wanted: string | null = null;
362
+ let overriddenUntil = 0;
363
+ try {
364
+ while (true) {
365
+ const launchedAt = Date.now();
366
+ const launched = await withLock(mover.pool.lockFile, async () => {
367
+ clearPresence({ dir: presenceDir, id });
368
+ if (terminating) return null;
369
+ const picked = validWanted(pool, wanted, id) ?? pickPiSeat(pool, launchedAt, model);
370
+ log("pisupervisor.launch", { id, pool, respawns, seat: picked?.id.slice(0, 8) ?? null, args: launchArgs.join(" ") });
371
+ const spawned = Bun.spawn([real, ...launchArgs], {
372
+ stdin: "inherit",
373
+ stdout: "inherit",
374
+ stderr: "inherit",
375
+ env: { ...childEnv, ...(picked ? { PI_CODING_AGENT_DIR: ensurePiStoreHome(pool, picked.id) } : {}) },
376
+ });
377
+ child = spawned;
378
+ if (picked) {
379
+ await recordPresenceOrStop({
380
+ child: spawned,
381
+ dir: presenceDir,
382
+ id,
383
+ accountId: picked.id,
384
+ event: "pisupervisor.presence_failed",
385
+ message: "could not write the pi session's presence file - refusing to run a session whose seat placement cannot see",
386
+ savedTermios,
387
+ });
388
+ }
389
+ return { proc: spawned, seat: picked };
390
+ });
391
+ if (launched == null) {
392
+ await releaseClaim();
393
+ return 143;
394
+ }
395
+
396
+ const { proc, seat } = launched;
397
+ const pending: { move: SwapDecision | null; killed: boolean } = { move: null, killed: false };
398
+ let seatCheckAt = 0;
399
+ await raceMarkerOrExit({
400
+ child: proc,
401
+ tick: async () => {
402
+ if (!seat || terminating || Date.now() < seatCheckAt) return false;
403
+ const watched = await watchSeat({ pool, seatId: seat.id, id, families, overriddenUntil });
404
+ seatCheckAt = Date.now() + (watched.decided ? SEAT_RETRY_MS : SEAT_POLL_MS);
405
+ pending.move = watched.move;
406
+ return watched.move !== null;
407
+ },
408
+ savedTermios,
409
+ onKill: () => {
410
+ pending.killed = true;
411
+ },
412
+ onError: () => clearPresence({ dir: presenceDir, id }),
413
+ onExited: () => {
414
+ child = null;
415
+ },
416
+ });
417
+
418
+ const target = pending.killed && !terminating ? (pending.move?.account ?? null) : null;
419
+ if (target) {
420
+ respawns++;
421
+ const waitUntil = pending.move?.waitUntil ?? 0;
422
+ if (waitUntil > Date.now()) {
423
+ if (await countdownWait(target.label, waitUntil, { stream: false, say })) overriddenUntil = waitUntil;
424
+ } else say(`\n\x1b[36m↻ tokenmaxxing: moving pi to ${target.label} - resuming...\x1b[0m\n`);
425
+ await releaseClaim();
426
+ wanted = target.id;
427
+ launchArgs = sessionExists(dirs, sid) ? ["--session-id", sid, ...args.flags, "--", RESUME_PROMPT] : firstArgs;
428
+ continue;
429
+ }
430
+ clearPresence({ dir: presenceDir, id });
431
+ await releaseClaim();
432
+ log("pisupervisor.exit", { id, respawns, code: proc.exitCode, signal: proc.signalCode, terminated: terminating || undefined });
433
+ return exitStatus(proc);
434
+ }
435
+ } catch (e) {
436
+ const msg = errorMessage(e);
437
+ say(`\n\x1b[31m${msg.startsWith("tokenmaxxing:") ? msg : `tokenmaxxing: ${msg}`}\x1b[0m\n`);
438
+ log("pisupervisor.fatal", { err: msg });
439
+ await releaseClaim();
440
+ return 1;
441
+ }
442
+ }
@@ -13,7 +13,7 @@ import { thresholdBars, usableAt } from "../lib/picker.ts";
13
13
  import { clearPresence } from "../lib/presence.ts";
14
14
  import { readLines } from "../lib/proc.ts";
15
15
  import { teeObservation } from "../lib/sample.ts";
16
- import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
16
+ import { countdownWait, exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough, SEAT_POLL_MS, SEAT_RETRY_MS, type Say } from "../lib/supervise.ts";
17
17
  import { saveTermios } from "../lib/tty.ts";
18
18
  import { liveSessionId, loadSessionFlags, pruneStaleSessions, saveSessionFlags, writeRespawnMarker } from "../lib/sessions.ts";
19
19
  import { loadAccounts, loadConfig, readJsonFile, releaseWaitClaim } from "../lib/state.ts";
@@ -203,9 +203,6 @@ function consumableMarker(marker: string, gate: MarkerGate): z.infer<typeof Resp
203
203
  return m;
204
204
  }
205
205
 
206
- const SEAT_POLL_MS = 10_000;
207
- const SEAT_RETRY_MS = 60_000;
208
-
209
206
  function seatBlockedUntil(seatId: string, now: number, families: string[], cfg: Config): number | null {
210
207
  const account = loadAccounts(claudePool).accounts.find((a) => a.id === seatId);
211
208
  if (!account) return null;
@@ -233,37 +230,10 @@ async function moveExhaustedSeat(seat: Account, sid: string, gate: MarkerGate, m
233
230
  return true;
234
231
  }
235
232
 
236
- type Say = (terminal: string, text: string) => void;
237
-
238
233
  function systemLine(sid: string, text: string): string {
239
234
  return `${JSON.stringify({ type: "system", subtype: "informational", content: text, level: "warning", uuid: crypto.randomUUID(), session_id: sid })}\n`;
240
235
  }
241
236
 
242
- const clockFmt = new Intl.DateTimeFormat("en-US", { hour: "2-digit", minute: "2-digit", hourCycle: "h23", timeZoneName: "short" });
243
-
244
- async function countdownWait(acct: string, until: number, out: { stream: boolean; say: Say }): Promise<boolean> {
245
- let aborted = false;
246
- const onInt = () => { aborted = true; };
247
- process.on("SIGINT", onInt);
248
- const minutes = Math.max(1, Math.ceil((until - Date.now()) / 60000));
249
- out.say(
250
- `\n\x1b[36m⏳ tokenmaxxing: all accounts at their limit. Resuming on ${acct} when it resets (Ctrl-C to resume now).\x1b[0m\n`,
251
- `tokenmaxxing: all accounts at their limit. Resuming at ${clockFmt.format(until)} (in ${minutes} min) on ${acct}.`,
252
- );
253
- while (!aborted && Date.now() < until) {
254
- if (!out.stream) {
255
- const left = until - Date.now();
256
- const m = Math.floor(left / 60000);
257
- const s = Math.floor((left % 60000) / 1000);
258
- process.stderr.write(`\r\x1b[36m resuming in ${m}m ${String(s).padStart(2, "0")}s \x1b[0m`);
259
- }
260
- await Bun.sleep(1000);
261
- }
262
- process.removeListener("SIGINT", onInt);
263
- out.say(`\n\x1b[36m↻ resuming on ${acct}...\x1b[0m\n`, `tokenmaxxing: resuming on ${acct}.`);
264
- return aborted;
265
- }
266
-
267
237
  function resumePrompt(input: { compacted: boolean; origin: z.infer<typeof RespawnMarkerSchema>["origin"] }): string {
268
238
  const moved = input.compacted
269
239
  ? "tokenmaxxing compacted this conversation and resumed the session on an account with quota headroom."
@@ -509,11 +479,13 @@ export async function runSupervisor(argv: string[]): Promise<number> {
509
479
  noticeSid = m.sessionId;
510
480
  const accounts = loadAccounts(claudePool).accounts;
511
481
  const label = accounts.find((a) => a.id === m.accountId)?.label ?? m.accountId.slice(0, 8);
512
- const walled = accounts.some((a) => a.id === seat?.id && a.enforcedUntil != null && a.enforcedUntil > Date.now());
482
+ const walledUntil = accounts.find((a) => a.id === seat?.id)?.enforcedUntil ?? 0;
513
483
  const transcript = transcriptPath(m.sessionId);
514
484
  const resumable = existsSync(transcript);
515
485
  let compacted = false;
516
- if (m.compact && seat && resumable && !walled) {
486
+ if (m.compact && seat && resumable && walledUntil > Date.now()) {
487
+ log("supervisor.compact_skipped", { sid: m.sessionId.slice(0, 8), seat: seat.id.slice(0, 8), until: walledUntil });
488
+ } else if (m.compact && seat && resumable) {
517
489
  say(`\n\x1b[36m↻ tokenmaxxing: compacting the conversation on ${seat.label} before the move...\x1b[0m\n`, `tokenmaxxing: compacting the conversation on ${seat.label} before the move.`);
518
490
  const compactEnv: Record<string, string | undefined> = { ...childEnv, TOKENMAXXING_PROBE: "1", CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(seat.id) };
519
491
  delete compactEnv.TOKENMAXXING_SUPERVISED;
package/src/lib/claude.ts CHANGED
@@ -9,6 +9,7 @@ import { errorMessage, log } from "./log.ts";
9
9
  import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential, InvalidGrantError } from "./oauth.ts";
10
10
  import { claudePool, env, paths, seatFromEnv, storeDirFor } from "./paths.ts";
11
11
  import { pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
12
+ import { deletePiStore } from "./piauth.ts";
12
13
  import { seatCounts } from "./presence.ts";
13
14
  import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
14
15
  import { foldTee, sampleAccountUsage, teeObservation, usageBlockedUntil } from "./sample.ts";
@@ -117,20 +118,22 @@ async function storeUsable(a: Account): Promise<boolean> {
117
118
  }
118
119
  }
119
120
 
120
- export function pickSeat(now: number, model: ModelInfo | null): Account | null {
121
+ export function pickSeat(now: number, model: ModelInfo | null, eligible: (a: Account) => boolean = () => true): Account | null {
121
122
  const cfg = loadConfig();
122
123
  const idx = loadAccounts(claudePool);
123
124
  let dirty = false;
124
125
  for (const a of idx.accounts) dirty = foldTee(a) || dirty;
125
126
  if (dirty) saveAccounts(claudePool, idx);
126
127
  const ctx: PickCtx = { now, thresholds: thresholdBars(cfg), currentId: null, families: gatedFamilies(model, cfg.policy.switchModels), seats: presence() };
127
- return pickBest(idx.accounts, ctx) ?? pickEarliestReset(idx.accounts, ctx)?.account ?? null;
128
+ const candidates = idx.accounts.filter(eligible);
129
+ return pickBest(candidates, ctx) ?? pickEarliestReset(candidates, ctx)?.account ?? null;
128
130
  }
129
131
 
130
132
  async function removeCredentials(a: Account): Promise<void> {
131
133
  await deleteItem(storeTarget(a.id));
132
134
  rmSync(storeDirFor(a.id), { recursive: true, force: true });
133
135
  rmSync(`${storeDirFor(a.id)}.lock`, { recursive: true, force: true });
136
+ deletePiStore("claude", a.id);
134
137
  clearUsageSnapshot(a.id);
135
138
  }
136
139
 
@@ -236,7 +239,7 @@ async function importLive(): Promise<Harvest | null> {
236
239
  return login();
237
240
  }
238
241
 
239
- function ensurePathAhead(): void {
242
+ export function ensurePathAhead(): void {
240
243
  const rc = shellRcPath();
241
244
  if (!rc) {
242
245
  console.log(c.yellow(`⚠ add to your shell rc: export PATH="${paths.binDir}:$PATH"`));
@@ -102,6 +102,15 @@ export function verifyRealBin(input: { bin: string; name: string; versionOk: (ve
102
102
 
103
103
  export const CLAUDE_BIN = { name: "claude", key: "claudeBin", versionOk: (out: string) => out.toLowerCase().includes("claude") } as const;
104
104
  export const CODEX_BIN = { name: "codex", key: "codexBin", versionOk: (out: string) => out.toLowerCase().includes("codex") } as const;
105
+ function isBareVersion(out: string): boolean {
106
+ try {
107
+ return Bun.semver.order(out, "0.0.0") >= 0;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ export const PI_BIN = { name: "pi", key: "piBin", versionOk: isBareVersion } as const;
105
114
 
106
115
  export function resolveVerifiedClaude(): string {
107
116
  const candidates: string[] = [];
package/src/lib/codex.ts CHANGED
@@ -4,7 +4,8 @@ import { writeFileAtomic } from "./atomic.ts";
4
4
  import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin, verifyRealBin } from "./claudebin.ts";
5
5
  import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
6
6
  import { CodexInvalidGrantError, CodexRefreshFailedError, refreshCodexAuth } from "./codexoauth.ts";
7
- import { seatCounts } from "./presence.ts";
7
+ import { deletePiStore } from "./piauth.ts";
8
+ import { livingPresences, seatCounts } from "./presence.ts";
8
9
  import { CodexUsageReadError, codexLimitLabel, fetchCodexUsage } from "./codexusage.ts";
9
10
  import { codexSupervisorLink, ensurePathInRc, installCodexSupervisor, managedShellRcSkipLines, shellRcPath } from "./install.ts";
10
11
  import { withLock } from "./lock.ts";
@@ -34,7 +35,7 @@ function applyUsage(account: Account, usage: CodexUsage, at: number): void {
34
35
 
35
36
  type CodexReadOutcome = { ok: true; usage: CodexUsage; at: number } | { ok: false; reason: string; deadGrant: boolean };
36
37
 
37
- async function readCodexUsage(account: Account, now: number, refresh: boolean): Promise<CodexReadOutcome> {
38
+ async function readCodexUsage(account: Account, now: number, refresh: boolean, holder?: string): Promise<CodexReadOutcome> {
38
39
  let auth: CodexAuthJson | null;
39
40
  try {
40
41
  auth = readCodexStoreAuth(account.id);
@@ -47,7 +48,7 @@ async function readCodexUsage(account: Account, now: number, refresh: boolean):
47
48
  if (!refresh) {
48
49
  return { ok: false, reason: "stored access token is expiring and this read never refreshes a store", deadGrant: false };
49
50
  }
50
- if (seatCounts(codexPaths.presenceDir).has(account.id)) {
51
+ if (livingPresences(codexPaths.presenceDir).some((p) => p.accountId === account.id && p.id !== holder)) {
51
52
  return { ok: false, reason: "running in a live codex session (store refresh unsafe)", deadGrant: false };
52
53
  }
53
54
  auth = await refreshCodexAuth({ auth, now });
@@ -62,9 +63,9 @@ async function readCodexUsage(account: Account, now: number, refresh: boolean):
62
63
  }
63
64
  }
64
65
 
65
- export async function observeCodex(account: Account, cfg: Config, now: number, opts: { probe: boolean; refresh: boolean }): Promise<Observation | null> {
66
+ export async function observeCodex(account: Account, cfg: Config, now: number, opts: { probe: boolean; refresh: boolean; holder?: string }): Promise<Observation | null> {
66
67
  if (opts.probe && (account.lastUsageAt == null || now - account.lastUsageAt > cfg.policy.usagePollTtlMs)) {
67
- const outcome = await readCodexUsage(account, now, opts.refresh);
68
+ const outcome = await readCodexUsage(account, now, opts.refresh, opts.holder);
68
69
  await withLock(codexPool.lockFile, () => {
69
70
  const idx = loadAccounts(codexPool);
70
71
  const a = idx.accounts.find((x) => x.id === account.id);
@@ -210,10 +211,10 @@ export function codexPickCtx(now: number, currentId: string | null): PickCtx {
210
211
  return { now, thresholds: thresholdBars(loadConfig()), currentId, families: null, seats: null };
211
212
  }
212
213
 
213
- export function pickCodexSeat(now: number): Account | null {
214
+ export function pickCodexSeat(now: number, eligible: (a: Account) => boolean = (a) => codexStoreUsable(a.id)): Account | null {
214
215
  const ctx = codexPickCtx(now, null);
215
216
  const present = seatCounts(codexPaths.presenceDir);
216
- const open = loadAccounts(codexPool).accounts.filter((a) => a.needsReauth !== true && !present.has(a.id) && codexStoreUsable(a.id));
217
+ const open = loadAccounts(codexPool).accounts.filter((a) => a.needsReauth !== true && !present.has(a.id) && eligible(a));
217
218
  return pickBest(open.filter((a) => !isExhausted(a, ctx)), ctx) ?? pickEarliestReset(open, ctx)?.account ?? null;
218
219
  }
219
220
 
@@ -232,7 +233,10 @@ export const codex: Provider = {
232
233
  mergeWindows: (next) => next,
233
234
  swap: prepareMove,
234
235
  classifySwapError: (e) => (e instanceof CodexInvalidGrantError ? "dead-grant" : e instanceof StoreUnusableError ? "skip" : "fatal"),
235
- removeCredentials: async (a) => deleteCodexStoreAuth(a.id),
236
+ removeCredentials: async (a) => {
237
+ deleteCodexStoreAuth(a.id);
238
+ deletePiStore("codex", a.id);
239
+ },
236
240
  storeUsable: async (a) => codexStoreUsable(a.id),
237
241
  login,
238
242
  importLive,
@@ -78,6 +78,14 @@ function decodeJwtPayload(input: { jwt: string }): unknown {
78
78
  return JSON.parse(payload);
79
79
  }
80
80
 
81
+ export function chatgptAccountIdOf(input: { jwt: string }): string | null {
82
+ try {
83
+ return IdClaimsSchema.parse(decodeJwtPayload(input))["https://api.openai.com/auth"]?.chatgpt_account_id ?? null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
81
89
  export type CodexIdentity = { accountId: string; email: string | null; planType: string | null };
82
90
 
83
91
  export function codexIdentityOf(input: { auth: CodexAuthJson }): CodexIdentity {