tokenmaxxing 1.57.0 → 1.58.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.
Files changed (53) hide show
  1. package/DESIGN.md +6 -6
  2. package/README.md +3 -3
  3. package/package.json +1 -1
  4. package/src/cli/auth.ts +6 -17
  5. package/src/cli/check.ts +2 -2
  6. package/src/cli/cloudrun.ts +12 -19
  7. package/src/cli/config.ts +2 -1
  8. package/src/cli/doctor.ts +5 -4
  9. package/src/cli/init.ts +0 -1
  10. package/src/cli/render.ts +24 -9
  11. package/src/cli/rm.ts +0 -4
  12. package/src/cli/status.ts +34 -57
  13. package/src/entries/codexstophook.ts +10 -23
  14. package/src/entries/codexsupervisor.ts +30 -72
  15. package/src/entries/statusline.ts +17 -26
  16. package/src/entries/stopfailurehook.ts +4 -5
  17. package/src/entries/stophook.ts +12 -10
  18. package/src/entries/subagentstatusline.ts +4 -10
  19. package/src/entries/supervisor.ts +51 -103
  20. package/src/lib/claude.ts +11 -12
  21. package/src/lib/claudebin.ts +3 -2
  22. package/src/lib/claudelock.ts +2 -2
  23. package/src/lib/cloud.ts +4 -8
  24. package/src/lib/codex.ts +3 -3
  25. package/src/lib/codexauth.ts +2 -11
  26. package/src/lib/codexbin.ts +2 -1
  27. package/src/lib/codexoauth.ts +3 -8
  28. package/src/lib/codexusage.ts +5 -5
  29. package/src/lib/compact.ts +5 -18
  30. package/src/lib/credstore.ts +1 -6
  31. package/src/lib/decide.ts +6 -24
  32. package/src/lib/grok.ts +38 -152
  33. package/src/lib/http.ts +1 -2
  34. package/src/lib/install.ts +20 -38
  35. package/src/lib/keychain.ts +1 -4
  36. package/src/lib/log.ts +5 -5
  37. package/src/lib/oauth.ts +6 -5
  38. package/src/lib/opencodego.ts +31 -140
  39. package/src/lib/paths.ts +6 -22
  40. package/src/lib/picker.ts +9 -20
  41. package/src/lib/presence.ts +4 -5
  42. package/src/lib/proc.ts +22 -0
  43. package/src/lib/sample.ts +25 -24
  44. package/src/lib/sessions.ts +5 -6
  45. package/src/lib/settings.ts +1 -8
  46. package/src/lib/state.ts +6 -29
  47. package/src/lib/statusonly.ts +143 -0
  48. package/src/lib/supervise.ts +111 -0
  49. package/src/lib/types.ts +27 -36
  50. package/src/lib/update.ts +6 -7
  51. package/src/lib/usage.ts +40 -191
  52. package/src/main.ts +5 -5
  53. package/src/entries/sessionstart.ts +0 -33
@@ -1,17 +1,18 @@
1
1
  import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
- import { codexPaths, codexPool, paths } from "../lib/paths.ts";
4
+ import { codexPaths, codexPool } from "../lib/paths.ts";
5
5
  import { withLock } from "../lib/lock.ts";
6
- import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, UNMANAGED_ENV, WRAP_DEPTH_ENV, WRAP_RATE_MAX, WRAP_RATE_WINDOW_MS, wrapDepth, wrapperEntryRateTripped } from "../lib/claudebin.ts";
6
+ import { UNMANAGED_ENV, WRAP_DEPTH_ENV, wrapDepth } from "../lib/claudebin.ts";
7
7
  import { resolveRealCodex } from "../lib/codexbin.ts";
8
8
  import { ensureCodexStoreHome } from "../lib/codexauth.ts";
9
9
  import { pickCodexSeat } from "../lib/codex.ts";
10
- import { clearPresence, writePresence } from "../lib/presence.ts";
11
- import { saveTermios, restoreTermios } from "../lib/tty.ts";
10
+ import { clearPresence } from "../lib/presence.ts";
11
+ import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
12
+ import { saveTermios } from "../lib/tty.ts";
12
13
  import { loadAccounts } from "../lib/state.ts";
13
14
  import { CodexRespawnMarkerSchema } from "../lib/types.ts";
