tokenmaxxing 1.2.0 → 1.2.1
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 +1 -1
- package/package.json +1 -1
- package/src/entries/codexsupervisor.ts +5 -2
- package/src/entries/supervisor.ts +8 -2
- package/src/lib/claudebin.ts +8 -0
- package/src/lib/slackstate.ts +2 -1
- package/src/sdk.ts +9 -5
package/DESIGN.md
CHANGED
|
@@ -107,7 +107,7 @@ A local Socket Mode daemon (no public URL) that turns Slack threads into Claude
|
|
|
107
107
|
- **One Slack app per daemon** (live incident 2026-07-20): Slack Socket Mode delivers each event envelope to exactly one of an app's open connections, so two serve daemons sharing one Slack app steal each other's events - mentions usually survive (Slack emits `app_mention` plus `message.channels` for one mention and the Chat SDK dedupes them per-process), but a non-mention thread follow-up rides a single envelope and silently dies when the wrong daemon receives it. No code can route Slack's load-balancing (the hello frame's `num_connections` is discarded by `@slack/socket-mode` before any event fires), so the constraint is documented (docs limitations page) and diagnosed: `serve.unlinked_channel` logs once per channel per run with the shared-app explanation. Every host needs its own Slack app with its own tokens.
|
|
108
108
|
- **Todo checklist card**: TodoWrite is bookkeeping, not a real tool run, so it never opens a generic card. Instead each stream gets one stable-id "Todos" card (id `todos`, subagents `todos-<parent_tool_use_id>`) that updates in place on every TodoWrite: `✅ content` for completed, `🔄 activeForm` for the in-progress item (live narration), `⬜ content` for pending (the Chat SDK Plan object's own iconography); card status goes complete only when every item is completed. The "Todos have been modified successfully" tool_result is suppressed, but a FAILED TodoWrite flips the card to error (the optimistic checklist must not claim a state that never took effect). Card ids do not carry across messages, so a TodoWrite after a dead-segment recovery starts a fresh card in the follow-on message: accepted, the latest state is always in the newest message. claude >= 2.1.142 defaults to the structured Task tools and never emits TodoWrite, so relayThread sets `CLAUDE_CODE_ENABLE_TASKS: "0"` in the spawn env (the documented opt-out; without it the checklist card is inert).
|
|
109
109
|
- **Terminal echo** (2026-07-18): the daemon registers a `log()` echo (`setLogEcho` in log.ts, off by default so hooks and the statusline keep their stdout protocols clean), so every event while it runs - serve.* plus the in-process swap/decision events from `ensureBestAccount()`/`stopHookCheck` - also prints one colored line in the foreground terminal (`formatLogLine`: dim HH:MM:SS, event painted red/yellow/cyan by structural endsWith severity, redacted fields). `serve.turn_done`/`serve.turn_failed` (with seconds) close every relayed turn, so a foreground `xx serve` is observable without tailing `tokenmaxxing.log`.
|
|
110
|
-
- **Live-verified end-to-end** (2026-07-18,
|
|
110
|
+
- **Live-verified end-to-end** (2026-07-18, in a live workspace channel): mention opens a session (worktree-per-thread at the time), replies stream, thread follow-ups resume with context, cards + fenced code render, segmentation and queue folding behave. Plus the hermetic suite: schemas/links, stream mapping, fail-fast paths.
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Automatic Claude Code account switching: pool multiple accounts and hot-swap when quota fills, resuming your session on the fresh account.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,7 +13,7 @@ import { join } from "node:path";
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { codexPaths, paths } from "../lib/paths.ts";
|
|
15
15
|
import { withLock } from "../lib/lock.ts";
|
|
16
|
-
import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, WRAP_RATE_MAX, WRAP_RATE_WINDOW_MS, wrapDepth, wrapperEntryRateTripped } from "../lib/claudebin.ts";
|
|
16
|
+
import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, UNMANAGED_ENV, WRAP_DEPTH_ENV, WRAP_RATE_MAX, WRAP_RATE_WINDOW_MS, wrapDepth, wrapperEntryRateTripped } from "../lib/claudebin.ts";
|
|
17
17
|
import { resolveRealCodex } from "../lib/codexbin.ts";
|
|
18
18
|
import { clearCodexPresence, writeCodexPresence } from "../lib/codexpresence.ts";
|
|
19
19
|
import { liveCodexAccountId } from "../lib/codexsample.ts";
|
|
@@ -92,7 +92,10 @@ export async function runCodexSupervisor(input: { argv: string[] }): Promise<num
|
|
|
92
92
|
const real = resolveRealCodex();
|
|
93
93
|
const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(depth + 1) };
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
// The unmanaged-zone sentinel forces passthrough regardless of argv, exactly
|
|
96
|
+
// like the claude shim: a serve turn's agent running `codex exec` must reach
|
|
97
|
+
// the real codex instead of dying at the shared depth cap.
|
|
98
|
+
if (!shouldManageCodex({ argv }) || process.env[UNMANAGED_ENV]) {
|
|
96
99
|
// STRIP the supervisor pairing env from unmanaged spawns: a nested codex
|
|
97
100
|
// launched from inside a supervised session (e.g. its agent running
|
|
98
101
|
// `codex exec ...`) would otherwise inherit the OUTER session's id, and
|
|
@@ -12,7 +12,7 @@ import { join } from "node:path";
|
|
|
12
12
|
import { maxBy } from "es-toolkit";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { paths } from "../lib/paths.ts";
|
|
15
|
-
import { LOOP_DIAGNOSIS, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, WRAP_RATE_MAX, WRAP_RATE_WINDOW_MS, resolveRealClaude, wrapDepth, wrapperEntryRateTripped } from "../lib/claudebin.ts";
|
|
15
|
+
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";
|
|
16
16
|
import { saveTermios, restoreTermios } from "../lib/tty.ts";
|
|
17
17
|
import { loadSessionFlags, pruneStaleSessions, saveSessionFlags } from "../lib/sessions.ts";
|
|
18
18
|
import { RespawnMarkerSchema } from "../lib/types.ts";
|
|
@@ -274,7 +274,13 @@ export async function runSupervisor(argv: string[]): Promise<number> {
|
|
|
274
274
|
const childEnv = { ...process.env, [WRAP_DEPTH_ENV]: String(depth + 1) };
|
|
275
275
|
|
|
276
276
|
// Pass-through: no session management, no respawn - exact stock behavior.
|
|
277
|
-
|
|
277
|
+
// The unmanaged-zone sentinel (pooledSpawnEnv) forces it regardless of argv:
|
|
278
|
+
// everything below an SDK-driven session runs the real claude unsupervised,
|
|
279
|
+
// so a repo script invoking `claude` from inside a serve turn works instead
|
|
280
|
+
// of dying at the depth cap. The sentinel rides childEnv, so the whole
|
|
281
|
+
// subtree stays unmanaged, and depth still counts wrapper entries toward
|
|
282
|
+
// the cap above, keeping a poisoned pin inside the zone bounded.
|
|
283
|
+
if (!info.manage || process.env[UNMANAGED_ENV]) {
|
|
278
284
|
// STRIP the supervision pairing env (mirrors the codex shim's passthrough
|
|
279
285
|
// arm, closing-review catch): a nested unmanaged claude inside a
|
|
280
286
|
// supervised session (e.g. the agent running `claude -p ...`) would
|
package/src/lib/claudebin.ts
CHANGED
|
@@ -15,6 +15,14 @@ import { writeFileAtomic } from "./atomic.ts";
|
|
|
15
15
|
* fork-bombing the machine (2026-07-12: ~1800 runaway bun processes). */
|
|
16
16
|
export const WRAP_DEPTH_ENV = "TOKENMAXXING_WRAP_DEPTH";
|
|
17
17
|
export const MAX_WRAP_DEPTH = 5;
|
|
18
|
+
/** "No session management anywhere below this env": the wrapper passes a
|
|
19
|
+
* `claude`/`codex` invocation straight through to the real binary, unmanaged.
|
|
20
|
+
* Set by pooledSpawnEnv so an SDK-driven session's descendants (an agent
|
|
21
|
+
* running `bun run validate` whose script invokes `claude -p`) still work
|
|
22
|
+
* instead of dying at the depth cap, which is a LOOP diagnostic, not an
|
|
23
|
+
* unmanaged-zone marker. Depth keeps incrementing per wrapper entry under
|
|
24
|
+
* this sentinel, so a poisoned pin inside the zone still aborts at the cap. */
|
|
25
|
+
export const UNMANAGED_ENV = "TOKENMAXXING_UNMANAGED";
|
|
18
26
|
/** Stable fragment of the loop-abort diagnostic; verifyRealClaude greps a
|
|
19
27
|
* child's stderr for it to name the failure precisely. */
|
|
20
28
|
export const LOOP_DIAGNOSIS = "wrapper re-entered without reaching the real claude";
|
package/src/lib/slackstate.ts
CHANGED
|
@@ -310,7 +310,8 @@ export function bareChannelId(id: string): string {
|
|
|
310
310
|
* Strip a leading Slack mention OF THE BOT from message text. Two forms: raw
|
|
311
311
|
* mrkdwn ("<@U0123> rest") and the bare form the Chat SDK's incoming
|
|
312
312
|
* mrkdwn->markdown normalization produces ("@U0123 rest") - observed live
|
|
313
|
-
* 2026-07-18 when a relayed prompt arrived starting
|
|
313
|
+
* 2026-07-18 when a relayed prompt arrived starting with the bot's bare
|
|
314
|
+
* "@U..." token. Only a
|
|
314
315
|
* token whose id equals botUserId is stripped (review catch 2026-07-18:
|
|
315
316
|
* handleTurn runs this over every subscribed message, so a follow-up starting
|
|
316
317
|
* with a colleague's mention must keep it - the prompt would otherwise lose
|
package/src/sdk.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// dependency set (zod, es-toolkit, ky).
|
|
15
15
|
|
|
16
16
|
import { z } from "zod";
|
|
17
|
-
import {
|
|
17
|
+
import { UNMANAGED_ENV, resolveRealClaude } from "./lib/claudebin.ts";
|
|
18
18
|
import { evaluateAndMaybeSwap, type SwapDecision } from "./lib/decide.ts";
|
|
19
19
|
import { CRED_ENV_OVERRIDES } from "./lib/usage.ts";
|
|
20
20
|
import { log } from "./lib/log.ts";
|
|
@@ -52,9 +52,13 @@ const AMBIENT_STORE_VARS = ["CLAUDE_SECURESTORAGE_CONFIG_DIR", "CLAUDE_CONFIG_DI
|
|
|
52
52
|
* The env an SDK-spawned claude must run under to meter the POOLED live
|
|
53
53
|
* credential: every ambient credential override is scrubbed (claude honors
|
|
54
54
|
* them BEFORE its keychain/file lookup, so one inherited ANTHROPIC_API_KEY
|
|
55
|
-
* silently meters the wrong account), and the
|
|
56
|
-
* so a
|
|
57
|
-
*
|
|
55
|
+
* silently meters the wrong account), and the unmanaged-zone sentinel is set
|
|
56
|
+
* so a descendant invoking `claude`/`codex` through the on-PATH wrapper gets a
|
|
57
|
+
* passthrough to the real binary instead of nested supervision (or, before
|
|
58
|
+
* this sentinel existed, a depth-cap abort that broke legitimate nested runs
|
|
59
|
+
* like a repo's `bun run validate` invoking `claude -p`). The wrap depth is
|
|
60
|
+
* NOT preset: it keeps counting real wrapper entries, so a poisoned claudeBin
|
|
61
|
+
* pin below here still dies at the cap.
|
|
58
62
|
*
|
|
59
63
|
* Returns a FULL environment, not a patch: the Agent SDK's Options.env
|
|
60
64
|
* REPLACES the subprocess env rather than merging over process.env (verified
|
|
@@ -69,7 +73,7 @@ export function pooledSpawnEnv(): Record<string, string> {
|
|
|
69
73
|
);
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
|
-
const env: Record<string, string> = { ...process.env, [
|
|
76
|
+
const env: Record<string, string> = { ...process.env, [UNMANAGED_ENV]: "1" };
|
|
73
77
|
for (const k of CRED_ENV_OVERRIDES) delete env[k];
|
|
74
78
|
return env;
|
|
75
79
|
}
|