tokenmaxxing 1.161.0 → 1.163.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/DESIGN.md CHANGED
@@ -66,7 +66,7 @@ The supervisor sees `respawn/<sid>`, SIGTERMs its child at the already-committed
66
66
 
67
67
  ### 3.4 Placement (supervisor, under the pool lock)
68
68
  1. Fold every account's tee into the index.
69
- 2. Rank the usable accounts (§5) with the live session counts from `live/`, or the soonest-recovering account when none is usable; with no pooled account or every account needing re-auth, launch without a store (still supervised, on whatever login the environment names). Print mode, every subcommand, the bare resume picker, an invalid `--session-id`, `--fork-session` with a resume, and `--no-session-persistence` (no transcript, so a move could not resume the run) are passed through unmanaged.
69
+ 2. Rank the usable accounts (§5) with the live session counts from `live/`, or the soonest-recovering account when none is usable; with no pooled account or every account needing re-auth, launch without a store (still supervised, on whatever login the environment names). Print mode, every subcommand, the bare resume picker, an invalid `--session-id`, `--fork-session` with a resume, and `--no-session-persistence` (no transcript, so a move could not resume the run) are passed through unmanaged. One exception: an invocation that carries T3 Code's capability probe shape (the stream-json flag set with `--strict-mcp-config`, `--setting-sources=user,project,local`, `--settings {"disableAllHooks":true}`, `--no-session-persistence`, and the probe environment `ENABLE_CLAUDEAI_MCP_SERVERS=false`, `CLAUDE_CODE_AUTO_CONNECT_IDE=0`, and `CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL=1`) is placed on the account placement picks, or the soonest-recovering account when none is usable, and its `get_usage` control request is answered from that account's sampled windows instead of reaching the child, so the probe sends no usage read. It writes no presence file and no session flags, and no other `--no-session-persistence` run is seated.
70
70
  3. Spawn claude with the store env set, then write `live/<sid>` with the child's pid and start time. The lock covers the pick and the presence write, so concurrent launches see each other.
71
71
 
72
72
  ### 3.5 Multiple concurrent sessions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "1.161.0",
3
+ "version": "1.163.0",
4
4
  "description": "Automatic Claude Code account switching: pool multiple accounts, start each session on its own account, and resume a session on a fresher account when quota fills.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -6,11 +6,11 @@ import { z } from "zod";
6
6
  import { claudePool, paths, storeDirFor } from "../lib/paths.ts";
7
7
  import { CLAUDE_BIN, UNMANAGED_ENV, WRAP_DEPTH_ENV, resolveRealBin, wrapDepth } from "../lib/claudebin.ts";
8
8
  import { apiKeyEnv, clearSessionCost, foldCompactionCost, foldSessionCost, loadApiKeys, readApiKey, sendApiKey, usableApiKey, writeSessionCost } from "../lib/apikeys.ts";
9
- import { claude, pickLaunch, type LaunchTarget } from "../lib/claude.ts";
9
+ import { claude, pickLaunch, pickSeat, type LaunchTarget } from "../lib/claude.ts";
10
10
  import { compactClaudeSession } from "../lib/compact.ts";
11
11
  import { evaluateAndMaybeSwap, moveTarget } from "../lib/decide.ts";
12
12
  import { withLock } from "../lib/lock.ts";
13
- import { thresholdBars, usableAt } from "../lib/picker.ts";
13
+ import { limitWindows, liveUsed, sessionWindow, thresholdBars, usableAt, weeklyWindow } from "../lib/picker.ts";
14
14
  import { clearPresence } from "../lib/presence.ts";
15
15
  import { readLines } from "../lib/proc.ts";
16
16
  import { teeObservation } from "../lib/sample.ts";
@@ -19,7 +19,7 @@ import { saveTermios } from "../lib/tty.ts";
19
19
  import { liveSessionId, loadSessionFlags, pruneStaleSessions, saveSessionFlags, writeRespawnMarker } from "../lib/sessions.ts";
