@inetafrica/open-claudia 3.5.10 → 3.5.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/.env.example +2 -0
- package/CHANGELOG.md +16 -0
- package/README.md +3 -2
- package/core/media.js +82 -20
- package/core/router.js +12 -4
- package/core/runner.js +31 -3
- package/core/system-prompt.js +2 -0
- package/core/usage-log.js +10 -1
- package/package.json +4 -3
- package/test-group-status-notes.js +140 -0
- package/test-provider-language.js +14 -1
package/.env.example
CHANGED
|
@@ -63,6 +63,8 @@ OC_THREAD_TITLES=on
|
|
|
63
63
|
THREAD_TITLE_PROVIDER=
|
|
64
64
|
THREAD_TITLE_MODEL_CLAUDE=
|
|
65
65
|
THREAD_TITLE_MODEL_CODEX=
|
|
66
|
+
# Token usage alerts to the owner: on | off (default off). Never posted in group chats.
|
|
67
|
+
USAGE_ALERTS=off
|
|
66
68
|
USAGE_ALERT_CONTEXT_TOKENS=120000
|
|
67
69
|
USAGE_ALERT_RATE_MULTIPLIER=1.75
|
|
68
70
|
USAGE_ALERT_BASELINE_TURNS=20
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.5.13
|
|
4
|
+
|
|
5
|
+
Group chats stop seeing notes meant only for the owner, and token usage alerts are off by default. Canary first, on the sales bot.
|
|
6
|
+
|
|
7
|
+
- **Fix: no more junk "📌 standing rule" posts.** After every owner turn the rule detector scanned the whole prompt, including the group or thread messages the bot was shown, so it saved the bot's own lines as the owner's rules. Its own "📌 Got it — I'll keep this loaded as a standing rule from now on" note then set off the next one, one more after every owner message in a staff group (sales bot, 4 Oct; same cause as the 1 Oct thread report). Rules now come only from the owner's own words (the typed message, voice transcript or caption), and only in a direct chat with the bot: group chats never capture rules or see 📌 notes.
|
|
8
|
+
- **Token usage alerts are off by default** (Sumeet, 4 Oct: "turn off this token usage thing by default on all the bots"). `USAGE_ALERTS=on` brings them back, with the same `USAGE_ALERT_*` thresholds, and even then they never go to a group chat.
|
|
9
|
+
- **Tests:** `test-group-status-notes.js` (new).
|
|
10
|
+
|
|
11
|
+
## 3.5.12
|
|
12
|
+
|
|
13
|
+
Two fixes: "No response requested." no longer reaches the chat, and voice notes wait out a busy speech server. Canary first, on the sales bot. (3.5.11 was tagged but never published: its release run failed on the new test, which assumed an agent CLI is installed; CI has none.)
|
|
14
|
+
|
|
15
|
+
- **Fix: "No response requested." never reaches the chat.** When a restart cut a turn short, Claude Code resumed that conversation by writing two lines into the bot's history that nobody sent: "Continue from where you left off." and a synthetic assistant reply, "No response requested." (model `<synthetic>`, never streamed). The bot then apologised for that reply or copied it as a real answer (reported by Sumeet, 3 Oct). The runner now drops a reply that is only that line, and the system prompt explains the two lines, so the bot finishes the interrupted work instead of mentioning them.
|
|
16
|
+
- **Fix: voice notes wait out the speech queue and say why they fail.** Hosted transcription waits up to 4 minutes in total and retries 429/502/503/504 and network drops after 2 s and 6 s; a 500, a 4xx or a timeout is not retried. "Couldn't transcribe" now names the reason, e.g. "(the speech server answered HTTP 500)", and `open-claudia transcribe` reports the reason too. The local Whisper fallback converts into its own temp file; it used to overwrite the .wav it was reading.
|
|
17
|
+
- **Tests:** `test-placeholder-reply.js` (new), and `test-media-speech.js` now runs in `npm test` (it existed since 3.1.15 but was never in the chain).
|
|
18
|
+
|
|
3
19
|
## 3.5.10
|
|
4
20
|
|
|
5
21
|
Passive replies in Kazee groups, and no endless bot-to-bot loops. Needs chat-central with the "Bot replies" group setting (an older server never sends the new mode, so groups keep today's behaviour). Canary first, on the sales bot.
|
package/README.md
CHANGED
|
@@ -448,8 +448,9 @@ All stored in `~/.open-claudia/`:
|
|
|
448
448
|
| `AGENT_ENV_PASSTHROUGH` | No | Comma-separated project environment keys to pass to provider children; reserved provider/control/keyring keys are always filtered and re-added only by the selected provider |
|
|
449
449
|
| `CLAUDE_MODEL` / `CODEX_MODEL` | No | Optional default model for the matching provider |
|
|
450
450
|
| `AUTO_COMPACT_TOKENS` | No | Auto-compact threshold in tokens (also settable via `/compactwindow`) |
|
|
451
|
-
| `
|
|
452
|
-
| `
|
|
451
|
+
| `USAGE_ALERTS` | No | Token usage alerts to the owner: `on` or `off` (default `off`). Never posted in group chats. The thresholds below apply only when on |
|
|
452
|
+
| `USAGE_ALERT_CONTEXT_TOKENS` | No | With alerts on: alert when one completed turn's context tokens exceed this ceiling (default `120000`, `off` disables) |
|
|
453
|
+
| `USAGE_ALERT_RATE_MULTIPLIER` | No | With alerts on: alert when the latest context-token rate exceeds the recent baseline by this multiple (default `1.75`, `off` disables) |
|
|
453
454
|
| `USAGE_ALERT_BASELINE_TURNS` / `USAGE_ALERT_MIN_BASELINE_TURNS` / `USAGE_ALERT_COOLDOWN_MS` | No | Tune token-rate baseline size, minimum sample size, and alert cooldown |
|
|
454
455
|
| `MEMORY_RECALL_MAX_CHARS` | No | Hard cap for auto-injected pack/entity memory per turn (default `9000`, `off` disables auto recall injection) |
|
|
455
456
|
| `RECALL_ENGINE` | No | Default recall engine when a chat hasn't set one via `/engine` (`classic` or `discoverer`, default `discoverer`) |
|
package/core/media.js
CHANGED
|
@@ -15,37 +15,95 @@ function speechBearerToken() {
|
|
|
15
15
|
|
|
16
16
|
function transcribeAudioLocal(oggPath) {
|
|
17
17
|
if (!WHISPER_CLI || !FFMPEG) return null;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
18
|
+
// Its own temp file, never the input's path with .wav: a .wav input (every
|
|
19
|
+
// transcribeMedia call) made ffmpeg overwrite the file it was reading.
|
|
20
|
+
const wavPath = path.join(TEMP_DIR, `stt-local-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.wav`);
|
|
21
|
+
try {
|
|
22
|
+
execSync(`"${FFMPEG}" -i "${oggPath}" -ar 16000 -ac 1 -y "${wavPath}" 2>/dev/null`);
|
|
23
|
+
const output = execSync(`"${WHISPER_CLI}" -m "${WHISPER_MODEL}" --no-timestamps -f "${wavPath}" 2>/dev/null`, { encoding: "utf-8" });
|
|
24
|
+
return output.split("\n")
|
|
25
|
+
.filter((l) => l.trim() && !l.startsWith("whisper_") && !l.startsWith("ggml_") && !l.startsWith("load_"))
|
|
26
|
+
.join(" ").trim();
|
|
27
|
+
} finally {
|
|
28
|
+
try { fs.unlinkSync(wavPath); } catch (e) {}
|
|
29
|
+
}
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
// Hosted STT: one engine serves every bot and takes notes in turn, so a burst
|
|
33
|
+
// can queue for a while. Wait for it, and retry only failures that mean busy or
|
|
34
|
+
// briefly away (rate limit, gateway restart or timeout, network drop).
|
|
35
|
+
const STT_TIMING = { waitMs: 240000, retryDelaysMs: [2000, 6000] };
|
|
36
|
+
const STT_RETRY_STATUSES = new Set([429, 502, 503, 504]);
|
|
37
|
+
|
|
38
|
+
async function transcribeHosted(oggPath, token) {
|
|
39
|
+
const deadline = Date.now() + STT_TIMING.waitMs;
|
|
40
|
+
let audio;
|
|
41
|
+
try {
|
|
42
|
+
audio = fs.readFileSync(oggPath);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error("Hosted STT error:", e.message);
|
|
45
|
+
return { error: "the audio file could not be read" };
|
|
46
|
+
}
|
|
47
|
+
for (let attempt = 0; ; attempt++) {
|
|
48
|
+
let failure;
|
|
49
|
+
let retry = false;
|
|
30
50
|
try {
|
|
31
51
|
const body = new FormData();
|
|
32
|
-
body.append("file", new Blob([
|
|
52
|
+
body.append("file", new Blob([audio]), path.basename(oggPath));
|
|
33
53
|
const res = await fetch(`${SPEECH_SERVICE_URL.replace(/\/$/, "")}/v1/audio/transcriptions`, {
|
|
34
54
|
method: "POST",
|
|
35
55
|
headers: { Authorization: `Bearer ${token}` },
|
|
36
56
|
body,
|
|
37
|
-
signal: AbortSignal.timeout(
|
|
57
|
+
signal: AbortSignal.timeout(Math.max(1000, deadline - Date.now())),
|
|
38
58
|
});
|
|
39
59
|
if (res.ok) {
|
|
40
|
-
const payload = await res.json();
|
|
41
|
-
return String(payload.text || "").trim();
|
|
60
|
+
const payload = await res.json().catch(() => null);
|
|
61
|
+
if (payload) return { text: String(payload.text || "").trim() };
|
|
62
|
+
failure = "the speech server sent an unreadable answer";
|
|
63
|
+
} else {
|
|
64
|
+
failure = `the speech server answered HTTP ${res.status}`;
|
|
65
|
+
retry = STT_RETRY_STATUSES.has(res.status);
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// A timeout means the server is still working through its queue; asking
|
|
69
|
+
// again would only queue a second copy behind the first.
|
|
70
|
+
if (e?.name === "TimeoutError" || e?.name === "AbortError") {
|
|
71
|
+
failure = `the speech server did not answer within ${Math.round(STT_TIMING.waitMs / 60000)} minutes`;
|
|
72
|
+
} else {
|
|
73
|
+
failure = `could not reach the speech server (${e.message})`;
|
|
74
|
+
retry = true;
|
|
42
75
|
}
|
|
43
|
-
|
|
76
|
+
}
|
|
77
|
+
const delay = STT_TIMING.retryDelaysMs[attempt];
|
|
78
|
+
if (!retry || delay === undefined || Date.now() + delay >= deadline) {
|
|
79
|
+
console.error(`Hosted STT failed: ${failure}`);
|
|
80
|
+
return { error: failure };
|
|
81
|
+
}
|
|
82
|
+
console.error(`Hosted STT: ${failure}; retrying in ${delay / 1000}s`);
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The transcript, or null when no route produced one; report.error then says
|
|
88
|
+
// why, in words fit for the person who sent the audio.
|
|
89
|
+
async function transcribeAudio(oggPath, report = {}) {
|
|
90
|
+
const reasons = [];
|
|
91
|
+
const token = speechBearerToken();
|
|
92
|
+
if (SPEECH_SERVICE_URL && token) {
|
|
93
|
+
const hosted = await transcribeHosted(oggPath, token);
|
|
94
|
+
if (hosted.text !== undefined) return hosted.text;
|
|
95
|
+
reasons.push(hosted.error);
|
|
96
|
+
}
|
|
97
|
+
if (WHISPER_CLI && FFMPEG) {
|
|
98
|
+
try {
|
|
99
|
+
return transcribeAudioLocal(oggPath);
|
|
44
100
|
} catch (e) {
|
|
45
|
-
console.error("
|
|
101
|
+
console.error("Local STT error:", e.message);
|
|
102
|
+
reasons.push("the local transcriber failed");
|
|
46
103
|
}
|
|
47
104
|
}
|
|
48
|
-
|
|
105
|
+
report.error = reasons.join(", and ") || "no speech-to-text is set up";
|
|
106
|
+
return null;
|
|
49
107
|
}
|
|
50
108
|
|
|
51
109
|
function cleanForTTS(text) {
|
|
@@ -278,7 +336,8 @@ function canTranscribe() {
|
|
|
278
336
|
// Transcribe ANY audio/video file (mp3, m4a, wav, ogg, mp4, mkv...): normalize
|
|
279
337
|
// to 16kHz mono wav first when ffmpeg is available — that extracts the audio
|
|
280
338
|
// track from video containers and shrinks the gateway upload. Returns the
|
|
281
|
-
// transcript string, or null when no STT route exists
|
|
339
|
+
// transcript string, or null when no STT route exists; throws with the reason
|
|
340
|
+
// when a route exists but failed.
|
|
282
341
|
async function transcribeMedia(filePath) {
|
|
283
342
|
if (!canTranscribe()) return null;
|
|
284
343
|
let wavPath = null;
|
|
@@ -296,7 +355,10 @@ async function transcribeMedia(filePath) {
|
|
|
296
355
|
}
|
|
297
356
|
}
|
|
298
357
|
try {
|
|
299
|
-
|
|
358
|
+
const report = {};
|
|
359
|
+
const text = await transcribeAudio(sendPath, report);
|
|
360
|
+
if (text === null) throw new Error(report.error);
|
|
361
|
+
return text;
|
|
300
362
|
} finally {
|
|
301
363
|
if (wavPath) { try { fs.unlinkSync(wavPath); } catch (e) {} }
|
|
302
364
|
}
|
|
@@ -356,4 +418,4 @@ async function synthSentenceMp3(text) {
|
|
|
356
418
|
}
|
|
357
419
|
}
|
|
358
420
|
|
|
359
|
-
module.exports = { transcribeAudio, transcribeAudioLocal, transcribeMedia, canTranscribe, textToVoice, textToVoiceFile, splitSentences, synthSentenceMp3, pocketToVoice, chunkForTTS, cloneVoiceFromClip, speechBearerToken, canSpeak, TTS_CMD };
|
|
421
|
+
module.exports = { transcribeAudio, transcribeAudioLocal, transcribeMedia, STT_TIMING, canTranscribe, textToVoice, textToVoiceFile, splitSentences, synthSentenceMp3, pocketToVoice, chunkForTTS, cloneVoiceFromClip, speechBearerToken, canSpeak, TTS_CMD };
|
package/core/router.js
CHANGED
|
@@ -244,6 +244,12 @@ function startPreTurnTyping(state) {
|
|
|
244
244
|
try { return startTypingHeartbeat(state); } catch (_) { return () => {}; }
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// "Couldn't transcribe", with the reason when a speech route failed (silence
|
|
248
|
+
// leaves none), so a broken speech server is visible from the chat.
|
|
249
|
+
function transcribeFailure(report, tail = "") {
|
|
250
|
+
return `Couldn't transcribe${report.error ? ` (${report.error})` : ""}.${tail}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
247
253
|
async function handleVoice(envelope) {
|
|
248
254
|
if (isDuplicate(envelope)) return;
|
|
249
255
|
const state = currentState();
|
|
@@ -270,10 +276,11 @@ async function handleVoice(envelope) {
|
|
|
270
276
|
const tStart = Date.now();
|
|
271
277
|
const oggPath = await envelope.adapter.downloadMedia(media);
|
|
272
278
|
const tAfterDownload = Date.now();
|
|
273
|
-
const
|
|
279
|
+
const stt = {};
|
|
280
|
+
const transcript = await transcribeAudio(oggPath, stt);
|
|
274
281
|
const tAfterStt = Date.now();
|
|
275
282
|
try { fs.unlinkSync(oggPath); } catch (e) {}
|
|
276
|
-
if (!transcript) return send("
|
|
283
|
+
if (!transcript) return send(transcribeFailure(stt, " Try typing it."));
|
|
277
284
|
await send(`Heard: "${transcript}"`, { replyTo: envelope.messageId });
|
|
278
285
|
if (!(await admitGuardedTurn(transcript, envelope, state))) return;
|
|
279
286
|
// Voice flags live on the conversation's lane: a voice note in one thread
|
|
@@ -312,9 +319,10 @@ async function handleAudio(envelope) {
|
|
|
312
319
|
});
|
|
313
320
|
stopPreTurnTyping = startPreTurnTyping(state);
|
|
314
321
|
const p = await envelope.adapter.downloadMedia(media);
|
|
315
|
-
const
|
|
322
|
+
const stt = {};
|
|
323
|
+
const t = await transcribeAudio(p, stt);
|
|
316
324
|
try { fs.unlinkSync(p); } catch (e) {}
|
|
317
|
-
if (!t) return send(
|
|
325
|
+
if (!t) return send(transcribeFailure(stt));
|
|
318
326
|
await send(`Heard: "${t}"`, { replyTo: envelope.messageId });
|
|
319
327
|
if (!(await admitGuardedTurn(t, envelope, state))) return;
|
|
320
328
|
return runWithContext(t, runContext, envelope, {
|
package/core/runner.js
CHANGED
|
@@ -234,10 +234,13 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
|
|
|
234
234
|
if (result.alert) {
|
|
235
235
|
state.usageAlertLastAt = Date.now();
|
|
236
236
|
// Owner-only: version/model/context internals must never surface to an
|
|
237
|
-
// external speaker, regardless of the usage-alert policy.
|
|
237
|
+
// external speaker, regardless of the usage-alert policy. Never in a group
|
|
238
|
+
// chat either: everyone else in the room sees only noise.
|
|
238
239
|
let usageOwnerOnly = true;
|
|
239
240
|
try { usageOwnerOnly = !require("./relationship").isCurrentSpeakerGuarded(); } catch (e) {}
|
|
240
|
-
|
|
241
|
+
let inGroup = false;
|
|
242
|
+
try { inGroup = require("./context").currentIsGroup(); } catch (e) {}
|
|
243
|
+
if (announce && usageOwnerOnly && !inGroup) {
|
|
241
244
|
const baseline = result.trend?.baselineAvgContextTokens || 0;
|
|
242
245
|
const rate = result.trend?.latestRate || 0;
|
|
243
246
|
const contextLabel = usageScope === "session_total" && !hasPeak ? "Session context total" : "Context this turn";
|
|
@@ -1704,6 +1707,13 @@ async function finalizeStreamPreview(lane, result, runContext = null) {
|
|
|
1704
1707
|
} catch (_) { /* preview cleanup is best-effort; the final answer still goes out */ }
|
|
1705
1708
|
}
|
|
1706
1709
|
|
|
1710
|
+
// When a restart cuts a turn short, Claude Code resumes that session by writing
|
|
1711
|
+
// two lines into it: a user "Continue from where you left off." and a synthetic
|
|
1712
|
+
// assistant "No response requested." (model "<synthetic>", never streamed to us).
|
|
1713
|
+
// A model that later sees them in its history can answer a wake-up with the same
|
|
1714
|
+
// words. That is never an answer, so it is never posted.
|
|
1715
|
+
const PLACEHOLDER_REPLY = /^no response requested\.?$/i;
|
|
1716
|
+
|
|
1707
1717
|
async function deliverForegroundResult(result, runContext, state = currentState()) {
|
|
1708
1718
|
const lane = laneForRun(state, runContext);
|
|
1709
1719
|
if (runContext.lastInputWasVoice) lane.lastInputWasVoice = false;
|
|
@@ -1722,6 +1732,11 @@ async function deliverForegroundResult(result, runContext, state = currentState(
|
|
|
1722
1732
|
const cleanAnswer = String(result.finalAnswerText || "").trim();
|
|
1723
1733
|
let finalText = result.ok ? (cleanAnswer || result.text || "(no output)") : (diagnostic || result.diagnostic || "Provider run failed");
|
|
1724
1734
|
|
|
1735
|
+
if (result.ok && PLACEHOLDER_REPLY.test(finalText.trim())) {
|
|
1736
|
+
console.log(`[runner] not posting the placeholder reply "No response requested." (${runContext.purpose || "foreground"} run ${runContext.runId || "?"})`);
|
|
1737
|
+
return { ok: true, held: true, suppressed: "placeholder" };
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1725
1740
|
if (result.ok) {
|
|
1726
1741
|
let guarded = false;
|
|
1727
1742
|
try { guarded = require("./relationship").isCurrentSpeakerGuarded(); } catch (error) {
|
|
@@ -2219,16 +2234,27 @@ function nameThreadAfterTurn(result, runContext, store) {
|
|
|
2219
2234
|
// detector spots durable-rule statements ("from now on / never / stop") in the
|
|
2220
2235
|
// owner's message and captures them into lessons.md (or asks first). Owner-only
|
|
2221
2236
|
// and best-effort: it never blocks or breaks a turn.
|
|
2237
|
+
//
|
|
2238
|
+
// Only the owner's own words count: store.inboundText (the typed message,
|
|
2239
|
+
// voice transcript or caption, set by admitGuardedTurn), never the prompt,
|
|
2240
|
+
// which also carries injected group/thread messages, quoted replies and the
|
|
2241
|
+
// bot's own earlier lines. Reading the prompt saved the bot's own "📌 ... from
|
|
2242
|
+
// now on" notes as the owner's rules, one more after every owner turn in a
|
|
2243
|
+
// group (sales bot, 4 Oct 2026). Group chats never capture: a rule note there
|
|
2244
|
+
// is noise for everyone else, so rules are set in a direct chat with the bot.
|
|
2222
2245
|
async function considerGuardrail(result, runContext, store) {
|
|
2223
2246
|
try {
|
|
2224
2247
|
if (!result.ok || runContext.purpose !== "foreground") return;
|
|
2248
|
+
if (store?.isGroup) return;
|
|
2249
|
+
const ownWords = String(store?.inboundText || "").trim();
|
|
2250
|
+
if (!ownWords) return;
|
|
2225
2251
|
const guardrail = require("./guardrail");
|
|
2226
2252
|
if (!guardrail.enabled()) return;
|
|
2227
2253
|
const relationship = require("./relationship");
|
|
2228
2254
|
const speaker = relationship.speakerFor(store?.adapter?.type, store?.channelId, store?.userId);
|
|
2229
2255
|
if (relationship.guardActive(speaker)) return; // external speakers never capture
|
|
2230
2256
|
const announce = (text, opts) => chatContext.run(store, () => (statusChatterMuted() ? true : send(text, opts)));
|
|
2231
|
-
await guardrail.consider({ text:
|
|
2257
|
+
await guardrail.consider({ text: ownWords, speaker, announce });
|
|
2232
2258
|
} catch (e) { /* best-effort — a guardrail miss must never break a turn */ }
|
|
2233
2259
|
}
|
|
2234
2260
|
|
|
@@ -2706,4 +2732,6 @@ module.exports = {
|
|
|
2706
2732
|
archiveCompactionBrief,
|
|
2707
2733
|
collectRepoStateFacts,
|
|
2708
2734
|
collectRecentTurns,
|
|
2735
|
+
considerGuardrail,
|
|
2736
|
+
logTurnUsage,
|
|
2709
2737
|
};
|
package/core/system-prompt.js
CHANGED
|
@@ -372,6 +372,8 @@ Notes: a background daemon persists between commands, so navigation state carrie
|
|
|
372
372
|
|
|
373
373
|
If you tell the user "I'll check back in N minutes" or "I'll run this every morning", you MUST schedule it with one of the commands above in the same turn. Otherwise nothing happens.
|
|
374
374
|
|
|
375
|
+
Interrupted turns: when a restart cuts a turn short, the agent CLI resumes the conversation by writing two lines into your history, "Continue from where you left off." and the reply "No response requested.". Neither came from the user or from you, and the user never saw them. Never mention or apologise for them, and never reply "No response requested." yourself: finish the interrupted work if it still matters, then act on the current message.
|
|
376
|
+
|
|
375
377
|
## Guidelines
|
|
376
378
|
- Keep responses concise — many users are on mobile.
|
|
377
379
|
- Be time-aware: the Runtime state block carries the current local time. Before suggesting that the user contact, test with, or wait on a colleague, check it against that person's working hours (entity notes often record their timezone). Late evening / night / early morning → schedule a wakeup for their next working morning instead of suggesting it now.
|
package/core/usage-log.js
CHANGED
|
@@ -56,11 +56,20 @@ function floatEnv(env, key, fallback) {
|
|
|
56
56
|
return Number.isFinite(n) ? n : fallback;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Token usage alerts are an operator's tuning aid, so they are off unless
|
|
60
|
+
// USAGE_ALERTS=on (Sumeet, 4 Oct 2026: "turn off this token usage thing by
|
|
61
|
+
// default on all the bots", after one landed in a staff group). The
|
|
62
|
+
// thresholds below only apply once they are switched on.
|
|
63
|
+
function usageAlertsOn(env = {}) {
|
|
64
|
+
const raw = env.USAGE_ALERTS ?? process.env.USAGE_ALERTS;
|
|
65
|
+
return /^(on|true|yes|1)$/i.test(String(raw ?? "").trim());
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
function usageAlertPolicy(env = {}) {
|
|
60
69
|
const absoluteContextTokens = intEnv(env, "USAGE_ALERT_CONTEXT_TOKENS", 120000);
|
|
61
70
|
const rateMultiplier = floatEnv(env, "USAGE_ALERT_RATE_MULTIPLIER", 1.75);
|
|
62
71
|
return {
|
|
63
|
-
enabled: absoluteContextTokens > 0 || rateMultiplier > 0,
|
|
72
|
+
enabled: usageAlertsOn(env) && (absoluteContextTokens > 0 || rateMultiplier > 0),
|
|
64
73
|
absoluteContextTokens,
|
|
65
74
|
rateMultiplier,
|
|
66
75
|
baselineTurns: intEnv(env, "USAGE_ALERT_BASELINE_TURNS", 20),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.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": {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"test:kazee-call": "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-call-speech.js && node test-kazee-call-session.js",
|
|
11
11
|
"setup": "node setup.js",
|
|
12
12
|
"start": "node bot.js",
|
|
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-codex-device-login.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-upgrade-path.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-context-media.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-call-speech.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 && 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-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-read-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-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-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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 && node test-kazee-cards.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-passive-replies-router.js && node test-loopback-cards.js"
|
|
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-codex-device-login.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-upgrade-path.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-context-media.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-group-status-notes.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-call-speech.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 && 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-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-read-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-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-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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 && node test-kazee-cards.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-placeholder-reply.js && node test-media-speech.js && node test-passive-replies-router.js && node test-loopback-cards.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"test-agency-ledger.js",
|
|
86
86
|
"test-agency-deliberate.js",
|
|
87
87
|
"test-guardrail.js",
|
|
88
|
+
"test-group-status-notes.js",
|
|
88
89
|
"test-delivery-contract.js",
|
|
89
90
|
"test-run-lock.js",
|
|
90
91
|
"test-thread-lanes.js",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Notes meant only for the owner stay out of group chats, and a standing rule
|
|
2
|
+
// comes only from the owner's own words. Sales bot, 4 Oct 2026: after each of
|
|
3
|
+
// Sumeet's turns in a staff group it posted "📌 Got it — I'll keep this loaded
|
|
4
|
+
// as a standing rule from now on" and saved its OWN previous 📌 line (shown to
|
|
5
|
+
// it as group context) as his rule, one more per turn; a "Token usage alert"
|
|
6
|
+
// landed in the same group. Token alerts are now off unless USAGE_ALERTS=on.
|
|
7
|
+
// No network, redirected config dir.
|
|
8
|
+
|
|
9
|
+
const assert = require("assert");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const os = require("os");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
|
|
14
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "oc-group-notes-"));
|
|
15
|
+
process.env.OPEN_CLAUDIA_TEST = "1";
|
|
16
|
+
process.env.OPEN_CLAUDIA_CONFIG_DIR = TMP;
|
|
17
|
+
process.env.WORKSPACE = TMP;
|
|
18
|
+
process.env.GUARDRAIL_CAPTURE = "on";
|
|
19
|
+
delete process.env.USAGE_ALERTS;
|
|
20
|
+
|
|
21
|
+
const lessons = require("./core/lessons");
|
|
22
|
+
const guardrail = require("./core/guardrail");
|
|
23
|
+
const relationship = require("./core/relationship");
|
|
24
|
+
const { runInChat } = require("./core/context");
|
|
25
|
+
const { usageAlertPolicy, evaluateUsageAlert } = require("./core/usage-log");
|
|
26
|
+
const { considerGuardrail, logTurnUsage } = require("./core/runner");
|
|
27
|
+
|
|
28
|
+
// The speaker is the owner throughout: who may capture at all is
|
|
29
|
+
// test-guardrail.js's job, this file is about which words and which rooms.
|
|
30
|
+
relationship.speakerFor = () => ({ isOwner: true, channelId: "kazee:room" });
|
|
31
|
+
relationship.guardActive = () => false;
|
|
32
|
+
relationship.isCurrentSpeakerGuarded = () => false;
|
|
33
|
+
|
|
34
|
+
function resetLessons() {
|
|
35
|
+
try { fs.rmSync(lessons.LESSONS_FILE, { force: true }); } catch (e) {}
|
|
36
|
+
try { fs.rmSync(lessons.LESSONS_META_FILE, { force: true }); } catch (e) {}
|
|
37
|
+
try { fs.rmSync(guardrail.PENDING_FILE, { force: true }); } catch (e) {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A chat store as the router builds it: inboundText is what the person said
|
|
41
|
+
// (typed text, voice transcript or caption), isGroup marks a group room.
|
|
42
|
+
function chat({ isGroup, inboundText }) {
|
|
43
|
+
const sent = [];
|
|
44
|
+
const adapter = {
|
|
45
|
+
type: "kazee",
|
|
46
|
+
send: async (channelId, text) => { sent.push(text); return { ok: true, messageId: `m${sent.length}` }; },
|
|
47
|
+
};
|
|
48
|
+
return { sent, store: { adapter, channelId: "kazee:room", userId: "owner", isGroup, inboundText } };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const OK = { ok: true };
|
|
52
|
+
const turn = (userPrompt) => ({ purpose: "foreground", userPrompt });
|
|
53
|
+
const RULE = "From now on, always send the deep link after posting a Spaces comment.";
|
|
54
|
+
const PIN = "📌 Got it — I'll keep this loaded as a standing rule from now on";
|
|
55
|
+
|
|
56
|
+
(async () => {
|
|
57
|
+
// 1. The live case: a question in a group whose context starts with the
|
|
58
|
+
// bot's own last 📌 note. Nothing is saved and nothing is posted.
|
|
59
|
+
resetLessons();
|
|
60
|
+
let c = chat({ isGroup: true, inboundText: "How many leads did we get last month?" });
|
|
61
|
+
await considerGuardrail(OK, turn([
|
|
62
|
+
"[Group context] Messages since your last reply:",
|
|
63
|
+
` [1d ago] You: ${PIN}`,
|
|
64
|
+
" [2m ago] Sumeet Rajpal: How many leads did we get last month?",
|
|
65
|
+
"",
|
|
66
|
+
"How many leads did we get last month?",
|
|
67
|
+
].join("\n")), c.store);
|
|
68
|
+
assert.deepStrictEqual(lessons.readLessons(), [], "the bot's own 📌 line in the context is not the owner's rule");
|
|
69
|
+
assert.deepStrictEqual(c.sent, [], "no 📌 note in the group");
|
|
70
|
+
|
|
71
|
+
// 2. A direct thread with the bot quoting its earlier 📌 note: the owner's
|
|
72
|
+
// own words hold no rule, so nothing is saved.
|
|
73
|
+
resetLessons();
|
|
74
|
+
c = chat({ isGroup: false, inboundText: "And deals?" });
|
|
75
|
+
await considerGuardrail(OK, turn(`[Kazee thread context]\n [36s ago] You: ${PIN}\n\nAnd deals?`), c.store);
|
|
76
|
+
assert.deepStrictEqual(lessons.readLessons(), [], "a quoted bot line never becomes a rule");
|
|
77
|
+
assert.deepStrictEqual(c.sent, []);
|
|
78
|
+
|
|
79
|
+
// 3. The owner states a rule in a direct chat: saved, and the 📌 note goes
|
|
80
|
+
// to that chat with its Undo.
|
|
81
|
+
resetLessons();
|
|
82
|
+
c = chat({ isGroup: false, inboundText: RULE });
|
|
83
|
+
await considerGuardrail(OK, turn(`[Kazee thread context]\n [5m ago] You: Done.\n\n${RULE}`), c.store);
|
|
84
|
+
const saved = lessons.readLessons();
|
|
85
|
+
assert.strictEqual(saved.length, 1, "the owner's own rule is saved");
|
|
86
|
+
assert.ok(/deep link/i.test(saved[0].text), saved[0].text);
|
|
87
|
+
assert.strictEqual(c.sent.length, 1);
|
|
88
|
+
assert.ok(c.sent[0].startsWith("📌"), c.sent[0]);
|
|
89
|
+
|
|
90
|
+
// 4. The same words in a group: rules are set in a direct chat, so the
|
|
91
|
+
// group gets no note and nothing is saved.
|
|
92
|
+
resetLessons();
|
|
93
|
+
c = chat({ isGroup: true, inboundText: RULE });
|
|
94
|
+
await considerGuardrail(OK, turn(RULE), c.store);
|
|
95
|
+
assert.deepStrictEqual(lessons.readLessons(), [], "groups never capture");
|
|
96
|
+
assert.deepStrictEqual(c.sent, []);
|
|
97
|
+
|
|
98
|
+
// 5. A turn with none of the owner's words (wake-up, cron): the prompt alone
|
|
99
|
+
// never captures, even when it reads like a rule.
|
|
100
|
+
resetLessons();
|
|
101
|
+
c = chat({ isGroup: false, inboundText: "" });
|
|
102
|
+
await considerGuardrail(OK, turn(`[Scheduled wakeup "x" fired] ${RULE}`), c.store);
|
|
103
|
+
assert.deepStrictEqual(lessons.readLessons(), [], "no own words, no capture");
|
|
104
|
+
|
|
105
|
+
// Token usage alerts: off by default; thresholds alone do not switch them
|
|
106
|
+
// on; USAGE_ALERTS=on does, unless both thresholds are off.
|
|
107
|
+
const big = { contextTokens: 127758 };
|
|
108
|
+
assert.strictEqual(usageAlertPolicy({}).enabled, false, "alerts are off by default");
|
|
109
|
+
assert.strictEqual(evaluateUsageAlert([], big, usageAlertPolicy({})).alert, false);
|
|
110
|
+
assert.strictEqual(usageAlertPolicy({ USAGE_ALERT_CONTEXT_TOKENS: "120000" }).enabled, false);
|
|
111
|
+
assert.strictEqual(usageAlertPolicy({ USAGE_ALERTS: "on" }).enabled, true);
|
|
112
|
+
assert.strictEqual(evaluateUsageAlert([], big, usageAlertPolicy({ USAGE_ALERTS: "on" })).alert, true);
|
|
113
|
+
assert.strictEqual(usageAlertPolicy({
|
|
114
|
+
USAGE_ALERTS: "on", USAGE_ALERT_CONTEXT_TOKENS: "off", USAGE_ALERT_RATE_MULTIPLIER: "off",
|
|
115
|
+
}).enabled, false);
|
|
116
|
+
|
|
117
|
+
// The turn's usage log: a 127.8k-context turn announces nothing by default,
|
|
118
|
+
// announces once in a direct chat with USAGE_ALERTS=on, and never in a group.
|
|
119
|
+
const usage = { input_tokens: 1000, output_tokens: 200, cache_read_input_tokens: 126558 };
|
|
120
|
+
async function alertsFor({ isGroup }) {
|
|
121
|
+
const alerts = [];
|
|
122
|
+
const announce = async (text) => { alerts.push(text); };
|
|
123
|
+
const state = { userId: "owner", sessionUsage: {} };
|
|
124
|
+
const { store } = chat({ isGroup, inboundText: "And deals?" });
|
|
125
|
+
await runInChat({ ...store }, async () => logTurnUsage(state, usage, 0, announce, {
|
|
126
|
+
backend: "claude", model: "claude-test", sessionId: "s1", liveContextTokens: 127758,
|
|
127
|
+
}));
|
|
128
|
+
return alerts;
|
|
129
|
+
}
|
|
130
|
+
assert.deepStrictEqual(await alertsFor({ isGroup: false }), [], "off by default in a direct chat too");
|
|
131
|
+
process.env.USAGE_ALERTS = "on";
|
|
132
|
+
const direct = await alertsFor({ isGroup: false });
|
|
133
|
+
assert.strictEqual(direct.length, 1, "USAGE_ALERTS=on announces in a direct chat");
|
|
134
|
+
assert.ok(direct[0].startsWith("Token usage alert:"), direct[0]);
|
|
135
|
+
assert.deepStrictEqual(await alertsFor({ isGroup: true }), [], "never in a group");
|
|
136
|
+
delete process.env.USAGE_ALERTS;
|
|
137
|
+
|
|
138
|
+
console.log("group status notes: ok");
|
|
139
|
+
process.exit(0);
|
|
140
|
+
})().catch((error) => { console.error(error); process.exit(1); });
|
|
@@ -540,7 +540,20 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
540
540
|
// then let's test on something like sales bot which is in groups"): passive
|
|
541
541
|
// replies in Kazee groups, no endless bot-to-bot loops, and decide asks in the
|
|
542
542
|
// thread that queued it. Canary on the sales bot only.
|
|
543
|
-
|
|
543
|
+
// 3.5.12 approved by Sumeet 2026-10-04 08:47 EAT ("Yes, ship it" on the card
|
|
544
|
+
// "Ship Open Claudia 3.5.11 and move the sales bot onto it?"): never post
|
|
545
|
+
// Claude Code's "No response requested." placeholder, and voice notes wait out
|
|
546
|
+
// the speech queue and say why they fail. 3.5.11 was tagged but never
|
|
547
|
+
// published (its release run failed on a test), so the same release ships as
|
|
548
|
+
// 3.5.12. Canary on the sales bot only.
|
|
549
|
+
// 3.5.13 approved by Sumeet 2026-10-04 12:27 EAT ("Yes, fix and ship" on the card
|
|
550
|
+
// "Switch token alerts off on every bot, fix the junk 📌 posts, and ship 3.5.13
|
|
551
|
+
// to the sales bot?"), after asking at 11:44 EAT ("Can we turn of[f] this token
|
|
552
|
+
// usage thing by default on all the bots"): rules come only from the owner's own
|
|
553
|
+
// words in a direct chat, so group chats never get junk "standing rule" posts,
|
|
554
|
+
// and token usage alerts are off by default and never go to a group chat.
|
|
555
|
+
// Canary on the sales bot only.
|
|
556
|
+
assert.strictEqual(pkg.version, "3.5.13", "release version must remain unchanged without explicit approval");
|
|
544
557
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
545
558
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
546
559
|
}
|