14
- import { log } from "../lib/log.ts";
15
+ import { errorMessage, log } from "../lib/log.ts";
15
16
 
16
17
  export const CODEX_SUPERVISOR_ID_ENV = "TOKENMAXXING_CODEX_SUPERVISOR_ID";
17
18
 
@@ -27,7 +28,7 @@ function readCodexMarker(marker: string): z.infer<typeof CodexRespawnMarkerSchem
27
28
  try {
28
29
  return CodexRespawnMarkerSchema.parse(JSON.parse(readFileSync(marker, "utf8")));
29
30
  } catch (e) {
30
- log("codexsupervisor.marker_invalid", { err: e instanceof Error ? e.message : String(e) });
31
+ log("codexsupervisor.marker_invalid", { err: errorMessage(e) });
31
32
  throw new Error(
32
33
  `${marker} is corrupt (unparsable JSON or off-schema) - the codex session was stopped instead of resumed on a stale target; inspect and remove the marker, then run \`codex resume\``,
33
34
  );
@@ -54,52 +55,17 @@ export function shouldManageCodex(input: { argv: string[] }): boolean {
54
55
  return firstPositional === null || !NONINTERACTIVE_SUBCMDS.has(firstPositional);
55
56
  }
56
57
 
57
- async function recordCodexPresence(child: { pid: number; exitCode: number | null; signalCode: string | null; kill: () => void; exited: Promise<unknown> }, supervisorId: string, seat: { id: string }, savedTermios: string | null): Promise<void> {
58
- for (let attempt = 0; ; attempt++) {
59
- try {
60
- writePresence({ dir: codexPaths.presenceDir, id: supervisorId, accountId: seat.id, pid: child.pid });
61
- return;
62
- } catch (e) {
63
- if (child.exitCode !== null || child.signalCode !== null) return;
64
- if (attempt >= 9) {
65
- log("codexsupervisor.presence_failed", { err: e instanceof Error ? e.message : String(e) });
66
- child.kill();
67
- await child.exited;
68
- restoreTermios(savedTermios);
69
- throw new Error("could not write the codex presence file - refusing to run an unprotected session (its account would look like a swap target)");
70
- }
71
- await Bun.sleep(100);
72
- }
73
- }
74
- }
75
-
76
58
  export async function runCodexSupervisor(input: { argv: string[] }): Promise<number> {
77
59
  const { argv } = input;
78
- const depth = wrapDepth();
79
- if (depth >= MAX_WRAP_DEPTH) {
80
- console.error(
81
- `tokenmaxxing: ${LOOP_DIAGNOSIS} (depth ${depth}) - codexBin in ${paths.configJson} does not launch the real codex binary. Fix codexBin, then run \`tokenmaxxing doctor\`.`,
82
- );
83
- log("codexsupervisor.loop_abort", { depth });
84
- return 1;
85
- }
86
- if (wrapperEntryRateTripped(Date.now())) {
87
- console.error(
88
- `tokenmaxxing: ${LOOP_DIAGNOSIS} (over ${WRAP_RATE_MAX} wrapper entries in ${WRAP_RATE_WINDOW_MS / 1000}s) - codexBin in ${paths.configJson} does not launch the real codex binary. Fix codexBin, then run \`tokenmaxxing doctor\`.`,
89
- );
90
- log("codexsupervisor.rate_abort", { max: WRAP_RATE_MAX });
91
- return 1;
92
- }
60
+ if (loopGuardTripped("codex")) return 1;
93
61
 
94
62
  const real = resolveRealCodex();
95
- const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(depth + 1) };
63
+ const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
96
64
 
97
65
  if (!shouldManageCodex({ argv }) || process.env[UNMANAGED_ENV]) {
98
66
  const passthroughEnv: Record<string, string | undefined> = { ...childEnv };
99
67
  delete passthroughEnv[CODEX_SUPERVISOR_ID_ENV];
100
- const p = Bun.spawn([real, ...argv], { stdin: "inherit", stdout: "inherit", stderr: "inherit", env: passthroughEnv });
101
- await p.exited;
102
- return p.exitCode ?? (p.signalCode ? 1 : 0);
68
+ return runPassthrough({ real, argv, env: passthroughEnv });
103
69
  }
104
70
 
105
71
  const supervisorId = crypto.randomUUID();
@@ -131,37 +97,29 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
131
97
  ...(store ? { CODEX_HOME: store } : {}),
132
98
  },
