tokenmaxxing 1.57.0 → 1.59.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 (54) 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 +37 -55
  13. package/src/entries/codexstophook.ts +11 -24
  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 +71 -113
  20. package/src/lib/claude.ts +18 -19
  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 +12 -29
  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/provider.ts +1 -1
  44. package/src/lib/sample.ts +25 -24
  45. package/src/lib/sessions.ts +5 -6
  46. package/src/lib/settings.ts +1 -8
  47. package/src/lib/state.ts +6 -29
  48. package/src/lib/statusonly.ts +143 -0
  49. package/src/lib/supervise.ts +111 -0
  50. package/src/lib/types.ts +27 -36
  51. package/src/lib/update.ts +6 -7
  52. package/src/lib/usage.ts +45 -188
  53. package/src/main.ts +5 -5
  54. package/src/entries/sessionstart.ts +0 -33
package/src/lib/usage.ts CHANGED
@@ -3,28 +3,13 @@ import { delay } from "es-toolkit";
3
3
  import { z } from "zod";
4
4
  import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealClaude } from "./claudebin.ts";
5
5
  import { http } from "./http.ts";
6
- import { log } from "./log.ts";
6
+ import { errorMessage, log } from "./log.ts";
7
7
  import { env } from "./paths.ts";
8
- import { JsonTextSchema, RateLimitsStdinSchema, type ModelInfo, type UsageWindow, type UsageWindows, type Window } from "./types.ts";
9
-
10
- export function normalizeResetsAt(v: unknown): number | null {
11
- const num = z.number().finite().safeParse(v);
12
- if (num.success) {
13
- return num.data < 1e12 ? Math.round(num.data * 1000) : Math.round(num.data);
14
- }
15
- const str = z.string().safeParse(v);
16
- if (str.success && str.data.trim() !== "") {
17
- const n = Number(str.data);
18
- if (Number.isFinite(n)) return normalizeResetsAt(n);
19
- const t = Date.parse(str.data);
20
- return Number.isFinite(t) ? t : null;
21
- }
22
- return null;
23
- }
8
+ import { EpochSecondsSchema, JsonTextSchema, RateLimitsStdinSchema, type ModelInfo, type UsageWindow, type UsageWindows, type Window } from "./types.ts";
24
9
 
25
10
  const win = (w: { used_percentage: number; resets_at?: number | null }): UsageWindow => ({
26
11
  usedPercentage: w.used_percentage,
27
- resetsAt: normalizeResetsAt(w.resets_at),
12
+ resetsAt: w.resets_at ?? null,
28
13
  });
29
14
 
30
15
  export function parseStatusLineStdin(obj: unknown): UsageWindows | null {
@@ -59,65 +44,12 @@ export function gatedFamilies(model: ModelInfo | null, families: string[]): stri
59
44
  return family ? [family] : [];
60
45
  }
61
46
 
62
- const MONTHS: Record<string, number> = {
63
- jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
64
- };
65
-
66
- function tzOffsetMs(utcMs: number, tz: string): number {
67
- const parts = new Intl.DateTimeFormat("en-US", {
68
- timeZone: tz,
69
- hourCycle: "h23",
70
- year: "numeric", month: "2-digit", day: "2-digit",
71
- hour: "2-digit", minute: "2-digit", second: "2-digit",
72
- }).formatToParts(new Date(utcMs));
73
- const p: Record<string, number> = {};
74
- for (const part of parts) if (part.type !== "literal") p[part.type] = Number(part.value);
75
- const asUTC = Date.UTC(p.year!, p.month! - 1, p.day!, p.hour!, p.minute!, p.second!);
76
- return asUTC - utcMs;
77
- }
78
-
79
- function zonedWallToEpoch(y: number, mon: number, day: number, hour: number, min: number, tz: string): number {
80
- const guess = Date.UTC(y, mon, day, hour, min);
81
- const off1 = tzOffsetMs(guess, tz);
82
- const off2 = tzOffsetMs(guess - off1, tz);
83
- return guess - off2;
84
- }
85
-
86
- export function parseResetClock(clock: string, now = Date.now()): number | null {
87
- const m = clock.match(/\b([A-Za-z]{3,9})\s+(\d{1,2})(?:[^\S\n]+at[^\S\n]+|,[^\S\n]*)(\d{1,2})(?::(\d{2}))?\s*([ap])m\s*\(([^)]+)\)/i);
88
- if (!m) return null;
89
- const mon = MONTHS[m[1]!.slice(0, 3).toLowerCase()];
90
- if (mon === undefined) return null;
91
- const day = Number(m[2]);
92
- let hour = Number(m[3]) % 12;
93
- if (m[5]!.toLowerCase() === "p") hour += 12;
94
- const min = m[4] ? Number(m[4]) : 0;
95
- const tz = m[6]!.trim();
96
-
97
- const baseYear = new Date(now).getUTCFullYear();
98
- let best: number | null = null;
99
- for (const y of [baseYear - 1, baseYear, baseYear + 1]) {
100
- let epoch: number;
101
- try {
102
- epoch = zonedWallToEpoch(y, mon, day, hour, min, tz);
103
- } catch {
104
- return null;
105
- }
106
- if (best === null || Math.abs(epoch - now) < Math.abs(best - now)) best = epoch;
107
- }
108
- return best;
109
- }
47
+ const MODEL_FAMILIES = ["sonnet", "opus", "haiku", "fable"];
110
48
 