20
20
  import { loadAccounts, loadConfig, readJsonFile, releaseWaitClaim, replaceWaitClaim, writeUsage } from "../lib/state.ts";
21
21
  import { gatedFamilies, modelFromFlag, parseStreamLine, scrubCredEnv } from "../lib/usage.ts";
22
- import { CostLineSchema, CostStateRowSchema, JsonTextSchema, RespawnMarkerSchema, type Account, type ApiKey, type Config, type ModelInfo, type SessionCost, type UsageWindows } from "../lib/types.ts";
22
+ import { CostLineSchema, CostStateRowSchema, JsonTextSchema, RespawnMarkerSchema, type Account, type ApiKey, type Config, type ModelInfo, type SessionCost, type UsageWindows, type Window } from "../lib/types.ts";
23
23
  import { errorMessage, log } from "../lib/log.ts";
24
24
 
25
25
  const SUBCOMMANDS = new Set([
@@ -128,6 +128,37 @@ export function analyzeArgs(argv: string[]): Analysis {
128
128
  return { manage, sessionId, resumeId, continueLatest, streamInput, streamOutput, model };
129
129
  }
130
130
 
131
+ const PROBE_VALUE_FLAGS = new Set(["--output-format", "--input-format", "--setting-sources", "--settings", "--permission-mode", "--debug-file"]);
132
+ const PROBE_BOOLEAN_FLAGS = new Set(["--verbose", "--strict-mcp-config", "--no-session-persistence"]);
133
+ const ProbeSettingsSchema = z.strictObject({ disableAllHooks: z.literal(true) });
134
+
135
+ export function isCapabilitiesProbe(argv: string[], envLike: Record<string, string | undefined>): boolean {
136
+ if (envLike.ENABLE_CLAUDEAI_MCP_SERVERS !== "false" || envLike.CLAUDE_CODE_AUTO_CONNECT_IDE !== "0" || envLike.CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL !== "1") return false;
137
+ const values = new Map<string, string>();
138
+ const booleans = new Set<string>();
139
+ for (let i = 0; i < argv.length; i++) {
140
+ const a = argv[i]!;
141
+ const eq = a.indexOf("=");
142
+ const flag = eq > 0 ? a.slice(0, eq) : a;
143
+ if (PROBE_VALUE_FLAGS.has(flag)) {
144
+ if (values.has(flag)) return false;
145
+ const value = eq > 0 ? a.slice(eq + 1) : argv[++i];
146
+ if (value === undefined) return false;
147
+ values.set(flag, value);
148
+ } else if (eq === -1 && PROBE_BOOLEAN_FLAGS.has(a)) {
149
+ if (booleans.has(a)) return false;
150
+ booleans.add(a);
151
+ } else return false;
152
+ }
153
+ if (!booleans.has("--verbose") || !booleans.has("--strict-mcp-config") || !booleans.has("--no-session-persistence")) return false;
154
+ if (values.get("--output-format") !== "stream-json" || values.get("--input-format") !== "stream-json") return false;
155
+ if (values.get("--setting-sources") !== "user,project,local") return false;
156
+ const permissionMode = values.get("--permission-mode");
157
+ if (permissionMode !== undefined && permissionMode !== "default") return false;
158
+ const settings = values.get("--settings");
159
+ return settings !== undefined && ProbeSettingsSchema.safeParse(JsonTextSchema.safeParse(settings).data).success;
160
+ }
161
+
131
162
  export function stripSessionFlags(argv: string[]): string[] {
132
163
  const out: string[] = [];
133
164
  for (let i = 0; i < argv.length; i++) {
@@ -478,6 +509,74 @@ class StdinRelay {
478
509
  }
479
510
  }
480
511
 
512
+ const ProbeCancelLineSchema = z.looseObject({ type: z.literal("control_cancel_request") });
513
+
514
+ function probeUsageLine(requestId: string, account: Account): string {
515
+ const now = Date.now();
516
+ const observed = teeObservation(account);
517
+ const current = observed ? { ...account, windows: observed.windows } : account;
518
+ const win = (w: Window | undefined) => (w == null ? null : { utilization: liveUsed(w, now), resets_at: w.resetsAt != null && w.resetsAt > now ? new Date(w.resetsAt).toISOString() : null });
519
+ const fiveHour = win(sessionWindow(current));
520
+ const sevenDay = win(weeklyWindow(current));
521
+ const scoped = limitWindows(current).map((w) => ({ display_name: w.name, utilization: liveUsed(w, now), resets_at: w.resetsAt != null && w.resetsAt > now ? new Date(w.resetsAt).toISOString() : null }));
522
+ const available = fiveHour != null || sevenDay != null;
523
+ return `${JSON.stringify({
524
+ type: "control_response",
525
+ response: {
526
+ subtype: "success",
527
+ request_id: requestId,
528
+ response: {
529
+ session: { total_cost_usd: 0, total_api_duration_ms: 0, total_duration_ms: 0, total_lines_added: 0, total_lines_removed: 0, model_usage: {} },
530
+ subscription_type: null,
531
+ rate_limits_available: available,
532
+ rate_limits: available ? { five_hour: fiveHour, seven_day: sevenDay, ...(scoped.length > 0 ? { model_scoped: scoped } : {}) } : null,
533
+ behaviors: null,
534
+ },
535
+ },
536
+ })}\n`;
537
+ }
538
+
539
+ async function runCapabilitiesProbe(input: { real: string; argv: string[]; env: Record<string, string | undefined>; seat: Account; onSpawn: (child: Subprocess) => void }): Promise<number> {
540
+ const proc = Bun.spawn([input.real, ...input.argv], { stdin: "pipe", stdout: "pipe", stderr: "inherit", env: input.env });
541
+ input.onSpawn(proc);
542
+ log("supervisor.probe_launch", { seat: input.seat.id.slice(0, 8) });
543
+ const stdoutPump = (async () => {
544
+ for await (const line of readLines(proc.stdout as ReadableStream<Uint8Array>)) process.stdout.write(`${line}\n`);
545
+ })().catch((e: unknown) => log("supervisor.probe_stdout_failed", { err: errorMessage(e) }));
546
+ void (async () => {
547
+ for await (const line of readLines(Bun.stdin.stream())) {
548
+ const parsed = RelayLineSchema.safeParse(JsonTextSchema.safeParse(line).data);
549
+ const request = parsed.success && parsed.data.type === "control_request" ? parsed.data : null;
550
+ if (request?.request.subtype === "get_usage") {
551
+ process.stdout.write(probeUsageLine(request.request_id, input.seat));
552
+ log("supervisor.probe_usage", { seat: input.seat.id.slice(0, 8) });
553
+ continue;
554
+ }
555
+ const forwarded = (parsed.success && parsed.data.type === "control_response") || request?.request.subtype === "initialize" || ProbeCancelLineSchema.safeParse(JsonTextSchema.safeParse(line).data).success;
556
+ if (!forwarded) {
557
+ log("supervisor.probe_refused", { seat: input.seat.id.slice(0, 8), kind: parsed.success ? parsed.data.type : "unparsed" });
558
+ proc.kill("SIGKILL");
559
+ break;
560
+ }
561
+ try {
562
+ proc.stdin.write(`${line}\n`);
563
+ proc.stdin.flush();
564
+ } catch (e) {
565
+ log("supervisor.probe_stdin_failed", { err: errorMessage(e) });
566
+ break;
567
+ }
568
+ }
569
+ try {
570
+ proc.stdin.end();
571
+ } catch (e) {
572
+ log("supervisor.probe_stdin_failed", { err: errorMessage(e) });
573
+ }
574
+ })().catch((e: unknown) => log("supervisor.probe_stdin_failed", { err: errorMessage(e) }));
575
+ await proc.exited;
576
+ await Promise.race([stdoutPump, Bun.sleep(1000)]);
577
+ return exitStatus(proc);
578
+ }
579
+
481
580
  function bareResumeStream(info: Analysis, argv: string[], sid: string | null): boolean {
482
581
  if (!info.manage || process.env[UNMANAGED_ENV] || info.sessionId != null || stripSessionFlags(argv).length !== 0 || sid == null) return false;
483
582
  const persisted = loadSessionFlags(sid);
@@ -532,6 +631,16 @@ export async function runSupervisor(argv: string[]): Promise<number> {
532
631
 
533
632
  if (!info.manage || process.env[UNMANAGED_ENV]) {
534
633
  const passthroughEnv = omit(childEnv, ["TOKENMAXXING_SUPERVISED", "TOKENMAXXING_SESSION_ID", "TOKENMAXXING_MODEL", "TOKENMAXXING_REFUSED", "TOKENMAXXING_API_KEY_ID"]);
634
+ if (!process.env[UNMANAGED_ENV] && !process.env.TOKENMAXXING_PROBE && isCapabilitiesProbe(argv, process.env)) {
635
+ const seat = await withLock(claudePool.lockFile, () => pickSeat(Date.now(), null)?.account ?? null).catch((e: unknown) => {
636
+ log("supervisor.probe_pick_failed", { err: errorMessage(e) });
637
+ return null;
638
+ });
639
+ if (seat != null) {
640
+ const probeEnv = { ...scrubCredEnv(passthroughEnv), CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(seat.id) };
641
+ return runCapabilitiesProbe({ real, argv, env: probeEnv, seat, onSpawn: (p) => { child = p; } });
642
+ }
643
+ }
535
644
  return runPassthrough({ real, argv, env: passthroughEnv, onSpawn: (p) => { child = p; } });
536
645
  }
537
646
 
package/src/lib/picker.ts CHANGED
@@ -164,6 +164,15 @@ export function seatHeadroom(a: Account, ctx: PickCtx): number {
164
164
  return (barFor(a, session, ctx.thresholds, ctx.now) - liveUsed(session, ctx.now)) / ((ctx.seats?.get(a.id) ?? 0) + 1);
165
165
  }
166
166
 
167
+ const MIN_SEAT_HEADROOM = 5;
168
+
169
+ function seatFull(a: Account, ctx: PickCtx): boolean {
170
+ const session = sessionWindow(a);
171
+ if (ctx.seats == null || session == null || spendsCredits(a, ctx.thresholds)) return false;
172
+ const fresh = barFor(a, session, ctx.thresholds, windowEnd(session)) / ((ctx.seats.get(a.id) ?? 0) + 1);
173
+ return seatHeadroom(a, ctx) < MIN_SEAT_HEADROOM && fresh >= MIN_SEAT_HEADROOM;
174
+ }
175
+
167
176
  export function onCredits(a: Account, ctx: PickCtx): boolean {
168
177
  return spendsCredits(a, ctx.thresholds) && isExhausted({ ...a, hasCredits: false }, ctx);
169
178
  }
@@ -178,13 +187,15 @@ const swapPreference = (ctx: PickCtx) => [
178
187
  ];
179
188
 
180
189
  export function pickBest(accounts: Account[], ctx: PickCtx): Account | null {
181
- const usable = accounts.filter((a) => a.id !== ctx.currentId && !a.needsReauth && !isExhausted(a, ctx));
190
+ const usable = accounts.filter((a) => a.id !== ctx.currentId && !a.needsReauth && !isExhausted(a, ctx) && !seatFull(a, ctx));
182
191
  return sortBy(usable, swapPreference(ctx))[0] ?? null;
183
192
  }
184
193
 
185
194
  export function usableAt(a: Account, ctx: PickCtx): number {
186
195
  const blocking = blockingUntil(a, ctx).filter((t) => t > ctx.now);
187
- return blocking.length ? Math.max(...blocking) : ctx.now;
196
+ const at = blocking.length ? Math.max(...blocking) : ctx.now;
197
+ const session = sessionWindow(a);
198
+ return session != null && seatFull(a, { ...ctx, now: at }) ? windowEnd(session) : at;
188
199
  }
189
200
 
190
201
  export type EarliestReset = { account: Account; availableAt: number };