133
99
  });
134
- if (picked) await recordCodexPresence(spawned, supervisorId, picked, savedTermios);
100
+ if (picked) {
101
+ await recordPresenceOrStop({
102
+ child: spawned,
103
+ dir: codexPaths.presenceDir,
104
+ id: supervisorId,
105
+ accountId: picked.id,
106
+ event: "codexsupervisor.presence_failed",
107
+ message: "could not write the codex presence file - refusing to run an unprotected session (its account would look like a swap target)",
108
+ savedTermios,
109
+ });
110
+ }
135
111
  return { child: spawned, seat: picked };
136
112
  });
137
113
 
138
- let done = false;
139
- const markerWatch = (async () => {
140
- while (!done) {
141
- if (existsSync(marker)) {
142
- readCodexMarker(marker);
143
- return true;
144
- }
145
- await Bun.sleep(150);
146
- }
147
- return false;
148
- })();
149
- const exited = child.exited.then(() => {
150
- done = true;
151
- return "exit";
114
+ await raceMarkerOrExit({
115
+ child,
116
+ tick: () => {
117
+ if (!existsSync(marker)) return false;
118
+ readCodexMarker(marker);
119
+ return true;
120
+ },
121
+ savedTermios,
152
122
  });
153
- try {
154
- const winner = await Promise.race([exited, markerWatch.then((found) => (found ? "marker" : "exit"))]);
155
- if (winner === "marker") child.kill();
156
- } catch (e) {
157
- child.kill();
158
- throw e;
159
- } finally {
160
- await child.exited;
161
- done = true;
162
- await markerWatch.catch(() => false);
163
- restoreTermios(savedTermios);
164
- }
165
123
 
166
124
  const payload = existsSync(marker) ? readCodexMarker(marker) : null;
167
125
  if (payload) {
@@ -175,6 +133,6 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
175
133
  }
176
134
  clearPresence({ dir: codexPaths.presenceDir, id: supervisorId });
177
135
  log("codexsupervisor.exit", { supervisorId: supervisorId.slice(0, 8), respawns, code: child.exitCode, signal: child.signalCode });
178
- return child.exitCode ?? (child.signalCode ? 1 : 0);
136
+ return exitStatus(child);
179
137
  }
180
138
  }
@@ -1,39 +1,32 @@
1
1
  import { sortBy } from "es-toolkit";
2
- import { z } from "zod";
3
2
  import { claudePool, seatFromEnv } from "../lib/paths.ts";
3
+ import { errorMessage } from "../lib/log.ts";
4
4
  import { loadAccounts, loadConfig, writeUsage } from "../lib/state.ts";
5
5
  import { familyTokens, matchedFamily, parseStatusLineStdin, parseStatusLineModel } from "../lib/usage.ts";
6
6
  import { earliestReset, limitWindows, weeklyExpiry, weeklyWindow } from "../lib/picker.ts";
7
+ import { readStdin } from "../lib/proc.ts";
7
8
  import { worktreeName } from "../lib/worktree.ts";
8
- import { makeColors, makeUsagePaint } from "../cli/render.ts";
9
- import { fmtResetShort } from "../lib/usage.ts";
9
+ import { fmtResetShort, makeColors, makeUsagePaint, statuslineColor } from "../cli/render.ts";
10
10
  import {
11
- AccountsIndexSchema,
12
11
  JsonTextSchema,
13
12
  StatusLineStdinSchema,
14
- WindowSchema,
15
13
  type Account,
14
+ type AccountsIndex,
16
15
  type UsageState,
17
16
  type UsageWindow,
17
+ type Window,
18
18
  } from "../lib/types.ts";
19
19
 
20
- const RenderCtxSchema = z.object({
21
- accounts: AccountsIndexSchema,
22
- perModel: z.record(z.string(), WindowSchema),
23
- switchModels: z.array(z.string()),
24
- worktree: z.string().nullable(),
25
- liveAccount: z.string().nullable(),
26
- now: z.number(),
27
- color: z.boolean(),
28
- truecolor: z.boolean(),
29
- });
30
- export type RenderCtx = z.infer<typeof RenderCtxSchema>;
31
-
32
- export async function readStdin(): Promise<string> {
33
- const chunks: Uint8Array[] = [];
34
- for await (const c of Bun.stdin.stream()) chunks.push(c);
35
- return Buffer.concat(chunks).toString("utf8");
36
- }
20
+ export type RenderCtx = {
21
+ accounts: AccountsIndex;
22
+ perModel: Record<string, Window>;
23
+ switchModels: string[];
24
+ worktree: string | null;
25
+ liveAccount: string | null;
26
+ now: number;
27
+ color: boolean;
28
+ truecolor: boolean;
29
+ };
37
30
 
