tokenmaxxing 1.56.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.
- package/DESIGN.md +7 -7
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/cli/auth.ts +6 -17
- package/src/cli/check.ts +2 -2
- package/src/cli/cloudrun.ts +12 -19
- package/src/cli/config.ts +2 -1
- package/src/cli/doctor.ts +5 -4
- package/src/cli/init.ts +0 -1
- package/src/cli/render.ts +24 -9
- package/src/cli/rm.ts +0 -4
- package/src/cli/status.ts +34 -57
- package/src/entries/codexstophook.ts +10 -23
- package/src/entries/codexsupervisor.ts +30 -72
- package/src/entries/statusline.ts +17 -26
- package/src/entries/stopfailurehook.ts +4 -5
- package/src/entries/stophook.ts +12 -10
- package/src/entries/subagentstatusline.ts +4 -10
- package/src/entries/supervisor.ts +91 -116
- package/src/lib/claude.ts +11 -12
- package/src/lib/claudebin.ts +3 -2
- package/src/lib/claudelock.ts +2 -2
- package/src/lib/cloud.ts +4 -8
- package/src/lib/codex.ts +3 -3
- package/src/lib/codexauth.ts +2 -11
- package/src/lib/codexbin.ts +2 -1
- package/src/lib/codexoauth.ts +3 -8
- package/src/lib/codexusage.ts +5 -5
- package/src/lib/compact.ts +5 -18
- package/src/lib/credstore.ts +1 -6
- package/src/lib/decide.ts +6 -24
- package/src/lib/grok.ts +38 -152
- package/src/lib/http.ts +1 -2
- package/src/lib/install.ts +20 -38
- package/src/lib/keychain.ts +1 -4
- package/src/lib/log.ts +5 -5
- package/src/lib/oauth.ts +6 -5
- package/src/lib/opencodego.ts +31 -140
- package/src/lib/paths.ts +6 -22
- package/src/lib/picker.ts +9 -20
- package/src/lib/presence.ts +4 -5
- package/src/lib/proc.ts +22 -0
- package/src/lib/sample.ts +25 -24
- package/src/lib/sessions.ts +5 -6
- package/src/lib/settings.ts +1 -8
- package/src/lib/state.ts +6 -29
- package/src/lib/statusonly.ts +143 -0
- package/src/lib/supervise.ts +111 -0
- package/src/lib/types.ts +27 -36
- package/src/lib/update.ts +6 -7
- package/src/lib/usage.ts +40 -191
- package/src/main.ts +5 -5
- 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
|
|
4
|
+
import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
5
5
|
import { withLock } from "../lib/lock.ts";
|
|
6
|
-
import {
|
|
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
|
|
11
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (existsSync(marker))
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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
|
-
|
|
21
|
-
accounts:
|
|
22
|
-
perModel:
|
|
23
|
-
switchModels:
|
|
24
|
-
worktree:
|
|
25
|
-
liveAccount:
|
|
26
|
-
now:
|
|
27
|
-
color:
|
|
28
|
-
truecolor:
|
|
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
|
-
|
|
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: ${
|
|
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
|
|
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:
|
|
86
|
+
log("stopfailure.error", { err: errorMessage(e) });
|
|
88
87
|
}
|
|
89
88
|
return 0;
|
|
90
89
|
}
|
package/src/entries/stophook.ts
CHANGED
|
@@ -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
|
|
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
|
|
14
|
+
export async function runBoundaryHook(event: "stop" | "sessionstart"): Promise<number> {
|
|
12
15
|
if (process.env.TOKENMAXXING_PROBE) return 0;
|
|
13
16
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
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:
|
|
23
|
-
log(decision.waitUntil !== undefined ? "
|
|
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(
|
|
28
|
+
log(`${event}.error`, { err: errorMessage(e) });
|
|
27
29
|
}
|
|
28
30
|
return 0;
|
|
29
31
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
}
|