tokenmaxxing 1.8.0 → 1.9.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 +2 -4
- package/README.md +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/src/cli/add.ts +1 -8
- package/src/cli/auth.ts +0 -23
- package/src/cli/check.ts +19 -13
- package/src/cli/codexadd.ts +0 -17
- package/src/cli/codexinit.ts +0 -40
- package/src/cli/codexrm.ts +0 -13
- package/src/cli/codexswitch.ts +0 -15
- package/src/cli/config.ts +0 -30
- package/src/cli/doctor.ts +1 -14
- package/src/cli/init.ts +1 -33
- package/src/cli/ls.ts +0 -2
- package/src/cli/onboard.ts +0 -37
- package/src/cli/rename.ts +0 -19
- package/src/cli/render.ts +0 -23
- package/src/cli/rm.ts +0 -19
- package/src/cli/status.ts +0 -80
- package/src/cli/switch.ts +1 -49
- package/src/cli/watch.ts +0 -17
- package/src/entries/codexstophook.ts +2 -73
- package/src/entries/codexsupervisor.ts +1 -67
- package/src/entries/mcp.ts +0 -11
- package/src/entries/sessionstart.ts +1 -8
- package/src/entries/statusline.ts +0 -66
- package/src/entries/stopfailurehook.ts +93 -0
- package/src/entries/stophook.ts +3 -34
- package/src/entries/subagentstatusline.ts +0 -19
- package/src/entries/supervisor.ts +32 -132
- package/src/lib/atomic.ts +0 -16
- package/src/lib/claudebin.ts +4 -55
- package/src/lib/claudejson.ts +0 -10
- package/src/lib/claudelock.ts +13 -35
- package/src/lib/codexauth.ts +0 -29
- package/src/lib/codexbin.ts +0 -10
- package/src/lib/codexdecide.ts +1 -112
- package/src/lib/codexoauth.ts +0 -16
- package/src/lib/codexpick.ts +0 -31
- package/src/lib/codexpresence.ts +0 -35
- package/src/lib/codexsample.ts +0 -23
- package/src/lib/codexstate.ts +0 -7
- package/src/lib/codexswap.ts +0 -32
- package/src/lib/codexusage.ts +0 -28
- package/src/lib/credstore.ts +0 -24
- package/src/lib/decide.ts +127 -180
- package/src/lib/http.ts +0 -9
- package/src/lib/install.ts +6 -127
- package/src/lib/keychain.ts +1 -39
- package/src/lib/lock.ts +0 -24
- package/src/lib/log.ts +0 -14
- package/src/lib/oauth.ts +1 -31
- package/src/lib/paths.ts +1 -48
- package/src/lib/picker.ts +1 -84
- package/src/lib/proc.ts +0 -17
- package/src/lib/sample.ts +0 -68
- package/src/lib/sessions.ts +0 -13
- package/src/lib/settings.ts +15 -42
- package/src/lib/state.ts +23 -77
- package/src/lib/swap.ts +3 -87
- package/src/lib/tty.ts +0 -4
- package/src/lib/types.ts +13 -140
- package/src/lib/usage.ts +108 -196
- package/src/lib/worktree.ts +0 -8
- package/src/main.ts +5 -40
- package/src/sdk.ts +0 -59
- package/agent-plugin/agents/tokenmaxxing-claude.md +0 -43
- package/agent-plugin/agents/tokenmaxxing-codex.md +0 -40
- package/agent-plugin/hooks/cursor-relay.json +0 -14
- package/agent-plugin/skills/relay-session/SKILL.md +0 -118
- package/agent-plugin/skills/relay-session/references/ipc.md +0 -23
- package/src/cli/relay.ts +0 -323
- package/src/entries/relaypermission.ts +0 -105
- package/src/lib/relay/config.ts +0 -84
- package/src/lib/relay/decide.ts +0 -75
- package/src/lib/relay/gc.ts +0 -80
- package/src/lib/relay/install.ts +0 -143
- package/src/lib/relay/markers.ts +0 -148
- package/src/lib/relay/modes.ts +0 -82
- package/src/lib/relay/protocol.ts +0 -61
- package/src/lib/relay/registry.ts +0 -175
- package/src/lib/relay/tmux.ts +0 -109
- package/src/lib/relay/turn.ts +0 -137
- package/src/lib/relay/worker.ts +0 -141
package/src/lib/types.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
// Shared data model - Zod schemas are the single source of truth; TS types are
|
|
2
|
-
// inferred from them. Everything that crosses an external boundary (keychain
|
|
3
|
-
// blob, ~/.claude.json, hook/statusLine stdin, OAuth response, our own state
|
|
4
|
-
// files) is validated through these instead of hand-checked.
|
|
5
|
-
|
|
6
1
|
import { z } from "zod";
|
|
7
2
|
|
|
8
|
-
/** OAuth object inside the keychain blob (`claudeAiOauth`). Loose: preserve any
|
|
9
|
-
* extra fields claude may add so a harvest→install round-trip is lossless. */
|
|
10
3
|
const OAuthCredsSchema = z.looseObject({
|
|
11
4
|
accessToken: z.string(),
|
|
12
5
|
refreshToken: z.string(),
|
|
@@ -18,15 +11,8 @@ const OAuthCredsSchema = z.looseObject({
|
|
|
18
11
|
});
|
|
19
12
|
export type OAuthCreds = z.infer<typeof OAuthCredsSchema>;
|
|
20
13
|
|
|
21
|
-
/** The `Claude Code-credentials` keychain item. Loose: the live item also holds
|
|
22
|
-
* sibling state (e.g. per-MCP-server OAuth tokens), which we must preserve when
|
|
23
|
-
* swapping - we only ever replace `claudeAiOauth`. */
|
|
24
14
|
export const CredentialBlobSchema = z.looseObject({ claudeAiOauth: OAuthCredsSchema });
|
|
25
15
|
|
|
26
|
-
/** The `oauthAccount` identity object in ~/.claude.json. Loose: preserve every
|
|
27
|
-
* key so we can reinstall it verbatim on activation. Only the three ids are
|
|
28
|
-
* required; real blobs carry many more fields (some null), so descriptive ones
|
|
29
|
-
* are `.nullish()` (string | null | undefined) to tolerate them. */
|
|
30
16
|
export const OAuthAccountSchema = z.looseObject({
|
|
31
17
|
accountUuid: z.string(),
|
|
32
18
|
emailAddress: z.string(),
|
|
@@ -35,8 +21,6 @@ export const OAuthAccountSchema = z.looseObject({
|
|
|
35
21
|
seatTier: z.string().nullish(),
|
|
36
22
|
billingType: z.string().nullish(),
|
|
37
23
|
displayName: z.string().nullish(),
|
|
38
|
-
/** rate-limit tier id ("default_claude_max_20x"), same value the credential
|
|
39
|
-
* blob carries; claude fills it in on profile fetch (live-verified 2.1.211). */
|
|
40
24
|
organizationRateLimitTier: z.string().nullish(),
|
|
41
25
|
});
|
|
42
26
|
export type OAuthAccount = z.infer<typeof OAuthAccountSchema>;
|
|
@@ -56,9 +40,6 @@ export type UsageWindows = z.infer<typeof UsageWindowsSchema>;
|
|
|
56
40
|
const ModelInfoSchema = z.object({ id: z.string(), display: z.string() });
|
|
57
41
|
export type ModelInfo = z.infer<typeof ModelInfoSchema>;
|
|
58
42
|
|
|
59
|
-
/** usage.json - written by the statusLine shim, read by the Stop hook. Carries
|
|
60
|
-
* the two AGGREGATE windows (session=fiveHour, week-all-models=sevenDay) plus
|
|
61
|
-
* the active model. Per-model caps live in ModelUsageState (from `/usage`). */
|
|
62
43
|
export const UsageStateSchema = UsageWindowsSchema.extend({
|
|
63
44
|
org: z.string().nullable(),
|
|
64
45
|
ts: z.number(),
|
|
@@ -66,22 +47,14 @@ export const UsageStateSchema = UsageWindowsSchema.extend({
|
|
|
66
47
|
});
|
|
67
48
|
export type UsageState = z.infer<typeof UsageStateSchema>;
|
|
68
49
|
|
|
69
|
-
/** model-usage.json - per-model weekly caps parsed from `claude -p '/usage'`,
|
|
70
|
-
* TTL-cached so we don't poll every turn. Keyed by model display name ("Fable"). */
|
|
71
50
|
export const ModelUsageStateSchema = z.object({
|
|
72
51
|
perModel: z.record(z.string(), UsageWindowSchema).default({}),
|
|
73
52
|
org: z.string().nullable(),
|
|
74
53
|
ts: z.number(),
|
|
75
|
-
/** when the rows were actually MEASURED. `ts` is the write time and drives
|
|
76
|
-
* the probe-TTL anti-storm, so a failed probe re-stamps it while carrying
|
|
77
|
-
* the OLD rows forward - dating those rows by ts rolled the null-reset
|
|
78
|
-
* self-bound forward on every failed probe (closing-review catch). Absent
|
|
79
|
-
* on records predating this field: readers fall back to ts. */
|
|
80
54
|
sampledAt: z.number().optional(),
|
|
81
55
|
});
|
|
82
56
|
export type ModelUsageState = z.infer<typeof ModelUsageStateSchema>;
|
|
83
57
|
|
|
84
|
-
/** A parked account in the pool (accounts.json - NON-secret). */
|
|
85
58
|
export const AccountSchema = z.object({
|
|
86
59
|
accountUuid: z.string(),
|
|
87
60
|
email: z.string(),
|
|
@@ -92,23 +65,11 @@ export const AccountSchema = z.object({
|
|
|
92
65
|
addedAt: z.string(),
|
|
93
66
|
lastUsage: UsageWindowsSchema.optional(),
|
|
94
67
|
lastPerModel: z.record(z.string(), UsageWindowSchema).optional(),
|
|
95
|
-
/** epoch ms of the sample behind lastPerModel SPECIFICALLY: the aggregate
|
|
96
|
-
* stamp (lastUsageAt) advances on every engaged evaluation while the
|
|
97
|
-
* per-model rows refresh only when a gated family is measured, so dating
|
|
98
|
-
* the rows by lastUsageAt inflated the null-reset self-bound by days
|
|
99
|
-
* (closing-review catch). Absent on records predating this field: readers
|
|
100
|
-
* fall back to lastUsageAt, the previous (over-)approximation. */
|
|
101
68
|
lastPerModelAt: z.number().optional(),
|
|
102
|
-
/** epoch ms of the sample behind the AGGREGATE lastUsage windows (per-model
|
|
103
|
-
* rows date by lastPerModelAt above). resetsAt values are absolute epochs
|
|
104
|
-
* (UTC-anchored), so even an old snapshot still resolves to correct
|
|
105
|
-
* resets - display it as a dated cache, never discard. */
|
|
106
69
|
lastUsageAt: z.number().optional(),
|
|
70
|
+
enforcedUntil: z.number().optional(),
|
|
107
71
|
needsReauth: z.boolean().optional(),
|
|
108
72
|
subscriptionType: z.string().optional(),
|
|
109
|
-
/** the blob's rate-limit tier id (e.g. "default_claude_max_20x"): the only
|
|
110
|
-
* field that distinguishes max 5x from max 20x (subscriptionType is just
|
|
111
|
-
* "max" for both). Refreshed on every verified sample. */
|
|
112
73
|
rateLimitTier: z.string().optional(),
|
|
113
74
|
});
|
|
114
75
|
export type Account = z.infer<typeof AccountSchema>;
|
|
@@ -119,22 +80,21 @@ export const AccountsIndexSchema = z.object({
|
|
|
119
80
|
accounts: z.array(AccountSchema).default([]),
|
|
120
81
|
});
|
|
121
82
|
|
|
122
|
-
/** lastswap.json - epoch ms of the last credential swap. Its own tiny file (not
|
|
123
|
-
* accounts.json) so the statusLine shim reads a few bytes per tick and no other
|
|
124
|
-
* index writer can clobber it. Absent = no swap has ever run. */
|
|
125
83
|
export const LastSwapSchema = z.object({ ts: z.number() });
|
|
126
84
|
export type AccountsIndex = z.infer<typeof AccountsIndexSchema>;
|
|
127
85
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
86
|
+
export const NextCheckSchema = z.object({ dueAt: z.number(), ts: z.number() });
|
|
87
|
+
|
|
88
|
+
export const EnforcedLimitSchema = z.object({
|
|
89
|
+
org: z.string(),
|
|
90
|
+
family: z.string().nullable(),
|
|
91
|
+
resetsAt: z.number().nullable(),
|
|
92
|
+
windowMs: z.number(),
|
|
93
|
+
});
|
|
94
|
+
export type EnforcedLimit = z.infer<typeof EnforcedLimitSchema>;
|
|
95
|
+
|
|
134
96
|
export const ThresholdsSchema = z.object({
|
|
135
|
-
/** 5h session window. */
|
|
136
97
|
session: z.number().min(0).max(100),
|
|
137
|
-
/** 7-day aggregate AND per-model weekly caps. */
|
|
138
98
|
weekly: z.number().min(0).max(100),
|
|
139
99
|
});
|
|
140
100
|
export type Thresholds = z.infer<typeof ThresholdsSchema>;
|
|
@@ -142,74 +102,34 @@ export type Thresholds = z.infer<typeof ThresholdsSchema>;
|
|
|
142
102
|
export const ConfigSchema = z
|
|
143
103
|
.object({
|
|
144
104
|
thresholds: ThresholdsSchema,
|
|
145
|
-
/** LAYER 2 - the wall bars. `thresholds` (Layer 1) screen normal
|
|
146
|
-
* account-to-account switching and deliberately leave headroom; these are
|
|
147
|
-
* the true-wall bars the decision falls back to ONLY once every account is
|
|
148
|
-
* exhausted at Layer 1. Below the wall a session holds its seat and pumps
|
|
149
|
-
* the last drops; a window at/over the wall (default 100 = the server's own
|
|
150
|
-
* limit, the same figure /rate-limit-options reads) is genuinely spent and
|
|
151
|
-
* the pool moves to the next account, parking only when all are walled.
|
|
152
|
-
* Set equal to `thresholds` to disable Layer 2 (hardBars subtracts the same
|
|
153
|
-
* projectionMargin as effectiveBars, so equal thresholds collapse to one
|
|
154
|
-
* effective bar and Layer 2 has no band to act in). At the default margin 0
|
|
155
|
-
* the wall is the literal 100. */
|
|
156
105
|
hardThresholds: ThresholdsSchema,
|
|
157
106
|
claudeBin: z.string(),
|
|
158
|
-
/** the real codex binary (empty = resolve from PATH); pinned by `init --codex`. */
|
|
159
107
|
codexBin: z.string(),
|
|
160
108
|
policy: z.object({
|
|
161
|
-
/** percent margin subtracted from every bar (effectiveBars); above 100 the
|
|
162
|
-
* effective bars go negative and everything reads exhausted, so bounded. */
|
|
163
109
|
projectionMargin: z.number().min(0).max(100),
|
|
164
|
-
/** session-used % at which the greedy convergence engages: from here on,
|
|
165
|
-
* every evaluation swaps to the usable account furthest behind its weekly
|
|
166
|
-
* pace whenever that beats the current one (idempotent; current keeps its
|
|
167
|
-
* seat on ties). Below the floor a fresh session rides its account. */
|
|
168
110
|
greedySessionFloor: z.number().min(0).max(100),
|
|
169
|
-
/** models whose PER-MODEL weekly cap should trigger a switch (display names, lowercased). */
|
|
170
111
|
switchModels: z.array(z.string()),
|
|
171
|
-
/** how long a `/usage` per-model poll stays fresh before we re-poll (ms). */
|
|
172
112
|
usagePollTtlMs: z.number().int().positive(),
|
|
173
|
-
/** when every account is depleted, auto-wait for a reset only if it is within this window (ms). */
|
|
174
113
|
maxWaitMs: z.number().int().positive(),
|
|
175
114
|
}),
|
|
176
115
|
})
|
|
177
|
-
// Cross-field (review catch, PR #31): effectiveBars subtracts the margin
|
|
178
|
-
// from each threshold, and a bar at or below zero makes EVERY nonnegative
|
|
179
|
-
// usage percentage read as exhausted - the whole pool looks depleted and the
|
|
180
|
-
// switch path churns. Per-field bounds alone cannot see this.
|
|
181
116
|
.refine((cfg) => cfg.policy.projectionMargin < Math.min(cfg.thresholds.session, cfg.thresholds.weekly), {
|
|
182
117
|
message: "policy.projectionMargin must be strictly below both thresholds (effectiveBars would hit zero and every account would read as exhausted)",
|
|
183
118
|
})
|
|
184
|
-
// The wall must sit at or above each screening bar. A wall BELOW its
|
|
185
|
-
// screening bar would make Layer 2 "usable" a stricter test than Layer 1
|
|
186
|
-
// screening - the pool could reach the wall fallback and find every account
|
|
187
|
-
// already over the (lower) wall, parking earlier than Layer 1 alone would.
|
|
188
|
-
// Equal is allowed and simply disables Layer 2 for that window.
|
|
189
119
|
.refine((cfg) => cfg.hardThresholds.session >= cfg.thresholds.session && cfg.hardThresholds.weekly >= cfg.thresholds.weekly, {
|
|
190
120
|
message: "hardThresholds (the Layer 2 wall) must be at or above thresholds (the Layer 1 screening bars) for both windows",
|
|
191
121
|
});
|
|
192
122
|
export type Config = z.infer<typeof ConfigSchema>;
|
|
193
123
|
|
|
194
|
-
/** The hook -> supervisor respawn marker. Written only for a depleted-pool
|
|
195
|
-
* wait (plain swaps adopt in place, no respawn). The marker FILE is keyed by
|
|
196
|
-
* the supervisor's PINNED session id (the path it watches - stable for the
|
|
197
|
-
* process's whole life), while `sessionId` carries the CURRENT transcript to
|
|
198
|
-
* resume: after /clear claude mints a new session id, and keying the file by
|
|
199
|
-
* the stdin sid orphaned every marker while the anticipatory pre-park still
|
|
200
|
-
* fired (closing-review HIGH catch; the codex marker was immune by exactly
|
|
201
|
-
* this construction). */
|
|
202
124
|
export const RespawnMarkerSchema = z.object({
|
|
203
125
|
account: z.string(),
|
|
204
126
|
ts: z.number(),
|
|
205
|
-
/** the supervisor waits until this epoch ms before relaunching. */
|
|
206
127
|
waitUntil: z.number(),
|
|
207
|
-
/** the transcript to `--resume`: the hook-stdin session id, which drifts
|
|
208
|
-
* from the pinned id after /clear. */
|
|
209
128
|
sessionId: z.string(),
|
|
129
|
+
prompt: z.string().optional(),
|
|
130
|
+
launchedAt: z.number().optional(),
|
|
210
131
|
});
|
|
211
132
|
|
|
212
|
-
/** rate_limits + model as they appear in statusLine stdin (epoch-seconds resets). */
|
|
213
133
|
export const RateLimitsStdinSchema = z.looseObject({
|
|
214
134
|
rate_limits: z
|
|
215
135
|
.looseObject({
|
|
@@ -221,11 +141,6 @@ export const RateLimitsStdinSchema = z.looseObject({
|
|
|
221
141
|
organizationUuid: z.string().optional(),
|
|
222
142
|
});
|
|
223
143
|
|
|
224
|
-
/** The statusLine stdin fields the native renderer consumes, on top of the
|
|
225
|
-
* rate-limit tee's needs. Loose + optional throughout: fields are null before
|
|
226
|
-
* the first API response and claude adds new ones freely. Each sub-object also
|
|
227
|
-
* `.catch(undefined)`es so a field that drifts to a wrong shape degrades to
|
|
228
|
-
* absent instead of failing the whole parse and erasing the info block. */
|
|
229
144
|
export const StatusLineStdinSchema = RateLimitsStdinSchema.extend({
|
|
230
145
|
workspace: z
|
|
231
146
|
.looseObject({
|
|
@@ -247,10 +162,6 @@ export const StatusLineStdinSchema = RateLimitsStdinSchema.extend({
|
|
|
247
162
|
effort: z.looseObject({ level: z.string().optional() }).nullable().optional().catch(undefined),
|
|
248
163
|
});
|
|
249
164
|
|
|
250
|
-
/** subagentStatusLine stdin: base session fields plus one entry per active
|
|
251
|
-
* subagent task (verified against the 2.1.214 bundle + docs 2026-07-18;
|
|
252
|
-
* tasks[].model/contextWindowSize need claude >= 2.1.205, effort >= 2.1.214).
|
|
253
|
-
* Same loose + catch degradation contract as StatusLineStdinSchema. */
|
|
254
165
|
export const SubagentStatusLineStdinSchema = z.looseObject({
|
|
255
166
|
tasks: z
|
|
256
167
|
.array(
|
|
@@ -269,7 +180,6 @@ export const SubagentStatusLineStdinSchema = z.looseObject({
|
|
|
269
180
|
.catch(undefined),
|
|
270
181
|
});
|
|
271
182
|
|
|
272
|
-
/** Success body of the OAuth refresh grant. */
|
|
273
183
|
export const RefreshResponseSchema = z.looseObject({
|
|
274
184
|
access_token: z.string(),
|
|
275
185
|
refresh_token: z.string().optional(),
|
|
@@ -279,19 +189,12 @@ export const RefreshResponseSchema = z.looseObject({
|
|
|
279
189
|
token_type: z.string().optional(),
|
|
280
190
|
});
|
|
281
191
|
|
|
282
|
-
/** Success body of GET /api/oauth/claude_cli/roles - the org a token ACTUALLY
|
|
283
|
-
* belongs to, independent of any stored label. */
|
|
284
192
|
export const RolesResponseSchema = z.looseObject({
|
|
285
193
|
organization_uuid: z.string(),
|
|
286
194
|
organization_name: z.string(),
|
|
287
195
|
});
|
|
288
196
|
export type RolesResponse = z.infer<typeof RolesResponseSchema>;
|
|
289
197
|
|
|
290
|
-
// ---- Codex pool ------------------------------------------------------------
|
|
291
|
-
|
|
292
|
-
/** `tokens` inside $CODEX_HOME/auth.json (verified against a live 0.144.4
|
|
293
|
-
* auth.json). Loose: preserve unknown siblings so a harvest and reinstall
|
|
294
|
-
* round-trip is lossless. */
|
|
295
198
|
const CodexTokensSchema = z.looseObject({
|
|
296
199
|
id_token: z.string(),
|
|
297
200
|
access_token: z.string(),
|
|
@@ -299,19 +202,12 @@ const CodexTokensSchema = z.looseObject({
|
|
|
299
202
|
account_id: z.string().optional(),
|
|
300
203
|
});
|
|
301
204
|
|
|
302
|
-
/** The whole auth.json. Loose: auth_mode, OPENAI_API_KEY (may be null), and
|
|
303
|
-
* future siblings ride along verbatim. */
|
|
304
205
|
export const CodexAuthJsonSchema = z.looseObject({
|
|
305
206
|
tokens: CodexTokensSchema,
|
|
306
207
|
last_refresh: z.string().optional(),
|
|
307
208
|
});
|
|
308
209
|
export type CodexAuthJson = z.infer<typeof CodexAuthJsonSchema>;
|
|
309
210
|
|
|
310
|
-
/** One rate-limit window as tokenmaxxing stores it: percent used, absolute
|
|
311
|
-
* epoch ms reset, and the server-declared duration. Codex windows are
|
|
312
|
-
* duration-driven (the weekly window is PRIMARY on plans whose 5h window was
|
|
313
|
-
* removed in July 2026), so classification must go by windowSeconds, never
|
|
314
|
-
* by primary/secondary position. */
|
|
315
211
|
const CodexWindowSchema = z.object({
|
|
316
212
|
usedPercentage: z.number(),
|
|
317
213
|
resetsAt: z.number().nullable(),
|
|
@@ -319,9 +215,6 @@ const CodexWindowSchema = z.object({
|
|
|
319
215
|
});
|
|
320
216
|
export type CodexWindow = z.infer<typeof CodexWindowSchema>;
|
|
321
217
|
|
|
322
|
-
/** Everything one free usage read yields: the token's OWN identity (the codex
|
|
323
|
-
* analog of the claude roles endpoint: labels drift, the token cannot lie)
|
|
324
|
-
* plus every rate-limit window, aggregate and per-limit-family. */
|
|
325
218
|
export const CodexUsageSchema = z.object({
|
|
326
219
|
accountId: z.string(),
|
|
327
220
|
email: z.string().nullable(),
|
|
@@ -331,19 +224,12 @@ export const CodexUsageSchema = z.object({
|
|
|
331
224
|
});
|
|
332
225
|
export type CodexUsage = z.infer<typeof CodexUsageSchema>;
|
|
333
226
|
|
|
334
|
-
/** A parked-credential file NAME, never a path. These are machine-written
|
|
335
|
-
* (`tokenmaxxing-codex-<id8>`), so a separator here means the index is
|
|
336
|
-
* corrupted - and `join(credsDir, credFile)` would normalize `../` right out
|
|
337
|
-
* of codex-creds, letting `rm --codex` unlink another file (review catch).
|
|
338
|
-
* Refusing at parse time matches the codex loaders' throw-on-unparsable
|
|
339
|
-
* contract and covers the read/write paths with the delete. */
|
|
340
227
|
const BareFileNameSchema = z
|
|
341
228
|
.string()
|
|
342
229
|
.refine((s) => s.length > 0 && s !== "." && s !== ".." && !s.includes("/") && !s.includes("\\"), {
|
|
343
230
|
message: "credFile must be a bare file name, not a path",
|
|
344
231
|
});
|
|
345
232
|
|
|
346
|
-
/** A pooled codex account (codex-accounts.json - NON-secret). */
|
|
347
233
|
export const CodexAccountSchema = z.object({
|
|
348
234
|
accountId: z.string(),
|
|
349
235
|
email: z.string().nullable(),
|
|
@@ -369,30 +255,17 @@ export const CodexAccountsIndexSchema = z.object({
|
|
|
369
255
|
});
|
|
370
256
|
export type CodexAccountsIndex = z.infer<typeof CodexAccountsIndexSchema>;
|
|
371
257
|
|
|
372
|
-
/** Codex Stop-hook stdin (verified against the 0.144.4 binary wire schema and
|
|
373
|
-
* the official hooks reference): only what the swap trigger consumes. */
|
|
374
258
|
export const CodexStopStdinSchema = z.looseObject({
|
|
375
259
|
session_id: z.string().optional(),
|
|
376
260
|
hook_event_name: z.string().optional(),
|
|
377
261
|
});
|
|
378
262
|
|
|
379
|
-
/** The codex supervisor's respawn marker payload: which session to resume and
|
|
380
|
-
* under which account label (for the switch banner). */
|
|
381
263
|
export const CodexRespawnMarkerSchema = z.object({
|
|
382
264
|
account: z.string(),
|
|
383
265
|
sessionId: z.string().nullable(),
|
|
384
266
|
ts: z.number(),
|
|
385
267
|
});
|
|
386
268
|
|
|
387
|
-
/** A cross-session reconcile signal (owner decisions 2026-07-20): the
|
|
388
|
-
* deciding actor saw the addressed supervisor's session running on this
|
|
389
|
-
* pooled NON-LIVE account - healthy or not (a non-live session cannot
|
|
390
|
-
* refresh cross-account and wedges at token expiry) - while the live seat
|
|
391
|
-
* is usable. The session's OWN Stop hook consumes it at its next turn
|
|
392
|
-
* boundary - the only safe respawn point - promoting it into a respawn
|
|
393
|
-
* marker with the session id its stdin alone carries. `accountId` doubles
|
|
394
|
-
* as the staleness guard: a session that already moved accounts drops the
|
|
395
|
-
* signal instead of respawning. */
|
|
396
269
|
export const CodexReconcileMarkerSchema = z.object({
|
|
397
270
|
accountId: z.string(),
|
|
398
271
|
ts: z.number(),
|