111
- export function fmtResetShort(epochMs: number | null | undefined, now = Date.now()): string {
112
- if (epochMs == null) return "";
113
- const dsec = Math.round((epochMs - now) / 1000);
114
- if (dsec <= 0) return "";
115
- const d = Math.floor(dsec / 86400);
116
- const h = Math.floor((dsec % 86400) / 3600);
117
- const m = Math.floor((dsec % 3600) / 60);
118
- if (d > 0) return `${d}d`;
119
- if (h > 0) return `${h}h`;
120
- return `${Math.max(m, 1)}m`;
49
+ export function modelFromFlag(value: string | null): ModelInfo | null {
50
+ const name = value?.split("[")[0]?.trim() ?? "";
51
+ const tokens = familyTokens(name);
52
+ return MODEL_FAMILIES.some((f) => tokens.includes(f)) ? { id: name, display: name } : null;
121
53
  }
122
54
 
123
55
  const TranscriptBlockSchema = z.looseObject({ type: z.string().optional(), text: z.string().optional() });
@@ -128,7 +60,7 @@ export const TranscriptRowSchema = z.looseObject({
128
60
  apiErrorIsTransient: z.boolean().optional(),
129
61
  error: z.string().optional(),
130
62
  errorDetails: z.string().optional(),
131
- quotaLimits: z.looseObject({ rateLimitType: z.string().optional(), resetsAt: z.number().optional() }).optional(),
63
+ quotaLimits: z.looseObject({ rateLimitType: z.string().optional(), resetsAt: EpochSecondsSchema.optional() }).optional(),
132
64
  message: z.looseObject({ content: z.unknown().optional() }).optional(),
133
65
  });
134
66
  export type TranscriptRow = z.infer<typeof TranscriptRowSchema>;
@@ -154,11 +86,8 @@ export function readTranscriptTail(path: string, maxBytes = TRANSCRIPT_TAIL_BYTE
154
86
  }
155
87
  const rows: TranscriptRow[] = [];
156
88
  for (const line of text.split("\n")) {
157
- if (line.trim() === "") continue;
158
- try {
159
- const parsed = TranscriptRowSchema.safeParse(JSON.parse(line));
160
- if (parsed.success) rows.push(parsed.data);
161
- } catch {}
89
+ const parsed = TranscriptRowSchema.safeParse(JsonTextSchema.safeParse(line).data);
90
+ if (parsed.success) rows.push(parsed.data);
162
91
  }
163
92
  return rows;
164
93
  }