38
31
  export function renderStatusline(stdinObj: unknown, ctx: RenderCtx): string {
39
32
  const col = makeColors(ctx.color);
@@ -134,7 +127,6 @@ export async function runStatusline(): Promise<number> {
134
127
  const accounts = loadAccounts(claudePool);
135
128
  const stdin = StatusLineStdinSchema.safeParse(obj);
136
129
  const dir = stdin.success ? (stdin.data.workspace?.current_dir ?? stdin.data.workspace?.project_dir ?? null) : null;
137
- const colorterm = z.string().optional().parse(process.env.COLORTERM);
138
130
  const live = accounts.accounts.find((a) => a.id === account);
139
131
  const ctx: RenderCtx = {
140
132
  accounts,
@@ -143,12 +135,11 @@ export async function runStatusline(): Promise<number> {
143
135
  worktree: dir == null ? null : worktreeName(dir),
144
136
  liveAccount: account,
145
137
  now,
146
- color: !process.env.NO_COLOR,
147
- truecolor: colorterm != null && (colorterm.includes("truecolor") || colorterm.includes("24bit")),
138
+ ...statuslineColor(),
148
139
  };
149
140
  line = renderStatusline(obj, ctx);
150
141
  } catch (e) {
151
- line = `tokenmaxxing: ${e instanceof Error ? e.message : String(e)}`;
142
+ line = `tokenmaxxing: ${errorMessage(e)}`;
152
143
  }
153
144
  process.stdout.write(line + "\n");
154
145
  return 0;
@@ -1,13 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { claude } from "../lib/claude.ts";
3
3
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
+ import { readStdin } from "../lib/proc.ts";
4
5
  import { supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
5
6
  import { loadConfig } from "../lib/state.ts";
6
7
  import { classifyEnforcedLimit, findEnforcedRow, parseErrorBody, readTranscriptTail, type TranscriptRow } from "../lib/usage.ts";
7
8
  import { paths } from "../lib/paths.ts";
8
9
  import { JsonTextSchema, type EnforcedLimit } from "../lib/types.ts";
9
- import { log } from "../lib/log.ts";
10
- import { readStdin } from "./statusline.ts";
10
+ import { errorMessage, log } from "../lib/log.ts";
11
11
 
12
12
  const StopFailureStdin = z.looseObject({
13
13
  session_id: z.uuid().optional().catch(undefined),
@@ -34,8 +34,7 @@ export async function runStopFailureHook(): Promise<number> {
34
34
 
35
35
  const account = claude.liveId();
36
36
  const now = Date.now();
37
- const raw = await readStdin();
38
- const parsed = StopFailureStdin.safeParse(JsonTextSchema.safeParse(raw).data);
37
+ const parsed = StopFailureStdin.safeParse(JsonTextSchema.safeParse(await readStdin()).data);
39
38
  const stdin = parsed.success ? parsed.data : {};
40
39
  if (stdin.error !== undefined && stdin.error !== "rate_limit") return 0;
41
40
 
@@ -84,7 +83,7 @@ export async function runStopFailureHook(): Promise<number> {
84
83
  log("stopfailure.decision", { reason: decision.reason, swapped: decision.swapped, account: decision.account?.id.slice(0, 8), waitUntil: decision.waitUntil });
85
84
  }
86
85
  } catch (e) {
87
- log("stopfailure.error", { err: e instanceof Error ? e.message : String(e) });
86
+ log("stopfailure.error", { err: errorMessage(e) });
88
87
  }
89
88
  return 0;
90
89
  }
@@ -1,29 +1,31 @@
1
1
  import { z } from "zod";
2
2
  import { claude } from "../lib/claude.ts";
3
3
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
+ import { readStdin } from "../lib/proc.ts";
4
5
  import { supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
5
6
  import { JsonTextSchema } from "../lib/types.ts";
6
- import { log } from "../lib/log.ts";
7
- import { readStdin } from "./statusline.ts";
7
+ import { errorMessage, log } from "../lib/log.ts";
8
8
 
9
- const StopStdin = z.looseObject({ session_id: z.uuid().optional().catch(undefined) });
9
+ const BoundaryStdin = z.looseObject({
10
+ source: z.string().optional(),
11
+ session_id: z.uuid().optional().catch(undefined),
12
+ });
10
13
 
11
- export async function runStopHook(): Promise<number> {
14
+ export async function runBoundaryHook(event: "stop" | "sessionstart"): Promise<number> {
12
15
  if (process.env.TOKENMAXXING_PROBE) return 0;
13
16
 
14
- const raw = await readStdin();
15
- const parsed = StopStdin.safeParse(JsonTextSchema.safeParse(raw).data);
16
- const stdinSid = parsed.success ? parsed.data.session_id : undefined;
17
+ const parsed = BoundaryStdin.safeParse(JsonTextSchema.safeParse(await readStdin()).data);
18
+ const stdin = parsed.success ? parsed.data : {};
17
19
  const session = supervisedSession();
18
20
 
19
21
  try {
20
22
  const decision = await evaluateAndMaybeSwap(claude, Date.now(), session != null);
21
23
  if (session && decision.account && (decision.swapped || decision.waitUntil !== undefined)) {
22
- writeRespawnMarker({ session, sessionId: stdinSid ?? session.sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? Date.now(), compact: true });
23
- log(decision.waitUntil !== undefined ? "stop.wait" : "stop.move", { account: decision.account.id.slice(0, 8), waitUntil: decision.waitUntil, session: session.sid.slice(0, 8) });
24
+ writeRespawnMarker({ session, sessionId: stdin.session_id ?? session.sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? Date.now(), compact: true });
25
+ log(`${event}.${decision.waitUntil !== undefined ? "wait" : "move"}`, { source: stdin.source, account: decision.account.id.slice(0, 8), waitUntil: decision.waitUntil, session: session.sid.slice(0, 8) });
24
26
  }
25
27
  } catch (e) {
26
- log("stop.error", { err: e instanceof Error ? e.message : String(e) });
28
+ log(`${event}.error`, { err: errorMessage(e) });
27
29
  }
28
30
  return 0;
29
31
  }
@@ -1,10 +1,8 @@
1
- import { z } from "zod";
2
- import { makeColors, makeUsagePaint } from "../cli/render.ts";
3
- import { readStdin } from "./statusline.ts";
1
+ import { makeColors, makeUsagePaint, statuslineColor } from "../cli/render.ts";
2
+ import { readStdin } from "../lib/proc.ts";
4
3
  import { JsonTextSchema, SubagentStatusLineStdinSchema } from "../lib/types.ts";
5
4
 
6
- const RowCtxSchema = z.object({ color: z.boolean(), truecolor: z.boolean() });
7
- export type RowCtx = z.infer<typeof RowCtxSchema>;
5
+ export type RowCtx = { color: boolean; truecolor: boolean };
8
6
 
9
7
  function modelFamily(id: string): string {
10
8
  const [head, family] = id.split("-");
@@ -44,11 +42,7 @@ export function renderSubagentRows(stdinObj: unknown, ctx: RowCtx): string[] {
44
42
 
45
43
  export async function runSubagentStatusline(): Promise<number> {
46
44
  const obj = JsonTextSchema.safeParse(await readStdin()).data ?? null;
47
- const colorterm = z.string().optional().parse(process.env.COLORTERM);
48
- const rows = renderSubagentRows(obj, {
49
- color: !process.env.NO_COLOR,
50
- truecolor: colorterm != null && (colorterm.includes("truecolor") || colorterm.includes("24bit")),
51
- });
45
+ const rows = renderSubagentRows(obj, statuslineColor());
52
46
  if (rows.length > 0) process.stdout.write(rows.join("\n") + "\n");
53
47
  return 0;
54
48
  }
@@ -4,19 +4,21 @@ import type { FileSink, Subprocess } from "bun";
4
4
  import { maxBy } from "es-toolkit";
5
5
  import { z } from "zod";
6
6
  import { claudePool, paths, storeDirFor } from "../lib/paths.ts";
7
- import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, UNMANAGED_ENV, WRAP_DEPTH_ENV, WRAP_RATE_MAX, WRAP_RATE_WINDOW_MS, resolveRealClaude, wrapDepth, wrapperEntryRateTripped } from "../lib/claudebin.ts";
7
+ import { UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealClaude, wrapDepth } from "../lib/claudebin.ts";
8
8
  import { claude, pickSeat } from "../lib/claude.ts";
9
9
  import { compactClaudeSession } from "../lib/compact.ts";
10
10
  import { evaluateAndMaybeSwap } from "../lib/decide.ts";
11
11
  import { withLock } from "../lib/lock.ts";
12
12
  import { thresholdBars, usableAt } from "../lib/picker.ts";
13
- import { clearPresence, writePresence } from "../lib/presence.ts";
13
+ import { clearPresence } from "../lib/presence.ts";
14
+ import { readLines } from "../lib/proc.ts";
14
15
  import { teeObservation } from "../lib/sample.ts";
15
- import { saveTermios, restoreTermios } from "../lib/tty.ts";
16
+ import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
17
+ import { saveTermios } from "../lib/tty.ts";
16
18
  import { loadSessionFlags, pruneStaleSessions, saveSessionFlags, writeRespawnMarker } from "../lib/sessions.ts";
17
19
  import { loadAccounts, loadConfig } from "../lib/state.ts";
18
20
  import { RespawnMarkerSchema, type Account } from "../lib/types.ts";
19
- import { log } from "../lib/log.ts";
21
+ import { errorMessage, log } from "../lib/log.ts";
20
22
 
21
23
  const NONINTERACTIVE_SUBCMDS = new Set([
22
24
  "mcp", "config", "doctor", "update", "install", "migrate-installer",
@@ -44,15 +46,14 @@ const OPTIONAL_VALUE_ROOT_FLAGS = new Set([
44
46
 
45
47
  const isUuid = (s: string) => z.uuid().safeParse(s).success;
46
48
 
47
- const AnalysisSchema = z.object({
48
- manage: z.boolean(),
49
- sessionId: z.string().nullable(),
50
- resumeId: z.string().nullable(),
51
- continueLatest: z.boolean(),
52
- streamInput: z.boolean(),
53
- streamOutput: z.boolean(),
54
- });
55
- type Analysis = z.infer<typeof AnalysisSchema>;
49
+ type Analysis = {
50
+ manage: boolean;
51
+ sessionId: string | null;
52
+ resumeId: string | null;
53
+ continueLatest: boolean;
54
+ streamInput: boolean;
55
+ streamOutput: boolean;
56
+ };
56
57
 
57
58
  export function analyzeArgs(argv: string[]): Analysis {
58
59
  let sessionId: string | null = null;
@@ -169,18 +170,17 @@ function latestSessionForCwd(): string | null {
169
170
  }
170
171
  }
171
172
 
172
- const MarkerGateSchema = z.object({
173
- launchedAt: z.number(),
174
- overriddenUntil: z.number(),
175
- });
176
- type MarkerGate = z.infer<typeof MarkerGateSchema>;
173
+ type MarkerGate = {
174
+ launchedAt: number;
175
+ overriddenUntil: number;
176
+ };
177
177
 
178
178
  function consumableMarker(marker: string, gate: MarkerGate): z.infer<typeof RespawnMarkerSchema> | null {
179
179
  let m: z.infer<typeof RespawnMarkerSchema>;
180
180
  try {
181
181
  m = RespawnMarkerSchema.parse(JSON.parse(readFileSync(marker, "utf8")));
182
182
  } catch (e) {
183
- log("supervisor.marker_invalid", { err: e instanceof Error ? e.message : String(e) });
183
+ log("supervisor.marker_invalid", { err: errorMessage(e) });
184
184
  throw new Error(
185
185
  `${marker} is corrupt (unparsable JSON or off-schema) - the session was stopped instead of guessing whether the pool is depleted; inspect the marker, then run \`claude --resume ${basename(marker)}\` (a fresh launch clears it)`,
186
186
  );
@@ -222,7 +222,7 @@ async function moveExhaustedSeat(seat: Account, sid: string, gate: MarkerGate):
222
222
  writeRespawnMarker({ session: { sid, launchedAt: gate.launchedAt }, sessionId: sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? now, compact: true });
223
223
  }
224
224
  } catch (e) {
225
- log("supervisor.seat_watch_error", { err: e instanceof Error ? e.message : String(e) });
225
+ log("supervisor.seat_watch_error", { err: errorMessage(e) });
226
226
  }
227
227
  return true;
228
228
  }
@@ -276,7 +276,7 @@ class StdinRelay {
276
276
 
277
277
  constructor() {
278
278
  this.pump()
279
- .catch((e: unknown) => log("supervisor.relay_read_failed", { err: e instanceof Error ? e.message : String(e) }))
279
+ .catch((e: unknown) => log("supervisor.relay_read_failed", { err: errorMessage(e) }))
280
280
  .finally(() => {
281
281
  this.ended = true;
282
282
  this.close();
@@ -296,19 +296,7 @@ class StdinRelay {
296
296
  }
297
297
 
298
298
  private async pump(): Promise<void> {
299
- const decoder = new TextDecoder();
300
- let pending = "";
301
- for await (const chunk of Bun.stdin.stream()) {
302
- pending += decoder.decode(chunk, { stream: true });
303
- let nl = pending.indexOf("\n");
304
- while (nl !== -1) {
305
- this.forward(pending.slice(0, nl + 1));
306
- pending = pending.slice(nl + 1);
307
- nl = pending.indexOf("\n");
308
- }
309
- }
310
- pending += decoder.decode();
311
- if (pending.length > 0) this.forward(`${pending}\n`);
299
+ for await (const line of readLines(Bun.stdin.stream())) this.forward(`${line}\n`);
312
300
  }
313
301
 
314
302
  private forward(line: string): void {
@@ -318,7 +306,7 @@ class StdinRelay {
318
306
  this.sink.flush();
319
307
  return;
320
308
  } catch (e) {
321
- log("supervisor.relay_write_failed", { err: e instanceof Error ? e.message : String(e) });
309
+ log("supervisor.relay_write_failed", { err: errorMessage(e) });
322
310
  this.sink = null;
323
311
  }
324
312
  }
@@ -330,50 +318,17 @@ class StdinRelay {
330
318
  try {
331
319
  this.sink.end();
332
320
  } catch (e) {
333
- log("supervisor.relay_end_failed", { err: e instanceof Error ? e.message : String(e) });
321
+ log("supervisor.relay_end_failed", { err: errorMessage(e) });
334
322
  }
335
323
  this.sink = null;
336
324
  }
337
325
  }
338
326
 
339
- async function recordPresence(child: Subprocess, sid: string, seat: Account, savedTermios: string | null): Promise<void> {
340
- for (let attempt = 0; ; attempt++) {
341
- try {
342
- writePresence({ dir: paths.presenceDir, id: sid, accountId: seat.id, pid: child.pid });
343
- return;
344
- } catch (e) {
345
- if (child.exitCode !== null || child.signalCode !== null) return;
346
- if (attempt >= 9) {
347
- log("supervisor.presence_failed", { err: e instanceof Error ? e.message : String(e) });
348
- child.kill();
349
- await child.exited;
350
- restoreTermios(savedTermios);
351
- throw new Error("could not write the session presence file - refusing to run a session whose seat placement cannot see");
352
- }
353
- await Bun.sleep(100);
354
- }
355
- }
356
- }
357
-
358
327
  export async function runSupervisor(argv: string[]): Promise<number> {
359
- const depth = wrapDepth();
360
- if (depth >= MAX_WRAP_DEPTH) {
361
- console.error(
362
- `tokenmaxxing: ${LOOP_DIAGNOSIS} (depth ${depth}) - claudeBin in ${paths.configJson} does not launch the real Claude binary. Fix claudeBin, then run \`tokenmaxxing doctor\`.`,
363
- );
364
- log("supervisor.loop_abort", { depth });
365
- return 1;
366
- }
367
- if (wrapperEntryRateTripped(Date.now())) {
368
- console.error(
369
- `tokenmaxxing: ${LOOP_DIAGNOSIS} (over ${WRAP_RATE_MAX} wrapper entries in ${WRAP_RATE_WINDOW_MS / 1000}s) - claudeBin in ${paths.configJson} does not launch the real Claude binary. Fix claudeBin, then run \`tokenmaxxing doctor\`.`,
370
- );
371
- log("supervisor.rate_abort", { max: WRAP_RATE_MAX });
372
- return 1;
373
- }
328
+ if (loopGuardTripped("claude")) return 1;
374
329
  const real = resolveRealClaude();
375
330
  const info = analyzeArgs(argv);
376
- const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(depth + 1) };
331
+ const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(wrapDepth() + 1) };
377
332
 
378
333
  let child: Subprocess | null = null;
379
334
  let terminating = false;
@@ -387,10 +342,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
387
342
  const passthroughEnv: Record<string, string | undefined> = { ...childEnv };
388
343
  delete passthroughEnv.TOKENMAXXING_SUPERVISED;
389
344
  delete passthroughEnv.TOKENMAXXING_SESSION_ID;
390
- const p = Bun.spawn([real, ...argv], { stdin: "inherit", stdout: "inherit", stderr: "inherit", env: passthroughEnv });
391
- child = p;
392
- await p.exited;
393
- return p.exitCode ?? (p.signalCode ? 1 : 0);
345
+ return runPassthrough({ real, argv, env: passthroughEnv, onSpawn: (p) => { child = p; } });
394
346
  }
395
347
 
396
348
  let base = stripSessionFlags(argv);
@@ -458,44 +410,40 @@ export async function runSupervisor(argv: string[]): Promise<number> {
458
410
  });
459
411
  if (relay !== null) relay.attach(spawned.stdin!, firstLine);
460
412
  firstLine = null;
461
- if (picked) await recordPresence(spawned, sid, picked, savedTermios);
413
+ if (picked) {
414
+ await recordPresenceOrStop({
415
+ child: spawned,
416
+ dir: paths.presenceDir,
417
+ id: sid,
418
+ accountId: picked.id,
419
+ event: "supervisor.presence_failed",
420
+ message: "could not write the session presence file - refusing to run a session whose seat placement cannot see",
421
+ savedTermios,
422
+ });
423
+ }
462
424
  return { child: spawned, seat: picked };
463
425
  });
464
426
 
465
427
  child = proc;
466
- let done = false;
467
428
  let seatCheckAt = 0;
468
- const markerWatch = (async () => {
469
- while (!done) {
429
+ await raceMarkerOrExit({
430
+ child: proc,
431
+ tick: async () => {
470
432
  if (existsSync(marker) && consumableMarker(marker, gate) != null) return true;
471
433
  if (seat && !terminating && Date.now() >= seatCheckAt) {
472
434
  const decided = await moveExhaustedSeat(seat, sid, gate);
473
435
  seatCheckAt = Date.now() + (decided ? SEAT_RETRY_MS : SEAT_POLL_MS);
474
436
  }
475
- await Bun.sleep(150);
476
- }
477
- return false;
478
- })();
479
- const exited = proc.exited.then(() => { done = true; return "exit" as const; });
480
- try {
481
- const winner = await Promise.race([exited, markerWatch.then((m) => (m ? "marker" : "exit"))]);
482
- if (winner === "marker") {
437
+ return false;
438
+ },
439
+ savedTermios,
440
+ onKill: () => relay?.detach(),
441
+ onError: () => clearPresence({ dir: paths.presenceDir, id: sid }),
442
+ onExited: () => {
443
+ child = null;
483
444
  relay?.detach();
484
- proc.kill();
485
- }
486
- } catch (e) {
487
- relay?.detach();
488
- proc.kill();
489
- clearPresence({ dir: paths.presenceDir, id: sid });
490
- throw e;
491
- } finally {
492
- await proc.exited;
493
- child = null;
494
- relay?.detach();
495
- done = true;
496
- await markerWatch.catch(() => {});
497
- restoreTermios(savedTermios);
498
- }
445
+ },
446
+ });
499
447
 
500
448
  const m = !terminating && existsSync(marker) ? consumableMarker(marker, gate) : null;
501
449
  if (m) {
@@ -528,6 +476,6 @@ export async function runSupervisor(argv: string[]): Promise<number> {
528
476
  }
529
477
  clearPresence({ dir: paths.presenceDir, id: sid });
530
478
  log("supervisor.exit", { sid, respawns, code: proc.exitCode, signal: proc.signalCode, terminated: terminating || undefined });
531
- return proc.exitCode ?? (proc.signalCode ? 1 : 0);
479
+ return exitStatus(proc);
532
480
  }
533
481
  }