@inetafrica/open-claudia 3.4.12 → 3.4.13
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/CHANGELOG.md +6 -0
- package/core/handlers.js +10 -0
- package/core/providers/codex-events.js +3 -1
- package/core/providers/codex-rollout.js +312 -0
- package/core/providers/codex.js +12 -0
- package/core/runner.js +90 -26
- package/package.json +2 -2
- package/test-provider-codex.js +12 -0
- package/test-provider-events.js +1 -1
- package/test-provider-language.js +3 -1
- package/test-usage-accounting.js +45 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.4.13
|
|
4
|
+
|
|
5
|
+
- **Codex is now measured the way Claude is.** After each Codex turn the bot reads Codex's own per-request token log (`$CODEX_HOME/sessions/…/rollout-*-<thread>.jsonl`) and uses the last request's size as the live context, instead of the turn's input summed over every tool call. That sum (33M on one turn) tripped auto-compact after almost every substantial Codex turn — 8 needless compactions on 2026-09-05, each a summary run plus a cold restart — while Codex's real window occupancy never exceeded 245k of its 258k window. Codex sessions now compact at 80% of the window Codex reports.
|
|
6
|
+
- **Codex usage was mis-recorded three ways; all fixed.** (1) `turn.completed` usage is per turn by Codex's own definition, but the ledger treated it as a running total and subtracted the previous turn — 9 of 36 Codex turns that day were logged as zero. (2) OpenAI counts cached tokens inside `input_tokens`; the ledger and `/usage` now report uncached input for both providers (the raw comparison read 89.7M vs 4.5k for what was really 2.4M vs 0.8M). (3) Sub-agent threads Codex spawns with `spawn_agent` never appeared anywhere: the day's ten hidden threads read ~240M tokens and took a weekly quota from 75% to 100% in two hours. They are now attributed to the parent turn (`subagentThreads`, `subagentInputTokens`, …) and shown by `/usage`.
|
|
7
|
+
- **Quota and window visibility.** Ledger rows carry `contextWindowTokens`, `requests`, `providerCompactions` (Codex's own compactions) and the plan quota snapshot (`quotaUsedPercent`, `quotaResetsAt`); `/usage` prints window occupancy, sub-agent totals and quota used.
|
|
8
|
+
|
|
3
9
|
## 3.4.12
|
|
4
10
|
|
|
5
11
|
- Keep incoming calls on their per-attempt admission policy when the organization canary setting changes; managed access supplies the assigned gateway and TURN configuration. Owner-only audio calling remains enforced.
|
package/core/handlers.js
CHANGED
|
@@ -1684,6 +1684,16 @@ register({
|
|
|
1684
1684
|
`Cost: $${u.costUsd.toFixed(4)}`,
|
|
1685
1685
|
`Last turn context: ${fmt(u.lastInputTokens)}`,
|
|
1686
1686
|
];
|
|
1687
|
+
const occupancy = u.liveContextTokens || u.lastInputTokens || 0;
|
|
1688
|
+
if (u.contextWindowTokens > 0) {
|
|
1689
|
+
lines.push(`Context window: ${fmt(occupancy)} of ${fmt(u.contextWindowTokens)} (${Math.round((100 * occupancy) / u.contextWindowTokens)}%)`);
|
|
1690
|
+
}
|
|
1691
|
+
if (u.subagentThreads > 0) {
|
|
1692
|
+
lines.push(`Provider sub-agents: ${u.subagentThreads} thread${u.subagentThreads === 1 ? "" : "s"} · ${fmt(u.subagentInputTokens || 0)} in / ${fmt(u.subagentOutputTokens || 0)} out (spawned by the model; not in the totals above)`);
|
|
1693
|
+
}
|
|
1694
|
+
if (Number.isFinite(u.quotaUsedPercent)) {
|
|
1695
|
+
lines.push(`Plan quota used: ${u.quotaUsedPercent}%${u.quotaResetsAt ? ` (resets ${String(u.quotaResetsAt).slice(0, 16).replace("T", " ")}Z)` : ""}`);
|
|
1696
|
+
}
|
|
1687
1697
|
if (u.lastInputTokens > 200000) {
|
|
1688
1698
|
const threshold = effectiveCompactThreshold(currentState());
|
|
1689
1699
|
if (threshold === 0) {
|
|
@@ -102,7 +102,9 @@ function normalizeCodexEvent(rawEvent, state = createCodexParserState()) {
|
|
|
102
102
|
if (state.terminal) return [];
|
|
103
103
|
state.terminal = true;
|
|
104
104
|
const events = [];
|
|
105
|
-
|
|
105
|
+
// Per-turn by Codex's own definition ("the usage of tokens during a turn",
|
|
106
|
+
// exec_events.rs) — not a session total; see runner.normalizeCodexUsage.
|
|
107
|
+
const usage = normalizedUsage(rawEvent.usage, "turn", state.seenUsage);
|
|
106
108
|
if (usage) events.push(usage);
|
|
107
109
|
events.push({
|
|
108
110
|
type: "result",
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Codex writes one JSONL "rollout" per thread under $CODEX_HOME/sessions/
|
|
4
|
+
// YYYY/MM/DD/rollout-<local start>-<thread id>.jsonl. Every model request
|
|
5
|
+
// appends an event_msg/token_count record whose info.last_token_usage is that
|
|
6
|
+
// single request (input_tokens INCLUDES cached_input_tokens, so it is the
|
|
7
|
+
// window occupancy after the request), plus info.model_context_window and a
|
|
8
|
+
// rate_limits snapshot (the ChatGPT-plan quota). Codex's own compactions land
|
|
9
|
+
// as top-level "compacted" records, and threads the model spawned with
|
|
10
|
+
// spawn_agent get their own rollout whose session_meta carries
|
|
11
|
+
// thread_source "subagent" and parent_thread_id.
|
|
12
|
+
//
|
|
13
|
+
// `codex exec --json` itself only reports the per-turn total (turn.completed),
|
|
14
|
+
// so this reader is how Open Claudia measures Codex the way it measures
|
|
15
|
+
// Claude: peak/last single-request context instead of the turn-summed input,
|
|
16
|
+
// and sub-agent threads the turn total never mentions (probed live
|
|
17
|
+
// 2026-09-05: three parallel sub-agents took a weekly quota from 75% to 100%
|
|
18
|
+
// while the ledger saw none of it).
|
|
19
|
+
|
|
20
|
+
const fs = require("fs");
|
|
21
|
+
const os = require("os");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
|
|
24
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
25
|
+
const DEFAULT_LOOKBACK_DAYS = 3;
|
|
26
|
+
// session_meta carries the full base instructions (~22 KB observed on
|
|
27
|
+
// 0.153.4), so the first line is read in chunks until its newline.
|
|
28
|
+
const FIRST_LINE_CHUNK_BYTES = 65536;
|
|
29
|
+
const FIRST_LINE_MAX_BYTES = 4 * 1024 * 1024;
|
|
30
|
+
|
|
31
|
+
function resolveCodexHome(env = process.env) {
|
|
32
|
+
const raw = (env && env.CODEX_HOME) || process.env.CODEX_HOME;
|
|
33
|
+
return raw ? path.resolve(String(raw)) : path.join(os.homedir(), ".codex");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pad(n) { return String(n).padStart(2, "0"); }
|
|
37
|
+
|
|
38
|
+
function dayDirs(codexHome, { now = Date.now(), days = DEFAULT_LOOKBACK_DAYS } = {}) {
|
|
39
|
+
// Rollout directories use the local date; include the UTC date too so a
|
|
40
|
+
// thread that straddles midnight in either clock is still found.
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const dirs = [];
|
|
43
|
+
for (let back = 0; back < days; back += 1) {
|
|
44
|
+
const d = new Date(now - back * DAY_MS);
|
|
45
|
+
const candidates = [
|
|
46
|
+
`${d.getFullYear()}/${pad(d.getMonth() + 1)}/${pad(d.getDate())}`,
|
|
47
|
+
`${d.getUTCFullYear()}/${pad(d.getUTCMonth() + 1)}/${pad(d.getUTCDate())}`,
|
|
48
|
+
];
|
|
49
|
+
for (const rel of candidates) {
|
|
50
|
+
if (seen.has(rel)) continue;
|
|
51
|
+
seen.add(rel);
|
|
52
|
+
const full = path.join(codexHome, "sessions", rel);
|
|
53
|
+
try { if (fs.statSync(full).isDirectory()) dirs.push(full); } catch (_) {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return dirs;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function listRollouts(codexHome, options = {}) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const dir of dayDirs(codexHome, options)) {
|
|
62
|
+
let names = [];
|
|
63
|
+
try { names = fs.readdirSync(dir); } catch (_) { continue; }
|
|
64
|
+
for (const name of names) {
|
|
65
|
+
if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
|
|
66
|
+
out.push(path.join(dir, name));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function threadIdOf(filePath) {
|
|
73
|
+
const base = path.basename(filePath, ".jsonl");
|
|
74
|
+
// rollout-2026-09-05T19-19-01-<uuid>: the uuid is the trailing 36 chars.
|
|
75
|
+
const match = base.match(/-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i);
|
|
76
|
+
return match ? match[1] : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findRolloutFile(threadId, { codexHome = resolveCodexHome(), now = Date.now(), days = DEFAULT_LOOKBACK_DAYS } = {}) {
|
|
80
|
+
if (!threadId) return null;
|
|
81
|
+
const suffix = `-${String(threadId)}.jsonl`;
|
|
82
|
+
for (const file of listRollouts(codexHome, { now, days })) {
|
|
83
|
+
if (file.endsWith(suffix)) return file;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseTs(value) {
|
|
89
|
+
const t = Date.parse(String(value || ""));
|
|
90
|
+
return Number.isFinite(t) ? t : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readSessionMeta(filePath) {
|
|
94
|
+
let fd = null;
|
|
95
|
+
try {
|
|
96
|
+
fd = fs.openSync(filePath, "r");
|
|
97
|
+
const chunks = [];
|
|
98
|
+
let total = 0;
|
|
99
|
+
let firstLine = null;
|
|
100
|
+
while (total < FIRST_LINE_MAX_BYTES) {
|
|
101
|
+
const buffer = Buffer.alloc(FIRST_LINE_CHUNK_BYTES);
|
|
102
|
+
const read = fs.readSync(fd, buffer, 0, FIRST_LINE_CHUNK_BYTES, total);
|
|
103
|
+
if (read <= 0) break;
|
|
104
|
+
chunks.push(buffer.subarray(0, read));
|
|
105
|
+
total += read;
|
|
106
|
+
const newline = buffer.subarray(0, read).indexOf(0x0a);
|
|
107
|
+
if (newline >= 0) {
|
|
108
|
+
firstLine = Buffer.concat(chunks).toString("utf8", 0, total - read + newline);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (firstLine === null) firstLine = Buffer.concat(chunks).toString("utf8");
|
|
113
|
+
const record = JSON.parse(firstLine);
|
|
114
|
+
if (record && record.type === "session_meta" && record.payload) return normalizeSessionMeta(record);
|
|
115
|
+
return null;
|
|
116
|
+
} catch (_) {
|
|
117
|
+
return null;
|
|
118
|
+
} finally {
|
|
119
|
+
if (fd !== null) { try { fs.closeSync(fd); } catch (_) {} }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeSessionMeta(record) {
|
|
124
|
+
const p = record.payload || {};
|
|
125
|
+
return {
|
|
126
|
+
threadId: p.id || p.session_id || null,
|
|
127
|
+
timestamp: parseTs(p.timestamp || record.timestamp),
|
|
128
|
+
threadSource: typeof p.thread_source === "string" ? p.thread_source : (p.thread_source ? "subagent" : null),
|
|
129
|
+
parentThreadId: p.parent_thread_id || null,
|
|
130
|
+
agentNickname: p.agent_nickname || null,
|
|
131
|
+
cliVersion: p.cli_version || null,
|
|
132
|
+
originator: p.originator || null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function num(value) {
|
|
137
|
+
const n = Number(value);
|
|
138
|
+
return Number.isFinite(n) ? n : 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function usageOf(raw) {
|
|
142
|
+
if (!raw || typeof raw !== "object") return null;
|
|
143
|
+
return {
|
|
144
|
+
input: num(raw.input_tokens),
|
|
145
|
+
cached: num(raw.cached_input_tokens),
|
|
146
|
+
cacheWrite: num(raw.cache_write_input_tokens),
|
|
147
|
+
output: num(raw.output_tokens),
|
|
148
|
+
reasoning: num(raw.reasoning_output_tokens),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function emptyTotals() {
|
|
153
|
+
return { input: 0, cached: 0, cacheWrite: 0, output: 0, reasoning: 0 };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function addTotals(target, usage) {
|
|
157
|
+
target.input += usage.input;
|
|
158
|
+
target.cached += usage.cached;
|
|
159
|
+
target.cacheWrite += usage.cacheWrite;
|
|
160
|
+
target.output += usage.output;
|
|
161
|
+
target.reasoning += usage.reasoning;
|
|
162
|
+
return target;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function rateLimitsOf(raw) {
|
|
166
|
+
if (!raw || typeof raw !== "object") return null;
|
|
167
|
+
const primary = raw.primary && typeof raw.primary === "object" ? raw.primary : null;
|
|
168
|
+
const secondary = raw.secondary && typeof raw.secondary === "object" ? raw.secondary : null;
|
|
169
|
+
const window = (w) => (w ? {
|
|
170
|
+
usedPercent: Number.isFinite(Number(w.used_percent)) ? Number(w.used_percent) : null,
|
|
171
|
+
windowMinutes: Number.isFinite(Number(w.window_minutes)) ? Number(w.window_minutes) : null,
|
|
172
|
+
resetsAt: Number.isFinite(Number(w.resets_at)) ? new Date(Number(w.resets_at) * 1000).toISOString() : null,
|
|
173
|
+
} : null);
|
|
174
|
+
return {
|
|
175
|
+
planType: raw.plan_type || null,
|
|
176
|
+
primary: window(primary),
|
|
177
|
+
secondary: window(secondary),
|
|
178
|
+
limitReached: raw.rate_limit_reached_type || null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Parse one rollout. `since` (ms epoch) keeps only records from the current
|
|
183
|
+
// turn — a rollout holds the whole thread, every turn since it was created.
|
|
184
|
+
function parseRollout(filePath, { since = 0 } = {}) {
|
|
185
|
+
let text;
|
|
186
|
+
try { text = fs.readFileSync(filePath, "utf8"); } catch (_) { return null; }
|
|
187
|
+
const result = {
|
|
188
|
+
file: filePath,
|
|
189
|
+
threadId: threadIdOf(filePath),
|
|
190
|
+
sessionMeta: null,
|
|
191
|
+
requests: [],
|
|
192
|
+
totals: emptyTotals(),
|
|
193
|
+
peakContextTokens: 0,
|
|
194
|
+
lastContextTokens: 0,
|
|
195
|
+
modelContextWindow: null,
|
|
196
|
+
compactions: 0,
|
|
197
|
+
rateLimits: null,
|
|
198
|
+
lastEventAt: null,
|
|
199
|
+
};
|
|
200
|
+
let previousFingerprint = null;
|
|
201
|
+
for (const line of text.split("\n")) {
|
|
202
|
+
if (!line) continue;
|
|
203
|
+
let record;
|
|
204
|
+
try { record = JSON.parse(line); } catch (_) { continue; }
|
|
205
|
+
if (!record || typeof record !== "object") continue;
|
|
206
|
+
if (record.type === "session_meta" && !result.sessionMeta) {
|
|
207
|
+
result.sessionMeta = normalizeSessionMeta(record);
|
|
208
|
+
if (!result.threadId) result.threadId = result.sessionMeta.threadId;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const ts = parseTs(record.timestamp);
|
|
212
|
+
if (ts !== null) result.lastEventAt = ts;
|
|
213
|
+
const inTurn = since ? (ts === null || ts >= since) : true;
|
|
214
|
+
if (record.type === "compacted") {
|
|
215
|
+
if (inTurn) result.compactions += 1;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const payload = record.payload;
|
|
219
|
+
if (!payload || payload.type !== "token_count") continue;
|
|
220
|
+
const info = payload.info || null;
|
|
221
|
+
if (info && Number.isFinite(Number(info.model_context_window))) {
|
|
222
|
+
result.modelContextWindow = Number(info.model_context_window);
|
|
223
|
+
}
|
|
224
|
+
const limits = rateLimitsOf(payload.rate_limits);
|
|
225
|
+
if (limits) result.rateLimits = limits;
|
|
226
|
+
if (!inTurn || !info) continue;
|
|
227
|
+
const last = usageOf(info.last_token_usage);
|
|
228
|
+
if (!last) continue;
|
|
229
|
+
const total = usageOf(info.total_token_usage) || last;
|
|
230
|
+
// Codex re-emits token_count for rate-limit refreshes with the same usage;
|
|
231
|
+
// one request must count once.
|
|
232
|
+
const fingerprint = JSON.stringify([last, total]);
|
|
233
|
+
if (fingerprint === previousFingerprint) continue;
|
|
234
|
+
previousFingerprint = fingerprint;
|
|
235
|
+
result.requests.push({ ts, ...last });
|
|
236
|
+
addTotals(result.totals, last);
|
|
237
|
+
if (last.input > result.peakContextTokens) result.peakContextTokens = last.input;
|
|
238
|
+
result.lastContextTokens = last.input;
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function findChildRollouts(parentThreadId, { codexHome = resolveCodexHome(), since = 0, now = Date.now(), days = DEFAULT_LOOKBACK_DAYS, exclude = new Set() } = {}) {
|
|
244
|
+
if (!parentThreadId) return [];
|
|
245
|
+
const children = [];
|
|
246
|
+
for (const file of listRollouts(codexHome, { now, days })) {
|
|
247
|
+
if (exclude.has(file)) continue;
|
|
248
|
+
const meta = readSessionMeta(file);
|
|
249
|
+
if (!meta || meta.parentThreadId !== parentThreadId) continue;
|
|
250
|
+
if (since && meta.timestamp !== null && meta.timestamp < since) continue;
|
|
251
|
+
children.push({ file, threadId: meta.threadId || threadIdOf(file), meta });
|
|
252
|
+
}
|
|
253
|
+
return children;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Everything the ledger and the compaction gate need for one Codex turn.
|
|
257
|
+
// Returns null when the rollout is not on disk (the caller falls back to the
|
|
258
|
+
// turn total Codex reported).
|
|
259
|
+
function collectCodexTurnUsage({ threadId, since = 0, env = process.env, codexHome = resolveCodexHome(env), now = Date.now(), includeSubagents = true, maxDepth = 2 } = {}) {
|
|
260
|
+
const file = findRolloutFile(threadId, { codexHome, now });
|
|
261
|
+
if (!file) return null;
|
|
262
|
+
const parent = parseRollout(file, { since });
|
|
263
|
+
if (!parent) return null;
|
|
264
|
+
const subagents = { threads: 0, requests: 0, totals: emptyTotals(), compactions: 0, threadIds: [] };
|
|
265
|
+
if (includeSubagents) {
|
|
266
|
+
const visited = new Set([file]);
|
|
267
|
+
let frontier = [threadId];
|
|
268
|
+
for (let depth = 0; depth < maxDepth && frontier.length; depth += 1) {
|
|
269
|
+
const next = [];
|
|
270
|
+
for (const parentId of frontier) {
|
|
271
|
+
for (const child of findChildRollouts(parentId, { codexHome, since, now, exclude: visited })) {
|
|
272
|
+
visited.add(child.file);
|
|
273
|
+
const parsed = parseRollout(child.file, { since: 0 });
|
|
274
|
+
if (!parsed) continue;
|
|
275
|
+
subagents.threads += 1;
|
|
276
|
+
subagents.requests += parsed.requests.length;
|
|
277
|
+
addTotals(subagents.totals, parsed.totals);
|
|
278
|
+
subagents.compactions += parsed.compactions;
|
|
279
|
+
subagents.threadIds.push(child.threadId);
|
|
280
|
+
if (child.threadId) next.push(child.threadId);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
frontier = next;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
source: "rollout",
|
|
288
|
+
file,
|
|
289
|
+
threadId: parent.threadId || threadId,
|
|
290
|
+
requests: parent.requests.length,
|
|
291
|
+
peakContextTokens: parent.peakContextTokens,
|
|
292
|
+
lastContextTokens: parent.lastContextTokens,
|
|
293
|
+
modelContextWindow: parent.modelContextWindow,
|
|
294
|
+
totals: parent.totals,
|
|
295
|
+
compactions: parent.compactions,
|
|
296
|
+
rateLimits: parent.rateLimits,
|
|
297
|
+
subagents,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
DEFAULT_LOOKBACK_DAYS,
|
|
303
|
+
resolveCodexHome,
|
|
304
|
+
dayDirs,
|
|
305
|
+
listRollouts,
|
|
306
|
+
threadIdOf,
|
|
307
|
+
findRolloutFile,
|
|
308
|
+
readSessionMeta,
|
|
309
|
+
parseRollout,
|
|
310
|
+
findChildRollouts,
|
|
311
|
+
collectCodexTurnUsage,
|
|
312
|
+
};
|
package/core/providers/codex.js
CHANGED
|
@@ -119,6 +119,12 @@ function createCodexProvider(options = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function codexMultiAgentEnabled(settings) {
|
|
123
|
+
if (settings && settings.multiAgent === false) return false;
|
|
124
|
+
const raw = String(process.env.CODEX_MULTI_AGENT || "").trim().toLowerCase();
|
|
125
|
+
return !(raw === "0" || raw === "off" || raw === "false" || raw === "disabled");
|
|
126
|
+
}
|
|
127
|
+
|
|
122
128
|
function buildInvocation(context, utility) {
|
|
123
129
|
if (!context || typeof context !== "object") throw providerError("INVALID_PROVIDER_CONTEXT", "provider context must be an object");
|
|
124
130
|
assertSupportedContext(context);
|
|
@@ -165,6 +171,12 @@ function createCodexProvider(options = {}) {
|
|
|
165
171
|
if (preparedHook) args.push(...preparedHook.args);
|
|
166
172
|
|
|
167
173
|
args.push("-c", `developer_instructions=${JSON.stringify(coreInstructions)}`);
|
|
174
|
+
// Codex 0.153 ships multi_agent on: the model may spawn parallel sub-agent
|
|
175
|
+
// threads (spawn_agent) whose usage its turn total never reports. Probed
|
|
176
|
+
// 2026-09-05: three parallel sub-agents took a weekly Pro quota from 75% to
|
|
177
|
+
// 100% in two hours. CODEX_MULTI_AGENT=off (or providerSettings.multiAgent
|
|
178
|
+
// false) disables the feature for every run the bot launches.
|
|
179
|
+
if (!codexMultiAgentEnabled(settings)) args.push("-c", "features.multi_agent=false");
|
|
168
180
|
|
|
169
181
|
const effort = context.effort || settings.effort || null;
|
|
170
182
|
if (effort) {
|
package/core/runner.js
CHANGED
|
@@ -64,24 +64,36 @@ function usageNumber(usage, ...keys) {
|
|
|
64
64
|
return 0;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
const { collectCodexTurnUsage } = require("./providers/codex-rollout");
|
|
68
|
+
|
|
69
|
+
// Never let a rollout read break a turn: the ledger falls back to the turn
|
|
70
|
+
// total Codex reported when the file is missing or unreadable.
|
|
71
|
+
function readCodexRollout(options) {
|
|
72
|
+
try { return collectCodexTurnUsage(options); } catch (_) { return null; }
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
function usageParts(usage, backend) {
|
|
68
76
|
if (!new Set(["claude", "codex"]).has(backend)) {
|
|
69
77
|
throw Object.assign(new TypeError("usage accounting requires an explicit provider"), { code: "INVALID_PROVIDER" });
|
|
70
78
|
}
|
|
71
|
-
const
|
|
79
|
+
const rawInput = usageNumber(usage, "input_tokens");
|
|
72
80
|
const cacheRead = backend === "codex"
|
|
73
81
|
? usageNumber(usage, "cached_input_tokens", "cache_read_input_tokens")
|
|
74
82
|
: usageNumber(usage, "cache_read_input_tokens", "cached_input_tokens");
|
|
75
83
|
const cacheCreation = usageNumber(usage, "cache_creation_input_tokens");
|
|
76
84
|
const output = usageNumber(usage, "output_tokens");
|
|
85
|
+
// Codex/OpenAI reports cached input INSIDE input_tokens; Anthropic reports
|
|
86
|
+
// cache reads BESIDE input_tokens. Normalise so `input` is the uncached
|
|
87
|
+
// input for both providers (like-for-like in /usage and the ledger — on
|
|
88
|
+
// 2026-09-05 the raw numbers read 89.7M vs 4.5k for what was really 2.4M vs
|
|
89
|
+
// 0.8M) while `context` stays the full prefix each provider actually sent.
|
|
90
|
+
const input = backend === "codex" ? Math.max(0, rawInput - cacheRead) : rawInput;
|
|
77
91
|
return {
|
|
78
92
|
input,
|
|
79
93
|
cacheRead,
|
|
80
94
|
cacheCreation,
|
|
81
95
|
output,
|
|
82
|
-
|
|
83
|
-
// cache-read field for attribution, but do not add it again to context.
|
|
84
|
-
context: backend === "codex" ? input : input + cacheRead + cacheCreation,
|
|
96
|
+
context: backend === "codex" ? rawInput : rawInput + cacheRead + cacheCreation,
|
|
85
97
|
};
|
|
86
98
|
}
|
|
87
99
|
|
|
@@ -109,24 +121,15 @@ function clampUsageDelta(current, previous) {
|
|
|
109
121
|
};
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
bucket.providerUsageSnapshot = {
|
|
119
|
-
input_tokens: usageNumber(usage, "input_tokens"),
|
|
120
|
-
cached_input_tokens: usageNumber(usage, "cached_input_tokens"),
|
|
121
|
-
output_tokens: usageNumber(usage, "output_tokens"),
|
|
122
|
-
};
|
|
123
|
-
return previous;
|
|
124
|
-
}
|
|
125
|
-
|
|
124
|
+
// Codex's turn.completed usage is "the usage of tokens during a turn"
|
|
125
|
+
// (codex-rs/exec/src/exec_events.rs) and every `codex exec resume` is its own
|
|
126
|
+
// process, so the number is already per turn. Until 3.4.13 it was treated as a
|
|
127
|
+
// cumulative session total and the previous turn subtracted: any turn smaller
|
|
128
|
+
// than its predecessor was recorded as 0 and the rest were undercounted (9 of
|
|
129
|
+
// 36 Codex turns on 2026-09-05 logged as zero). Pass it through unchanged.
|
|
126
130
|
function normalizeCodexUsage(state, sessionId, usage, options = {}) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return { usage: clampUsageDelta(usage, previous), usageScope: "turn_delta", rawUsage: usage };
|
|
131
|
+
void state; void sessionId; void options;
|
|
132
|
+
return { usage, usageScope: "turn", rawUsage: usage };
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
function applyUsageToState(state, usage, costUsd, opts = {}) {
|
|
@@ -147,6 +150,18 @@ function applyUsageToState(state, usage, costUsd, opts = {}) {
|
|
|
147
150
|
u.lastUsageScope = opts.usageScope || "turn_delta";
|
|
148
151
|
if (Number.isFinite(opts.liveContextTokens)) u.liveContextTokens = opts.liveContextTokens;
|
|
149
152
|
else u.liveContextTokens = parts.context;
|
|
153
|
+
if (Number.isFinite(opts.contextWindowTokens) && opts.contextWindowTokens > 0) u.contextWindowTokens = opts.contextWindowTokens;
|
|
154
|
+
if (opts.quota && Number.isFinite(opts.quota.usedPercent)) {
|
|
155
|
+
u.quotaUsedPercent = opts.quota.usedPercent;
|
|
156
|
+
u.quotaResetsAt = opts.quota.resetsAt || null;
|
|
157
|
+
}
|
|
158
|
+
if (opts.subagents && opts.subagents.threads > 0) {
|
|
159
|
+
const totals = opts.subagents.totals || {};
|
|
160
|
+
u.subagentThreads = (u.subagentThreads || 0) + opts.subagents.threads;
|
|
161
|
+
u.subagentInputTokens = (u.subagentInputTokens || 0) + Math.max(0, (totals.input || 0) - (totals.cached || 0));
|
|
162
|
+
u.subagentCacheReadTokens = (u.subagentCacheReadTokens || 0) + (totals.cached || 0);
|
|
163
|
+
u.subagentOutputTokens = (u.subagentOutputTokens || 0) + (totals.output || 0);
|
|
164
|
+
}
|
|
150
165
|
if (typeof costUsd === "number") u.costUsd += costUsd;
|
|
151
166
|
return parts;
|
|
152
167
|
}
|
|
@@ -185,6 +200,23 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
|
|
|
185
200
|
costUsd: typeof costUsd === "number" ? costUsd : 0,
|
|
186
201
|
};
|
|
187
202
|
if (typeof opts.coldStart === "boolean") record.coldStart = opts.coldStart;
|
|
203
|
+
if (opts.contextSource) record.contextSource = opts.contextSource;
|
|
204
|
+
if (Number.isFinite(opts.contextWindowTokens) && opts.contextWindowTokens > 0) record.contextWindowTokens = opts.contextWindowTokens;
|
|
205
|
+
if (Number.isFinite(opts.requests) && opts.requests > 0) record.requests = opts.requests;
|
|
206
|
+
if (Number.isFinite(opts.providerCompactions)) record.providerCompactions = opts.providerCompactions;
|
|
207
|
+
if (opts.quota && Number.isFinite(opts.quota.usedPercent)) {
|
|
208
|
+
record.quotaUsedPercent = opts.quota.usedPercent;
|
|
209
|
+
if (opts.quota.windowMinutes) record.quotaWindowMinutes = opts.quota.windowMinutes;
|
|
210
|
+
if (opts.quota.resetsAt) record.quotaResetsAt = opts.quota.resetsAt;
|
|
211
|
+
}
|
|
212
|
+
if (opts.subagents && opts.subagents.threads > 0) {
|
|
213
|
+
const totals = opts.subagents.totals || {};
|
|
214
|
+
record.subagentThreads = opts.subagents.threads;
|
|
215
|
+
record.subagentRequests = opts.subagents.requests || 0;
|
|
216
|
+
record.subagentInputTokens = Math.max(0, (totals.input || 0) - (totals.cached || 0));
|
|
217
|
+
record.subagentCacheReadTokens = totals.cached || 0;
|
|
218
|
+
record.subagentOutputTokens = totals.output || 0;
|
|
219
|
+
}
|
|
188
220
|
if (opts.rawUsage && opts.rawUsage !== usage) {
|
|
189
221
|
const raw = usageParts(opts.rawUsage, backend);
|
|
190
222
|
record.rawInputTokens = raw.input;
|
|
@@ -207,7 +239,7 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
|
|
|
207
239
|
if (announce && usageOwnerOnly) {
|
|
208
240
|
const baseline = result.trend?.baselineAvgContextTokens || 0;
|
|
209
241
|
const rate = result.trend?.latestRate || 0;
|
|
210
|
-
const contextLabel =
|
|
242
|
+
const contextLabel = usageScope === "session_total" && !hasPeak ? "Session context total" : "Context this turn";
|
|
211
243
|
announce([
|
|
212
244
|
"Token usage alert:",
|
|
213
245
|
`${contextLabel}: ${fmtTokens(record.contextTokens)}`,
|
|
@@ -948,11 +980,22 @@ function resolveRunCwd(cwd) {
|
|
|
948
980
|
return fallback;
|
|
949
981
|
}
|
|
950
982
|
|
|
951
|
-
|
|
983
|
+
const CODEX_WINDOW_COMPACT_FRACTION = 0.8;
|
|
984
|
+
|
|
985
|
+
function effectiveCompactThreshold(state = currentState(), opts = {}) {
|
|
952
986
|
const override = state.settings?.compactWindow;
|
|
953
987
|
if (override === 0) return 0;
|
|
954
|
-
|
|
955
|
-
|
|
988
|
+
const base = Number.isFinite(override) && override > 0
|
|
989
|
+
? override
|
|
990
|
+
: (Number.isFinite(AUTO_COMPACT_TOKENS) ? AUTO_COMPACT_TOKENS : 380000);
|
|
991
|
+
// Codex's window (258k for gpt-6-astra) sits below the default threshold, so
|
|
992
|
+
// measured honestly the bot would never compact a Codex session — and would
|
|
993
|
+
// lose the brief that feeds memory. Compact at 80% of the reported window.
|
|
994
|
+
const window = Number(opts.contextWindowTokens ?? state.sessionUsage?.contextWindowTokens);
|
|
995
|
+
if (getActiveProvider(state) === "codex" && Number.isFinite(window) && window > 0) {
|
|
996
|
+
return Math.min(base, Math.floor(window * CODEX_WINDOW_COMPACT_FRACTION));
|
|
997
|
+
}
|
|
998
|
+
return base;
|
|
956
999
|
}
|
|
957
1000
|
|
|
958
1001
|
function shouldAutoCompact(state = currentState(), opts = {}) {
|
|
@@ -1511,13 +1554,32 @@ async function persistForegroundUsage(result, runContext, state = currentState()
|
|
|
1511
1554
|
?? usageParts(usage, runContext.provider).context;
|
|
1512
1555
|
const usageSessionId = result.sessionId || runContext.sessionId;
|
|
1513
1556
|
const usageProject = runContext.project?.name || null;
|
|
1557
|
+
let rollout = null;
|
|
1514
1558
|
if (runContext.provider === "codex") {
|
|
1515
1559
|
const normalized = normalizeCodexUsage(state, usageSessionId, usage, { project: usageProject });
|
|
1516
1560
|
usage = normalized.usage;
|
|
1517
1561
|
usageScope = normalized.usageScope;
|
|
1518
1562
|
rawUsage = normalized.rawUsage;
|
|
1519
|
-
|
|
1563
|
+
// Measure Codex like Claude: the last single-request window occupancy
|
|
1564
|
+
// from its rollout, never the turn-summed input — that sum tripped
|
|
1565
|
+
// auto-compact after almost every substantial turn (8 needless Codex
|
|
1566
|
+
// compactions on 2026-09-05) and hid the sub-agent threads entirely.
|
|
1567
|
+
rollout = readCodexRollout({
|
|
1568
|
+
threadId: usageSessionId,
|
|
1569
|
+
since: (result.timings?.startedAt || Date.now()) - 5000,
|
|
1570
|
+
});
|
|
1571
|
+
liveContextTokens = rollout && rollout.lastContextTokens > 0
|
|
1572
|
+
? rollout.lastContextTokens
|
|
1573
|
+
: usageParts(rawUsage, "codex").context;
|
|
1520
1574
|
}
|
|
1575
|
+
const rolloutOpts = rollout ? {
|
|
1576
|
+
contextSource: "rollout_last_request",
|
|
1577
|
+
contextWindowTokens: rollout.modelContextWindow || undefined,
|
|
1578
|
+
requests: rollout.requests,
|
|
1579
|
+
providerCompactions: rollout.compactions,
|
|
1580
|
+
quota: rollout.rateLimits?.primary || null,
|
|
1581
|
+
subagents: rollout.subagents,
|
|
1582
|
+
} : (runContext.provider === "codex" ? { contextSource: "turn_total_fallback" } : {});
|
|
1521
1583
|
result.usage = usage;
|
|
1522
1584
|
result.usageScope = usageScope;
|
|
1523
1585
|
applyUsageToState(state, usage, result.costUsd || 0, {
|
|
@@ -1526,6 +1588,7 @@ async function persistForegroundUsage(result, runContext, state = currentState()
|
|
|
1526
1588
|
liveContextTokens,
|
|
1527
1589
|
project: usageProject,
|
|
1528
1590
|
sessionId: usageSessionId,
|
|
1591
|
+
...rolloutOpts,
|
|
1529
1592
|
});
|
|
1530
1593
|
logTurnUsage(
|
|
1531
1594
|
state,
|
|
@@ -1544,6 +1607,7 @@ async function persistForegroundUsage(result, runContext, state = currentState()
|
|
|
1544
1607
|
rawUsage,
|
|
1545
1608
|
coldStart: usageSessionColdStart(usageSessionId),
|
|
1546
1609
|
strict: true,
|
|
1610
|
+
...rolloutOpts,
|
|
1547
1611
|
},
|
|
1548
1612
|
);
|
|
1549
1613
|
saveState({ strict: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.13",
|
|
4
4
|
"description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
|
|
5
5
|
"main": "bot.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"setup": "node setup.js",
|
|
12
12
|
"start": "node bot.js",
|
|
13
13
|
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-kazee-call-session.js",
|
|
14
|
-
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js"
|
|
14
|
+
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
package/test-provider-codex.js
CHANGED
|
@@ -134,6 +134,18 @@ const directResume = provider.buildMainInvocation(simpleContext({ sessionId: "th
|
|
|
134
134
|
assert.ok(directResume.args.includes("--dangerously-bypass-approvals-and-sandbox"));
|
|
135
135
|
assert.ok(directResume.args.indexOf("--dangerously-bypass-approvals-and-sandbox") < directResume.args.indexOf("thread-direct"));
|
|
136
136
|
|
|
137
|
+
{
|
|
138
|
+
const before = process.env.CODEX_MULTI_AGENT;
|
|
139
|
+
delete process.env.CODEX_MULTI_AGENT;
|
|
140
|
+
assert.ok(!provider.buildMainInvocation(simpleContext({})).args.includes("features.multi_agent=false"), "multi_agent stays as Codex ships it by default");
|
|
141
|
+
process.env.CODEX_MULTI_AGENT = "off";
|
|
142
|
+
const noAgents = provider.buildMainInvocation(simpleContext({}));
|
|
143
|
+
const at = noAgents.args.indexOf("features.multi_agent=false");
|
|
144
|
+
assert.ok(at > 0 && noAgents.args[at - 1] === "-c", "CODEX_MULTI_AGENT=off passes -c features.multi_agent=false");
|
|
145
|
+
assert.ok(at < noAgents.args.indexOf("--json"), "the override sits with the other -c overrides, before the exec flags");
|
|
146
|
+
if (before === undefined) delete process.env.CODEX_MULTI_AGENT; else process.env.CODEX_MULTI_AGENT = before;
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
const continued = provider.buildMainInvocation(simpleContext({ continueSession: true, sessionId: "thread-continue" }));
|
|
138
150
|
assert.strictEqual(continued.args[1], "resume");
|
|
139
151
|
assert.ok(continued.args.includes("thread-continue"));
|
package/test-provider-events.js
CHANGED
|
@@ -203,7 +203,7 @@ assert.deepStrictEqual(normalizeCodexEvent(samples.codex.assistant, codexState),
|
|
|
203
203
|
{ type: "text_final", text: "Hello from Codex" },
|
|
204
204
|
]);
|
|
205
205
|
assert.deepStrictEqual(normalizeCodexEvent(samples.codex.result, codexState), [
|
|
206
|
-
{ type: "usage", usage: { input_tokens: 20, cached_input_tokens: 5, output_tokens: 4 }, scope: "
|
|
206
|
+
{ type: "usage", usage: { input_tokens: 20, cached_input_tokens: 5, output_tokens: 4 }, scope: "turn" },
|
|
207
207
|
{ type: "result", text: "", sessionId: "codex-thread-1" },
|
|
208
208
|
]);
|
|
209
209
|
assert.deepStrictEqual(normalizeCodexEvent(samples.codex.result, codexState), [], "duplicate Codex terminal and usage settle once");
|
|
@@ -464,7 +464,9 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
464
464
|
// tool_name "Bash" and the deny gate caught the probe, so the gate no longer
|
|
465
465
|
// requires unified_exec off. Push approved by Sumeet before release.
|
|
466
466
|
// 3.4.12: Sumeet approved the full calling rollout on 2026-09-05; preserve assigned media configuration across canary changes.
|
|
467
|
-
|
|
467
|
+
// 3.4.13: Codex usage accounting fix — Sumeet approved the three-bug fix on 2026-09-05 ("Yes");
|
|
468
|
+
// prepared 2026-09-06 for his release go. Rollout-based Codex context, per-turn ledger, sub-agent + quota accounting.
|
|
469
|
+
assert.strictEqual(pkg.version, "3.4.13", "release version must remain unchanged without explicit approval");
|
|
468
470
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
469
471
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
470
472
|
}
|
package/test-usage-accounting.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
normalizeCodexUsage,
|
|
10
10
|
applyUsageToState,
|
|
11
11
|
shouldAutoCompact,
|
|
12
|
+
effectiveCompactThreshold,
|
|
12
13
|
peakContextTokens,
|
|
13
14
|
usageSessionColdStart,
|
|
14
15
|
} = require("./core/runner");
|
|
@@ -48,7 +49,9 @@ function state(overrides = {}) {
|
|
|
48
49
|
|
|
49
50
|
assert.deepStrictEqual(
|
|
50
51
|
usageParts({ input_tokens: 100, cached_input_tokens: 60, output_tokens: 10 }, "codex"),
|
|
51
|
-
|
|
52
|
+
// OpenAI counts cached tokens inside input_tokens: `input` is the uncached
|
|
53
|
+
// part so it compares like-for-like with Claude; `context` is the full prefix.
|
|
54
|
+
{ input: 40, cacheRead: 60, cacheCreation: 0, output: 10, context: 100 },
|
|
52
55
|
);
|
|
53
56
|
|
|
54
57
|
assert.deepStrictEqual(
|
|
@@ -62,7 +65,7 @@ const first = normalizeCodexUsage(s, "codex-session-1", {
|
|
|
62
65
|
cached_input_tokens: 700,
|
|
63
66
|
output_tokens: 50,
|
|
64
67
|
}, { project: "alpha" });
|
|
65
|
-
assert.strictEqual(first.usageScope, "
|
|
68
|
+
assert.strictEqual(first.usageScope, "turn", "Codex turn.completed usage is per turn, never a session total");
|
|
66
69
|
assert.strictEqual(usageParts(first.usage, "codex").context, 1000);
|
|
67
70
|
|
|
68
71
|
const second = normalizeCodexUsage(s, "codex-session-1", {
|
|
@@ -70,13 +73,13 @@ const second = normalizeCodexUsage(s, "codex-session-1", {
|
|
|
70
73
|
cached_input_tokens: 800,
|
|
71
74
|
output_tokens: 80,
|
|
72
75
|
}, { project: "alpha" });
|
|
73
|
-
assert.strictEqual(second.usageScope, "
|
|
76
|
+
assert.strictEqual(second.usageScope, "turn");
|
|
77
|
+
// No delta against the previous turn: a smaller turn after a bigger one used
|
|
78
|
+
// to be recorded as 0 (9 of 36 Codex turns on 2026-09-05).
|
|
74
79
|
assert.deepStrictEqual(second.usage, {
|
|
75
|
-
input_tokens:
|
|
76
|
-
cached_input_tokens:
|
|
77
|
-
|
|
78
|
-
cache_creation_input_tokens: 0,
|
|
79
|
-
output_tokens: 30,
|
|
80
|
+
input_tokens: 1250,
|
|
81
|
+
cached_input_tokens: 800,
|
|
82
|
+
output_tokens: 80,
|
|
80
83
|
});
|
|
81
84
|
|
|
82
85
|
applyUsageToState(s, second.usage, 0, {
|
|
@@ -87,9 +90,9 @@ applyUsageToState(s, second.usage, 0, {
|
|
|
87
90
|
liveContextTokens: usageParts(second.rawUsage, "codex").context,
|
|
88
91
|
});
|
|
89
92
|
assert.strictEqual(s.sessionUsage.turns, 1);
|
|
90
|
-
assert.strictEqual(s.sessionUsage.inputTokens,
|
|
91
|
-
assert.strictEqual(s.sessionUsage.cacheReadTokens,
|
|
92
|
-
assert.strictEqual(s.sessionUsage.lastInputTokens,
|
|
93
|
+
assert.strictEqual(s.sessionUsage.inputTokens, 450, "uncached input only");
|
|
94
|
+
assert.strictEqual(s.sessionUsage.cacheReadTokens, 800);
|
|
95
|
+
assert.strictEqual(s.sessionUsage.lastInputTokens, 1250);
|
|
93
96
|
assert.strictEqual(s.sessionUsage.liveContextTokens, 1250);
|
|
94
97
|
|
|
95
98
|
// The same native-looking ID in another project has an independent cumulative
|
|
@@ -99,7 +102,7 @@ const otherProject = normalizeCodexUsage(s, "codex-session-1", {
|
|
|
99
102
|
cached_input_tokens: 1200,
|
|
100
103
|
output_tokens: 100,
|
|
101
104
|
}, { project: "beta" });
|
|
102
|
-
assert.strictEqual(otherProject.usageScope, "
|
|
105
|
+
assert.strictEqual(otherProject.usageScope, "turn");
|
|
103
106
|
s.currentSession = { name: "beta", dir: "/workspace/beta" };
|
|
104
107
|
s.settings.backend = "claude";
|
|
105
108
|
s.activeSessions.beta = { claude: "claude-beta", codex: "codex-session-1" };
|
|
@@ -154,6 +157,36 @@ assert.strictEqual(shouldAutoCompact(state({
|
|
|
154
157
|
sessionUsage: { liveContextTokens: 250, lastInputTokens: 250, lastCompactedAt: Date.now() },
|
|
155
158
|
})), true);
|
|
156
159
|
|
|
160
|
+
// Codex is compacted at 80% of the window it reports, because its window
|
|
161
|
+
// (258k on gpt-6-astra) is below the default threshold — measured honestly the
|
|
162
|
+
// bot would otherwise never compact a Codex session.
|
|
163
|
+
{
|
|
164
|
+
const windowed = state({ sessionUsage: { liveContextTokens: 210000, lastInputTokens: 210000, contextWindowTokens: 258400 } });
|
|
165
|
+
delete windowed.settings.compactWindow;
|
|
166
|
+
assert.strictEqual(effectiveCompactThreshold(windowed), 206720, "80% of the reported Codex window");
|
|
167
|
+
assert.strictEqual(shouldAutoCompact(windowed), true, "210k of a 258k window compacts");
|
|
168
|
+
windowed.sessionUsage.liveContextTokens = 200000;
|
|
169
|
+
assert.strictEqual(shouldAutoCompact(windowed), false, "200k of a 258k window does not");
|
|
170
|
+
windowed.settings.backend = "claude";
|
|
171
|
+
assert.strictEqual(effectiveCompactThreshold(windowed), 380000, "the Codex window cap never applies to Claude");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Rollout-derived facts ride along into the session bucket.
|
|
175
|
+
{
|
|
176
|
+
const enriched = state();
|
|
177
|
+
applyUsageToState(enriched, { input_tokens: 1000, cached_input_tokens: 900, output_tokens: 10 }, 0, {
|
|
178
|
+
backend: "codex", project: "alpha", sessionId: "codex-session-1", usageScope: "turn", liveContextTokens: 1000,
|
|
179
|
+
contextWindowTokens: 258400, quota: { usedPercent: 31, resetsAt: "2026-09-12T13:57:00.000Z" },
|
|
180
|
+
subagents: { threads: 2, requests: 5, totals: { input: 70000, cached: 50000, output: 300 } },
|
|
181
|
+
});
|
|
182
|
+
assert.strictEqual(enriched.sessionUsage.contextWindowTokens, 258400);
|
|
183
|
+
assert.strictEqual(enriched.sessionUsage.quotaUsedPercent, 31);
|
|
184
|
+
assert.strictEqual(enriched.sessionUsage.subagentThreads, 2);
|
|
185
|
+
assert.strictEqual(enriched.sessionUsage.subagentInputTokens, 20000, "sub-agent input is stored uncached");
|
|
186
|
+
assert.strictEqual(enriched.sessionUsage.subagentCacheReadTokens, 50000);
|
|
187
|
+
assert.strictEqual(enriched.sessionUsage.subagentOutputTokens, 300);
|
|
188
|
+
}
|
|
189
|
+
|
|
157
190
|
// peakContextTokens: the live context number must be the LARGEST single API
|
|
158
191
|
// call, not the turn-cumulative sum (the v3.0.0 347k-vs-160k regression).
|
|
159
192
|
assert.strictEqual(peakContextTokens([
|