@inetafrica/open-claudia 3.4.12 → 3.4.14
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 +12 -0
- package/channels/kazee/call/session.js +17 -4
- 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-kazee-call-session.js +64 -2
- package/test-provider-codex.js +12 -0
- package/test-provider-events.js +1 -1
- package/test-provider-language.js +4 -1
- package/test-usage-accounting.js +45 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.4.14
|
|
4
|
+
|
|
5
|
+
- Subscribe to managed call audio only when gateway-authenticated publisher metadata identifies a User. Exclude bots, the current bot's other feeds, and missing or malformed classifications before creating subscriber connections or speech streams.
|
|
6
|
+
- Preserve metadata through the initial room response and live publisher events. Managed calls use that authenticated classification; legacy calls retain their existing display-prefix exclusion because client-supplied metadata is untrusted.
|
|
7
|
+
- Exercise the actual session subscription and offer/answer path for allowed users and rejected bot, self and unknown feeds.
|
|
8
|
+
|
|
9
|
+
## 3.4.13
|
|
10
|
+
|
|
11
|
+
- **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.
|
|
12
|
+
- **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`.
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
3
15
|
## 3.4.12
|
|
4
16
|
|
|
5
17
|
- 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.
|
|
@@ -256,8 +256,8 @@ class KazeeCallSession {
|
|
|
256
256
|
}
|
|
257
257
|
for (const publisher of data.publishers || []) {
|
|
258
258
|
const feedId = Number(publisher.id);
|
|
259
|
-
if (Number.isSafeInteger(feedId) && feedId > 0 && feedId !== this.publisherId && !this.subscribers.has(feedId)
|
|
260
|
-
await this._subscribe(feedId, publisher.display);
|
|
259
|
+
if (Number.isSafeInteger(feedId) && feedId > 0 && feedId !== this.publisherId && !this.subscribers.has(feedId)) {
|
|
260
|
+
await this._subscribe(feedId, publisher.display, publisher.metadata);
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
if (data.videoroom === "joined") {
|
|
@@ -357,8 +357,21 @@ class KazeeCallSession {
|
|
|
357
357
|
this.speak(this.greeting).catch((error) => console.error("Kazee call greeting:", error.message));
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
async _subscribe(feedId, display) {
|
|
361
|
-
if (this.subscribers.has(feedId)) return;
|
|
360
|
+
async _subscribe(feedId, display, metadata) {
|
|
361
|
+
if (this.closed || feedId === this.publisherId || this.subscribers.has(feedId)) return;
|
|
362
|
+
if (this.managedRoom) {
|
|
363
|
+
// Only the enforced gateway owns this metadata. Display remains an email
|
|
364
|
+
// for presentation, and cannot tell us whether a publisher is another bot.
|
|
365
|
+
// Missing or malformed classification must not start STT on an unknown feed.
|
|
366
|
+
const principal = metadata?.kazee;
|
|
367
|
+
if (!principal || Array.isArray(principal) || principal.version !== 1 ||
|
|
368
|
+
typeof principal.userId !== "string" || !/^[a-f\d]{24}$/i.test(principal.userId) ||
|
|
369
|
+
!["User", "Bot"].includes(principal.principalKind) || principal.principalKind === "Bot" ||
|
|
370
|
+
principal.userId.toLowerCase() === this.botUserId.toLowerCase()) return;
|
|
371
|
+
} else if (String(display || "").startsWith("open-claudia:")) {
|
|
372
|
+
// Legacy/direct rooms allow client metadata, so it is not a trusted source.
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
362
375
|
// Reserve the slot before the first await: the join response and the async
|
|
363
376
|
// publishers event both list the same feed, and while attach() is in
|
|
364
377
|
// flight the second frame passes the caller's has() check and subscribes
|
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.14",
|
|
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",
|
|
@@ -462,10 +462,11 @@ async function testManagedRoomCredentials() {
|
|
|
462
462
|
assert.strictEqual(session.janus.options.token, "root-token");
|
|
463
463
|
assert.strictEqual(session.janus.calls[0].token, "publisher-token");
|
|
464
464
|
await session._consumePublisherFrame({ plugindata: { data: { publishers: [{ id: 41, display: 'canonical-bot@calling.invalid' }, { id: 42, display: 'open-claudia:legacy-bot' }] } } });
|
|
465
|
-
assert.strictEqual(session.subscribers.size, 0, 'own joined publisher ID
|
|
465
|
+
assert.strictEqual(session.subscribers.size, 0, 'own joined publisher ID and unclassified managed feeds are excluded independently of display');
|
|
466
466
|
|
|
467
467
|
session.roomTokenExpiresAt = Date.now() - 1;
|
|
468
|
-
|
|
468
|
+
const userMetadata = { kazee: { version: 1, userId: 'a'.repeat(24), principalKind: 'User' } };
|
|
469
|
+
await Promise.all([session._subscribe(17, "owner", userMetadata), session._subscribe(18, "other", userMetadata)]);
|
|
469
470
|
const subJoin = session.janus.calls.find(body => body.ptype === "subscriber");
|
|
470
471
|
assert.strictEqual(subJoin.token, "subscriber-token");
|
|
471
472
|
assert.strictEqual(subJoin.private_id, 999);
|
|
@@ -484,6 +485,66 @@ async function testManagedRoomCredentials() {
|
|
|
484
485
|
assert.ok(!session.janus.calls.some(body => ["create", "destroy"].includes(body.request)), "server allocated room ownership never transfers to the bot");
|
|
485
486
|
}
|
|
486
487
|
|
|
488
|
+
async function testTrustedPublisherMetadata() {
|
|
489
|
+
const botId = 'b'.repeat(24), userId = 'a'.repeat(24);
|
|
490
|
+
const metadata = (principalKind, id = userId) => ({ kazee: { version: 1, userId: id, principalKind } });
|
|
491
|
+
const publisher = (id, meta, display = 'speaker@calling.invalid') => ({ id, display, metadata: meta });
|
|
492
|
+
let initial = [];
|
|
493
|
+
class MetadataJanus extends FakeJanus {
|
|
494
|
+
constructor() { super(); this.calls = []; this.listeners = new Map(); }
|
|
495
|
+
onHandle(handle, listener) { this.listeners.set(handle, listener); }
|
|
496
|
+
async message(handle, body) {
|
|
497
|
+
this.calls.push({ handle, ...body });
|
|
498
|
+
if (body.request === 'join' && body.ptype === 'publisher') return { plugindata: { data: { videoroom: 'joined', id: 41, private_id: 999, publishers: initial } } };
|
|
499
|
+
if (body.request === 'configure') return { jsep: { type: 'answer', sdp: 'fixture' } };
|
|
500
|
+
if (body.request === 'join' && body.ptype === 'subscriber') return { plugindata: { data: { videoroom: 'attached' } }, jsep: { type: 'offer', sdp: 'fixture' } };
|
|
501
|
+
return {};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const make = managedRoom => new KazeeCallSession({ roomId: 7, chatId: 'chat', callerId: userId, botUserId: botId, managedRoom,
|
|
505
|
+
roomToken: 'fixture', roomTokenExpiresAt: Date.now() + 300000,
|
|
506
|
+
iceServers: [{ urls: 'turn:relay:3478', username: 'fixture', credential: 'fixture' }], iceCredentialsExpiresAt: Date.now() + 3600000,
|
|
507
|
+
JanusClientImpl: MetadataJanus, PeerConnectionImpl: FakePC, SpeechStreamImpl: FakeSpeechStream });
|
|
508
|
+
const invalid = [undefined, null, {}, { kazee: null }, { kazee: [] },
|
|
509
|
+
{ kazee: { version: 2, userId, principalKind: 'User' } }, { kazee: { version: '1', userId, principalKind: 'User' } },
|
|
510
|
+
metadata('Human'), metadata('bot'), metadata(undefined), metadata('User', 'invalid'), { kazee: { version: 1, principalKind: 'User' } },
|
|
511
|
+
{ kazee: { version: 1, userId: { $ne: null }, principalKind: 'User' } }];
|
|
512
|
+
initial = [publisher(41, metadata('User')), publisher(42, metadata('Bot', 'c'.repeat(24)), 'other-bot@calling.invalid'),
|
|
513
|
+
publisher(43, metadata('User')), publisher(44, metadata('User', botId.toUpperCase())),
|
|
514
|
+
...invalid.map((meta, i) => publisher(100 + i, meta))];
|
|
515
|
+
const managed = make(true);
|
|
516
|
+
try {
|
|
517
|
+
await managed.start();
|
|
518
|
+
assert.deepStrictEqual([...managed.subscribers.keys()], [43], 'initial join reaches real subscription only for a classified human, never self/bot/unknown');
|
|
519
|
+
assert.strictEqual(managed.subscribers.get(43).started, true, 'authorized human reaches the actual subscriber offer/answer path');
|
|
520
|
+
const emit = publishers => managed.janus.listeners.get(managed.publisherHandle)({ janus: 'event', plugindata: { data: { publishers } } });
|
|
521
|
+
await emit([publisher(50, metadata('Bot', 'd'.repeat(24)), 'another-bot@calling.invalid'),
|
|
522
|
+
publisher(51, metadata('User'), 'open-claudia:display-is-not-managed-authority'),
|
|
523
|
+
...invalid.map((meta, i) => publisher(200 + i, meta))]);
|
|
524
|
+
assert.deepStrictEqual([...managed.subscribers.keys()], [43, 51], 'live publisher metadata survives forwarding; managed classification never falls back to display prefix');
|
|
525
|
+
assert.strictEqual(managed.subscribers.get(51).display, 'open-claudia:display-is-not-managed-authority', 'presentation remains unchanged');
|
|
526
|
+
const handles = managed.janus.handles;
|
|
527
|
+
await managed._subscribe(60, 'unclassified@calling.invalid');
|
|
528
|
+
await managed._subscribe(61, 'bot@calling.invalid', metadata('Bot'));
|
|
529
|
+
await managed._subscribe(41, 'own@calling.invalid', metadata('User'));
|
|
530
|
+
assert.strictEqual(managed.janus.handles, handles, 'the subscription method itself fails closed before creating handles or speech streams');
|
|
531
|
+
assert.deepStrictEqual(managed.janus.calls.filter(body => body.ptype === 'subscriber').map(body => body.feed), [43, 51]);
|
|
532
|
+
} finally { await managed.close(); }
|
|
533
|
+
|
|
534
|
+
initial = [publisher(41, metadata('User')), publisher(42, metadata('User'), 'open-claudia:legacy-bot'),
|
|
535
|
+
publisher(43, metadata('Bot'), 'human@calling.invalid')];
|
|
536
|
+
const legacy = make(false);
|
|
537
|
+
try {
|
|
538
|
+
await legacy.start();
|
|
539
|
+
assert.deepStrictEqual([...legacy.subscribers.keys()], [43], 'legacy metadata cannot hide a human or impersonate one for a prefixed bot; own numeric feed remains excluded');
|
|
540
|
+
await legacy.janus.listeners.get(legacy.publisherHandle)({ janus: 'event', plugindata: { data: { publishers: [
|
|
541
|
+
publisher(50, metadata('Bot'), 'untrusted-kind@calling.invalid'), publisher(51, metadata('User'), 'open-claudia:other-bot'),
|
|
542
|
+
publisher(52, undefined, 'older-client@calling.invalid'),
|
|
543
|
+
] } } });
|
|
544
|
+
assert.deepStrictEqual([...legacy.subscribers.keys()], [43, 50, 52], 'legacy live publishers keep display-prefix behavior regardless of spoofed or absent metadata');
|
|
545
|
+
} finally { await legacy.close(); }
|
|
546
|
+
}
|
|
547
|
+
|
|
487
548
|
async function testAssignedRelayTransportLifetime() {
|
|
488
549
|
const { RTCPeerConnection } = require('werift');
|
|
489
550
|
const originalExpiry = Date.now() + 3600000;
|
|
@@ -538,6 +599,7 @@ async function testAssignedRelayTransportLifetime() {
|
|
|
538
599
|
}
|
|
539
600
|
|
|
540
601
|
(async () => {
|
|
602
|
+
await testTrustedPublisherMetadata();
|
|
541
603
|
await testAssignedRelayTransportLifetime();
|
|
542
604
|
await testManagedRoomCredentials();
|
|
543
605
|
await testSpeakSerialization();
|
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,10 @@ 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.14: bot managed-call consumer (subscribe only to gateway-authenticated User publishers) — part of the Janus calling plan Sumeet approved on 2026-09-05.
|
|
468
|
+
// 3.4.13: Codex usage accounting fix — Sumeet approved the three-bug fix on 2026-09-05 ("Yes");
|
|
469
|
+
// prepared 2026-09-06 for his release go. Rollout-based Codex context, per-turn ledger, sub-agent + quota accounting.
|
|
470
|
+
assert.strictEqual(pkg.version, "3.4.14", "release version must remain unchanged without explicit approval");
|
|
468
471
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
469
472
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
470
473
|
}
|
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([
|