@@ -184,12 +113,10 @@ export function findEnforcedRow(input: { rows: TranscriptRow[]; lastAssistantMes
184
113
  return null;
185
114
  }
186
115
 
187
- export const EnforcedClassSchema = z.discriminatedUnion("kind", [
188
- z.object({ kind: z.literal("session"), resetsAt: z.number().nullable() }),
189
- z.object({ kind: z.literal("weekly"), resetsAt: z.number().nullable() }),
190
- z.object({ kind: z.literal("model"), family: z.string(), resetsAt: z.number().nullable() }),
191
- ]);
192
- export type EnforcedClass = z.infer<typeof EnforcedClassSchema>;
116
+ export type EnforcedClass =
117
+ | { kind: "session"; resetsAt: number | null }
118
+ | { kind: "weekly"; resetsAt: number | null }
119
+ | { kind: "model"; family: string; resetsAt: number | null };
193
120
 
194
121
  const ErrorBodySchema = z.looseObject({
195
122
  error: z.looseObject({ type: z.string().optional(), details: z.looseObject({ error_code: z.string().optional() }).optional() }).optional(),
@@ -201,18 +128,14 @@ export function parseErrorBody(errorDetails: string | undefined): z.infer<typeof
201
128
  if (!errorDetails) return null;
202
129
  const at = errorDetails.indexOf("{");
203
130
  if (at < 0) return null;
204
- try {
205
- const body = ErrorBodySchema.safeParse(JSON.parse(errorDetails.slice(at)));
206
- return body.success ? body.data : null;
207
- } catch {
208
- return null;
209
- }
131
+ const body = ErrorBodySchema.safeParse(JsonTextSchema.safeParse(errorDetails.slice(at)).data);
132
+ return body.success ? body.data : null;
210
133
  }
211
134
 
212
135
  export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]): EnforcedClass | null {
213
136
  const q = row.quotaLimits;
214
137
  if (q) {
215
- const resetsAt = q.resetsAt != null ? normalizeResetsAt(q.resetsAt) : null;
138
+ const resetsAt = q.resetsAt ?? null;
216
139
  const type = q.rateLimitType ?? "";
217
140
  if (type === "five_hour") return { kind: "session", resetsAt };
218
141
  if (type === "seven_day") return { kind: "weekly", resetsAt };
@@ -225,22 +148,6 @@ export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]
225
148
  return family ? { kind: "model", family, resetsAt: null } : null;
226
149
  }
227
150
 
228
- export function parseUsageText(text: string, now = Date.now()): UsageWindows | null {
229
- if (!text) return null;
230
- const re = /current (session|week \(([^)]+)\)):\s*(\d+)\s*%(?:(?:(?!current)[^\n])*?\bresets\s+([A-Z][a-z]{2,8}\s+\d{1,2}(?:[^\S\n]+at[^\S\n]+|,[^\S\n]*)\d{1,2}(?::\d{2})?\s*[ap]m\s*\([^)]+\)))?/gi;
231
- let fiveHour: UsageWindow | null = null;
232
- let sevenDay: UsageWindow | null = null;
233
- const perModel: Record<string, UsageWindow> = {};
234
- for (let m = re.exec(text); m !== null; m = re.exec(text)) {
235
- const window: UsageWindow = { usedPercentage: Number(m[3]), resetsAt: m[4] ? parseResetClock(m[4], now) : null };
236
- if (m[1]!.toLowerCase() === "session") fiveHour = window;
237
- else if (/^all models$/i.test(m[2]!.trim())) sevenDay = window;
238
- else perModel[m[2]!.trim()] = window;
239
- }
240
- if (!fiveHour || !sevenDay) return null;
241
- return { fiveHour, sevenDay, perModel };
242
- }
243
-
244
151
  const FIVE_HOURS_S = 5 * 3600;
245
152
  const WEEK_S = 7 * 24 * 3600;
246
153
 
@@ -273,97 +180,46 @@ export const CRED_ENV_OVERRIDES = [
273
180
  const PROBE_KILL_MS = 60_000;
274
181
  const PIPE_GRACE_MS = 2_000;
275
182
 
276
- const SpawnResultSchema = z.object({
277
- exitCode: z.number().nullable(),
278
- stdout: z.string(),
279
- stderr: z.string(),
280
- });
281
- type SpawnResult = z.infer<typeof SpawnResultSchema>;
282
-
283
- async function spawnClaudeBounded(cmd: string[], env: Record<string, string>): Promise<SpawnResult | null> {
284
- const p = Bun.spawn(cmd, { env, stdout: "pipe", stderr: "pipe", timeout: PROBE_KILL_MS, killSignal: "SIGKILL" });
285
- const reads = Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
286
- const settled = await Promise.race([
287
- reads,
288
- p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null),
289
- ]);
290
- if (settled === null) return null;
291
- const [stdout, stderr] = settled;
292
- await p.exited;
293
- return { exitCode: p.exitCode, stdout, stderr };
294
- }
295
-
296
- async function probeUsageOnce(env: Record<string, string>, now: number): Promise<UsageWindows | null> {
297
- let out: string;
298
- try {
299
- const r = await spawnClaudeBounded([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], env);
300
- if (r === null) {
301
- log("usage.probe_failed", { err: "output pipes still open after child exit (leaked descendant)" });
302
- return null;
303
- }
304
- if (r.exitCode !== 0) {
305
- log("usage.probe_failed", { exit: r.exitCode ?? "signal", stderr: r.stderr.trim().slice(0, 200) });
306
- return null;
307
- }
308
- out = r.stdout;
309
- } catch (e) {
310
- log("usage.probe_failed", { err: e instanceof Error ? e.message : String(e) });
311
- return null;
312
- }
313
-
314
- const j = z.object({ result: z.string() }).safeParse(JsonTextSchema.safeParse(out).data);
315
- const text = j.success ? j.data.result : out;
316
- const full = parseUsageText(text, now);
317
- if (!full) {
318
- log("usage.probe_unparsed", { sample: text.trim().slice(0, 200) });
319
- } else {
320
- const clockLine = text.split("\n").find((l) => /^current /i.test(l) && /\bresets\b/i.test(l));
321
- if (clockLine && [full.fiveHour, full.sevenDay, ...Object.values(full.perModel)].every((w) => w.resetsAt === null)) {
322
- log("usage.reset_clock_unparsed", { sample: clockLine.slice(0, 120) });
323
- }
324
- }
325
- return full;
326
- }
327
-
328
- const PROBE_RETRY_DELAYS_MS = [2000, 5000];
329
-
330
183
  export function scrubCredentialEnv(env: Record<string, string>): Record<string, string> {
331
184
  const scrubbed = { ...env };
332
185
  for (const k of CRED_ENV_OVERRIDES) delete scrubbed[k];
333
186
  return scrubbed;
334
187
  }
335
188
 
336
- export type ProbeTarget = { configDir: string; store?: string };
189
+ export type ProbeTarget = { configDir: string; store: string };
337
190
 
338
- function probeEnv(target: ProbeTarget): Record<string, string> {
191
+ export async function refreshViaProbe(target: ProbeTarget): Promise<boolean> {
339
192
  const env = scrubCredentialEnv({ ...process.env, TOKENMAXXING_PROBE: "1", [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH) });
340
193
  env.CLAUDE_CONFIG_DIR = target.configDir;
341
- if (target.store) env.CLAUDE_SECURESTORAGE_CONFIG_DIR = target.store;
342
- return env;
343
- }
344
-
345
- export async function probeUsage(target: ProbeTarget, now = Date.now(), opts: { retries?: number } = {}): Promise<UsageWindows | null> {
346
- const probe = probeEnv(target);
347
-
348
- for (let attempt = 0; ; attempt++) {
349
- const full = await probeUsageOnce(probe, now);
350
- if (full) return full;
351
- if (attempt >= (opts.retries ?? PROBE_RETRY_DELAYS_MS.length)) {
352
- log("usage.probe_gave_up", { attempts: attempt + 1 });
353
- return null;
194
+ env.CLAUDE_SECURESTORAGE_CONFIG_DIR = target.store;
195
+ try {
196
+ const p = Bun.spawn([resolveRealClaude(), "-p", "/usage", "--output-format", "json"], { env, stdout: "ignore", stderr: "pipe", timeout: PROBE_KILL_MS, killSignal: "SIGKILL" });
197
+ const stderr = await Promise.race([new Response(p.stderr).text(), p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null)]);
198
+ await p.exited;
199
+ if (stderr === null) {
200
+ log("usage.probe_failed", { err: "output pipes still open after child exit (leaked descendant)" });
201
+ return false;
202
+ }
203
+ if (p.exitCode !== 0) {
204
+ log("usage.probe_failed", { exit: p.exitCode ?? "signal", stderr: stderr.trim().slice(0, 200) });
205
+ return false;
354
206
  }
355
- await delay(PROBE_RETRY_DELAYS_MS[Math.min(attempt, PROBE_RETRY_DELAYS_MS.length - 1)]!);
207
+ return true;
208
+ } catch (e) {
209
+ log("usage.probe_failed", { err: errorMessage(e) });
210
+ return false;
356
211
  }
357
212
  }
358
213
 
359
214
  const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", "https://api.anthropic.com/api/oauth/usage");
360
215
  const USAGE_DEADLINE_MS = 10_000;
361
216
 
362
- const UsageLimitSchema = z.looseObject({ utilization: z.number(), resets_at: z.unknown() });
217
+ const ResetsAtSchema = z.iso.datetime({ offset: true }).transform((iso) => Date.parse(iso)).nullish();
218
+ const UsageLimitSchema = z.looseObject({ utilization: z.number(), resets_at: ResetsAtSchema });
363
219
  const UsageScopedLimitSchema = z.looseObject({
364
220
  kind: z.string(),
365
221
  percent: z.number(),
366
- resets_at: z.unknown(),
222
+ resets_at: ResetsAtSchema,
367
223
  scope: z.looseObject({ model: z.looseObject({ display_name: z.string() }) }),
368
224
  });
369
225
  const UsageResponseSchema = z.looseObject({
@@ -377,7 +233,7 @@ function scopedRows(limits: unknown[]): Record<string, UsageWindow> {
377
233
  for (const raw of limits) {
378
234
  const row = UsageScopedLimitSchema.safeParse(raw);
379
235
  if (!row.success || row.data.kind !== "weekly_scoped") continue;
380
- perModel[row.data.scope.model.display_name] = { usedPercentage: row.data.percent, resetsAt: normalizeResetsAt(row.data.resets_at) };
236
+ perModel[row.data.scope.model.display_name] = { usedPercentage: row.data.percent, resetsAt: row.data.resets_at ?? null };
381
237
  }
382
238
  return perModel;
383
239
  }
@@ -385,12 +241,13 @@ function scopedRows(limits: unknown[]): Record<string, UsageWindow> {
385
241
  export async function fetchUsageDirect(accessToken: string): Promise<UsageWindows | null> {
386
242
  let res: Response;
387
243
  try {
388
- res = await http.get(`${USAGE_URL}?at_wall=1&skip_spend=1`, {
244
+ res = await http.get(USAGE_URL, {
245
+ searchParams: { at_wall: 1, skip_spend: 1 },
389
246
  headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
390
247
  signal: AbortSignal.timeout(USAGE_DEADLINE_MS),
391
248
  });
392
249
  } catch (e) {
393
- log("usage.get_failed", { err: e instanceof Error ? e.message : String(e) });
250
+ log("usage.get_failed", { err: errorMessage(e) });
394
251
  return null;
395
252
  }
396
253
  if (!res.ok) {
@@ -399,9 +256,9 @@ export async function fetchUsageDirect(accessToken: string): Promise<UsageWindow
399
256
  }
400
257
  const parsed = UsageResponseSchema.safeParse(JsonTextSchema.safeParse(await res.text()).data);
401
258
  if (!parsed.success || !parsed.data.five_hour || !parsed.data.seven_day) {
402
- log("usage.get_incomplete", { ok: parsed.success });
259
+ log("usage.get_incomplete", { ok: parsed.success, issue: parsed.success ? undefined : z.prettifyError(parsed.error).slice(0, 200) });
403
260
  return null;
404
261
  }
405
- const win = (w: z.infer<typeof UsageLimitSchema>): UsageWindow => ({ usedPercentage: w.utilization, resetsAt: normalizeResetsAt(w.resets_at) });
262
+ const win = (w: z.infer<typeof UsageLimitSchema>): UsageWindow => ({ usedPercentage: w.utilization, resetsAt: w.resets_at ?? null });
406
263
  return { fiveHour: win(parsed.data.five_hour), sevenDay: win(parsed.data.seven_day), perModel: scopedRows(parsed.data.limits ?? []) };
407
264
  }
package/src/main.ts CHANGED
@@ -4,9 +4,8 @@ import { basename } from "node:path";
4
4
  import { runSupervisor } from "./entries/supervisor.ts";
5
5
  import { runStatusline } from "./entries/statusline.ts";
6
6
  import { runSubagentStatusline } from "./entries/subagentstatusline.ts";
7
- import { runStopHook } from "./entries/stophook.ts";
7
+ import { runBoundaryHook } from "./entries/stophook.ts";
8
8
  import { runStopFailureHook } from "./entries/stopfailurehook.ts";
9
- import { runSessionStart } from "./entries/sessionstart.ts";
10
9
  import { runCodexSupervisor } from "./entries/codexsupervisor.ts";
11
10
  import { runCodexStopHook } from "./entries/codexstophook.ts";
12
11
  import { claude } from "./lib/claude.ts";
@@ -26,6 +25,7 @@ import { cmdSetupToken } from "./cli/setuptoken.ts";
26
25
  import { cmdCloudRun } from "./cli/cloudrun.ts";
27
26
  import { cmdCursorInit } from "./cli/cursorinit.ts";
28
27
  import { onLoginHome, timerDeactivationHint, uninstallSupervisor, uninstallTargets } from "./lib/install.ts";
28
+ import { errorMessage } from "./lib/log.ts";
29
29
  import { HOME } from "./lib/paths.ts";
30
30
  import { c, emitError } from "./cli/render.ts";
31
31
 
@@ -124,9 +124,9 @@ async function main(): Promise<number> {
124
124
  switch (sub) {
125
125
  case "__statusline": return runStatusline();
126
126
  case "__subagent-statusline": return runSubagentStatusline();
127
- case "__stop-hook": return runStopHook();
127
+ case "__stop-hook": return runBoundaryHook("stop");
128
128
  case "__stop-failure-hook": return runStopFailureHook();
129
- case "__session-start": return runSessionStart();
129
+ case "__session-start": return runBoundaryHook("sessionstart");
130
130
  case "__codex-stop-hook": return runCodexStopHook();
131
131
  case undefined:
132
132
  case "status": {
@@ -201,6 +201,6 @@ async function main(): Promise<number> {
201
201
  try {
202
202
  process.exit(await main());
203
203
  } catch (e) {
204
- emitError({ json: jsonMode, message: e instanceof Error ? e.message : String(e) });
204
+ emitError({ json: jsonMode, message: errorMessage(e) });
205
205
  process.exit(1);
206
206
  }
@@ -1,33 +0,0 @@
1
- import { z } from "zod";
2
- import { claude } from "../lib/claude.ts";
3
- import { evaluateAndMaybeSwap } from "../lib/decide.ts";
4
- import { supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
5
- import { log } from "../lib/log.ts";
6
- import { readStdin } from "./statusline.ts";
7
- import { JsonTextSchema } from "../lib/types.ts";
8
-
9
- const SessionStartStdin = z.looseObject({
10
- source: z.string().optional(),
11
- session_id: z.uuid().optional().catch(undefined),
12
- });
13
-
14
- export async function runSessionStart(): Promise<number> {
15
- if (process.env.TOKENMAXXING_PROBE) return 0;
16
-
17
- const raw = await readStdin();
18
- const parsed = SessionStartStdin.safeParse(JsonTextSchema.safeParse(raw).data);
19
- const source = parsed.success ? parsed.data.source : undefined;
20
- const stdinSid = parsed.success ? parsed.data.session_id : undefined;
21
- const session = supervisedSession();
22
-
23
- try {
24
- const decision = await evaluateAndMaybeSwap(claude, Date.now(), session != null);
25
- if (session && decision.account && (decision.swapped || decision.waitUntil !== undefined)) {
26
- writeRespawnMarker({ session, sessionId: stdinSid ?? session.sid, accountId: decision.account.id, waitUntil: decision.waitUntil ?? Date.now(), compact: true });
27
- log("sessionstart.marker", { source, account: decision.account.id.slice(0, 8), waitUntil: decision.waitUntil });
28
- }
29
- } catch (e) {
30
- log("sessionstart.error", { err: e instanceof Error ? e.message : String(e) });
31
- }
32
- return 0;
33
- }