@inetafrica/open-claudia 3.1.28 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/bin/cli.js +105 -0
- package/bin/entity.js +9 -0
- package/bin/pack.js +10 -0
- package/bin/recall-case.js +90 -0
- package/bin/remind.js +82 -0
- package/bin/tool.js +8 -3
- package/channels/kazee/adapter.js +37 -3
- package/channels/kazee/call/manager.js +128 -2
- package/channels/kazee/call/session.js +63 -25
- package/channels/kazee/socket.js +3 -0
- package/channels/spaces/socket.js +3 -0
- package/channels/telegram/adapter.js +9 -5
- package/core/apprenticeship.js +104 -0
- package/core/approvals.js +39 -0
- package/core/cost-report.js +140 -0
- package/core/dedup-adjudicator.js +250 -0
- package/core/dream.js +41 -8
- package/core/entities.js +27 -5
- package/core/loopback.js +51 -1
- package/core/media.js +58 -5
- package/core/pack-review.js +115 -12
- package/core/packs.js +102 -4
- package/core/prospective.js +135 -0
- package/core/recall-regressions.js +171 -0
- package/core/socket-reconnect.js +71 -0
- package/core/system-prompt.js +84 -6
- package/core/tools.js +77 -0
- package/package.json +3 -3
- package/test-fixtures/fake-agent-cli.js +5 -1
- package/test-kazee-call-manager.js +44 -1
- package/test-kazee-call-session.js +45 -2
- package/test-provider-language.js +11 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// C2: memory regression suite — every corrected recall miss becomes a
|
|
4
|
+
// permanent test case, re-graded nightly. Cases live in a JSONL store; the
|
|
5
|
+
// nightly runner grades each query against the DETERMINISTIC recall floor
|
|
6
|
+
// (matchPacks + matchEntities — the classic candidate stage every engine
|
|
7
|
+
// builds on; the discoverer's LLM walk only adds to it and is fail-open back
|
|
8
|
+
// to this). No model call anywhere: the suite must run — and mean the same
|
|
9
|
+
// thing — even on nights the providers are down. Pass-rate + delta land in
|
|
10
|
+
// the dream summary's memory-health section.
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const { atomicWriteFileSync } = require("./fsutil");
|
|
15
|
+
const { resolveConfigDir, ensureConfigDir } = require("./config-dir");
|
|
16
|
+
|
|
17
|
+
const NIGHTLY_CAP = Number(process.env.RECALL_REGRESSION_CAP || 20);
|
|
18
|
+
const HISTORY_MAX = 90;
|
|
19
|
+
|
|
20
|
+
function casesPath() { return path.join(resolveConfigDir(), "recall-regressions.jsonl"); }
|
|
21
|
+
function historyPath() { return path.join(resolveConfigDir(), "recall-health-history.json"); }
|
|
22
|
+
|
|
23
|
+
function loadCases() {
|
|
24
|
+
try {
|
|
25
|
+
return fs.readFileSync(casesPath(), "utf-8")
|
|
26
|
+
.split("\n").filter(Boolean)
|
|
27
|
+
.map((l) => { try { return JSON.parse(l); } catch (e) { return null; } })
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function saveCases(cases) {
|
|
35
|
+
ensureConfigDir();
|
|
36
|
+
atomicWriteFileSync(casesPath(), cases.map((c) => JSON.stringify(c)).join("\n") + (cases.length ? "\n" : ""));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addCase({ query, expectSlugs, source = "manual", note = "" } = {}) {
|
|
40
|
+
const q = String(query || "").trim();
|
|
41
|
+
if (!q) throw new Error("query is required");
|
|
42
|
+
const expect = [].concat(expectSlugs || []).map((s) => String(s || "").trim()).filter(Boolean);
|
|
43
|
+
if (expect.length === 0) throw new Error("at least one expected slug is required");
|
|
44
|
+
if (source !== "manual" && source !== "live-miss") throw new Error(`source must be manual or live-miss (got ${source})`);
|
|
45
|
+
const entry = {
|
|
46
|
+
id: `rc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
47
|
+
query: q,
|
|
48
|
+
expectSlugs: expect,
|
|
49
|
+
source,
|
|
50
|
+
note: String(note || ""),
|
|
51
|
+
addedAt: new Date().toISOString(),
|
|
52
|
+
lastRunAt: null,
|
|
53
|
+
lastResult: null,
|
|
54
|
+
};
|
|
55
|
+
const cases = loadCases();
|
|
56
|
+
cases.push(entry);
|
|
57
|
+
saveCases(cases);
|
|
58
|
+
return entry;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function listCases() {
|
|
62
|
+
return loadCases();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function removeCase(id) {
|
|
66
|
+
const cases = loadCases();
|
|
67
|
+
const next = cases.filter((c) => c.id !== id);
|
|
68
|
+
if (next.length === cases.length) return false;
|
|
69
|
+
saveCases(next);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Grade one case against the deterministic floor. Expected slugs that no
|
|
74
|
+
// longer exist anywhere (pack merged away, entity removed) are not recall
|
|
75
|
+
// failures — a case with NO live target is "orphaned"; live targets are
|
|
76
|
+
// graded normally.
|
|
77
|
+
function gradeCase(c, { packLimit, entityLimit } = {}) {
|
|
78
|
+
const packs = require("./packs");
|
|
79
|
+
const entities = require("./entities");
|
|
80
|
+
const pl = packLimit || Number(process.env.PACK_MATCH_LIMIT || 6);
|
|
81
|
+
const el = entityLimit || 4;
|
|
82
|
+
const exists = (slug) => {
|
|
83
|
+
try { if (packs.readPack(slug)) return true; } catch (e) {}
|
|
84
|
+
try { if (entities.readEntity(slug)) return true; } catch (e) {}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
const live = c.expectSlugs.filter(exists);
|
|
88
|
+
if (live.length === 0) return { status: "orphaned", missing: c.expectSlugs.slice() };
|
|
89
|
+
const found = new Set();
|
|
90
|
+
try { for (const m of packs.matchPacks(c.query, { limit: pl })) found.add(m.dir); } catch (e) {}
|
|
91
|
+
try { for (const m of entities.matchEntities(c.query, { limit: el })) found.add(m.slug); } catch (e) {}
|
|
92
|
+
const missing = live.filter((s) => !found.has(s));
|
|
93
|
+
return missing.length === 0
|
|
94
|
+
? { status: "pass" }
|
|
95
|
+
: { status: "fail", missing, matched: [...found] };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Nightly-capped, oldest-run-first rotation: a store bigger than the cap
|
|
99
|
+
// still gets full coverage over successive nights. Orphaned cases are
|
|
100
|
+
// dropped from the store (same policy as prospective-reminder GC).
|
|
101
|
+
function runSuite({ cap = NIGHTLY_CAP, packLimit, entityLimit, now = Date.now() } = {}) {
|
|
102
|
+
const all = loadCases();
|
|
103
|
+
if (all.length === 0) return { ran: 0, passed: 0, failed: 0, orphaned: 0, failures: [], total: 0 };
|
|
104
|
+
const order = [...all].sort((x, y) => String(x.lastRunAt || "").localeCompare(String(y.lastRunAt || "")));
|
|
105
|
+
const toRun = order.slice(0, Math.max(1, cap));
|
|
106
|
+
const stamp = new Date(now).toISOString();
|
|
107
|
+
const failures = [];
|
|
108
|
+
const orphanIds = new Set();
|
|
109
|
+
let passed = 0, failed = 0;
|
|
110
|
+
for (const c of toRun) {
|
|
111
|
+
const g = gradeCase(c, { packLimit, entityLimit });
|
|
112
|
+
c.lastRunAt = stamp;
|
|
113
|
+
c.lastResult = g.status;
|
|
114
|
+
if (g.status === "pass") passed++;
|
|
115
|
+
else if (g.status === "fail") { failed++; failures.push({ id: c.id, query: c.query, missing: g.missing }); }
|
|
116
|
+
else orphanIds.add(c.id);
|
|
117
|
+
}
|
|
118
|
+
const kept = all.filter((c) => !orphanIds.has(c.id));
|
|
119
|
+
saveCases(kept);
|
|
120
|
+
return { ran: toRun.length - orphanIds.size, passed, failed, orphaned: orphanIds.size, failures, total: kept.length };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function loadHistory() {
|
|
124
|
+
try {
|
|
125
|
+
const h = JSON.parse(fs.readFileSync(historyPath(), "utf-8"));
|
|
126
|
+
return Array.isArray(h) ? h : [];
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Dream entry point: grade, record the trend, and return announce lines for
|
|
133
|
+
// the consolidation report. Empty store → zero cost, zero lines.
|
|
134
|
+
function nightlyRun(opts = {}) {
|
|
135
|
+
const res = runSuite(opts);
|
|
136
|
+
if (res.ran === 0 && res.orphaned === 0) return { ...res, passRate: null, lines: [] };
|
|
137
|
+
const history = loadHistory();
|
|
138
|
+
const prev = history.length ? history[history.length - 1] : null;
|
|
139
|
+
const rate = res.ran > 0 ? Math.round((res.passed / res.ran) * 100) : null;
|
|
140
|
+
if (res.ran > 0) {
|
|
141
|
+
history.push({ at: new Date().toISOString(), ran: res.ran, passed: res.passed, failed: res.failed, orphaned: res.orphaned, passRate: rate });
|
|
142
|
+
ensureConfigDir();
|
|
143
|
+
atomicWriteFileSync(historyPath(), JSON.stringify(history.slice(-HISTORY_MAX), null, 2) + "\n");
|
|
144
|
+
}
|
|
145
|
+
const lines = [];
|
|
146
|
+
if (res.ran > 0) {
|
|
147
|
+
const delta = prev && typeof prev.passRate === "number" && rate !== null ? rate - prev.passRate : null;
|
|
148
|
+
const deltaTxt = delta === null ? "" : ` (${delta >= 0 ? "+" : ""}${delta} vs last run)`;
|
|
149
|
+
let line = `🧪 Memory health: ${res.passed}/${res.ran} recall regression case${res.ran === 1 ? "" : "s"} passing — ${rate}%${deltaTxt}.`;
|
|
150
|
+
if (res.failed > 0) {
|
|
151
|
+
const detail = res.failures.slice(0, 3).map((f) => `"${f.query.slice(0, 40)}" (missing ${f.missing.join(", ")})`).join("; ");
|
|
152
|
+
line += ` Failing: ${detail}${res.failures.length > 3 ? ` +${res.failures.length - 3} more` : ""} — open-claudia recall-case list`;
|
|
153
|
+
}
|
|
154
|
+
lines.push(line);
|
|
155
|
+
}
|
|
156
|
+
if (res.orphaned > 0) lines.push(`🧪 Dropped ${res.orphaned} recall regression case${res.orphaned === 1 ? "" : "s"} whose target no longer exists.`);
|
|
157
|
+
return { ...res, passRate: rate, lines };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
NIGHTLY_CAP,
|
|
162
|
+
addCase,
|
|
163
|
+
listCases,
|
|
164
|
+
removeCase,
|
|
165
|
+
gradeCase,
|
|
166
|
+
runSuite,
|
|
167
|
+
nightlyRun,
|
|
168
|
+
loadHistory,
|
|
169
|
+
casesPath,
|
|
170
|
+
historyPath,
|
|
171
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// socket.io-client permanently stops reconnecting when the server REJECTS a
|
|
4
|
+
// handshake (auth middleware next(new Error())): connect_error fires with
|
|
5
|
+
// socket.active === false and the manager never dials again. During the
|
|
6
|
+
// 2026-08-08 outage chat-central booted before Central's MongoDB and rejected
|
|
7
|
+
// every handshake for a few minutes, terminally stranding the whole bot fleet.
|
|
8
|
+
// This wires a manual connect() retry with capped backoff so a rejected
|
|
9
|
+
// handshake is never terminal. A deliberate client-side disconnect() cancels
|
|
10
|
+
// any pending retry and stays final until connect() is called again.
|
|
11
|
+
|
|
12
|
+
const DEFAULT_INITIAL_DELAY_MS = 2000;
|
|
13
|
+
const DEFAULT_MAX_DELAY_MS = 30000;
|
|
14
|
+
|
|
15
|
+
function wireTerminalReconnect(socket, opts = {}) {
|
|
16
|
+
const label = opts.label || "socket";
|
|
17
|
+
const log = opts.log || ((msg) => console.error(msg));
|
|
18
|
+
const initialDelay = opts.initialDelayMs || DEFAULT_INITIAL_DELAY_MS;
|
|
19
|
+
const maxDelay = opts.maxDelayMs || DEFAULT_MAX_DELAY_MS;
|
|
20
|
+
|
|
21
|
+
let attempts = 0;
|
|
22
|
+
let timer = null;
|
|
23
|
+
let stopped = false;
|
|
24
|
+
|
|
25
|
+
const cancel = () => {
|
|
26
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const schedule = (why) => {
|
|
30
|
+
if (stopped || timer) return;
|
|
31
|
+
const delay = Math.min(initialDelay * 2 ** attempts, maxDelay);
|
|
32
|
+
attempts += 1;
|
|
33
|
+
log(`${label}: ${why} — socket.io will not retry on its own; manual connect() in ${Math.ceil(delay / 1000)}s (attempt ${attempts})`);
|
|
34
|
+
timer = setTimeout(() => {
|
|
35
|
+
timer = null;
|
|
36
|
+
if (stopped || socket.connected || socket.active) return;
|
|
37
|
+
try { socket.connect(); }
|
|
38
|
+
catch (e) { schedule(`reconnect attempt threw (${e.message})`); }
|
|
39
|
+
}, delay);
|
|
40
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
socket.on("connect", () => { attempts = 0; cancel(); });
|
|
44
|
+
socket.on("connect_error", (err) => {
|
|
45
|
+
if (socket.active) return; // transport failure: the manager auto-retries
|
|
46
|
+
schedule(`handshake rejected (${err && err.message ? err.message : err})`);
|
|
47
|
+
});
|
|
48
|
+
socket.on("disconnect", (reason) => {
|
|
49
|
+
if (reason === "io client disconnect") { cancel(); return; }
|
|
50
|
+
if (socket.active) return; // the manager auto-reconnects
|
|
51
|
+
schedule(`disconnected (${reason})`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// disconnect() while wedged emits no event, so intercept the call itself;
|
|
55
|
+
// connect() re-arms retries for the adapter-restart path.
|
|
56
|
+
const origDisconnect = socket.disconnect;
|
|
57
|
+
socket.disconnect = function (...args) {
|
|
58
|
+
stopped = true;
|
|
59
|
+
cancel();
|
|
60
|
+
return origDisconnect.apply(this, args);
|
|
61
|
+
};
|
|
62
|
+
const origConnect = socket.connect;
|
|
63
|
+
socket.connect = function (...args) {
|
|
64
|
+
stopped = false;
|
|
65
|
+
return origConnect.apply(this, args);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return socket;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { wireTerminalReconnect, DEFAULT_INITIAL_DELAY_MS, DEFAULT_MAX_DELAY_MS };
|
package/core/system-prompt.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { SOUL_FILE, CRONS_FILE, VAULT_FILE, FILES_DIR, BOT_DIR, config } = require("./config");
|
|
7
|
-
const { canSpeak } = require("./media");
|
|
7
|
+
const { canSpeak, canTranscribe } = require("./media");
|
|
8
8
|
const CONFIG_DIR = require("../config-dir");
|
|
9
9
|
const { currentState, getProjectKey, getProviderSession } = require("./state");
|
|
10
|
-
const { currentAdapter, currentChannelId, currentUserId } = require("./context");
|
|
10
|
+
const { currentAdapter, currentChannelId, currentUserId, currentSpeakerName } = require("./context");
|
|
11
11
|
const { vault } = require("./vault-store");
|
|
12
12
|
const {
|
|
13
13
|
transcriptPointerNoteForRun,
|
|
@@ -202,6 +202,7 @@ function buildCoreInstructionsUncached() {
|
|
|
202
202
|
// Gate on actual TTS capability, not local Whisper: hosted pods hear and
|
|
203
203
|
// speak through the speech gateway with no whisper binary installed.
|
|
204
204
|
const hasVoice = canSpeak();
|
|
205
|
+
const hasEars = canTranscribe();
|
|
205
206
|
return `
|
|
206
207
|
${soul}
|
|
207
208
|
${buildPersonaBlock()}
|
|
@@ -235,6 +236,13 @@ Reply normally in your final answer. To send a file, image, or voice clip back t
|
|
|
235
236
|
- \`open-claudia send-voice <path>\` — ogg/opus voice note
|
|
236
237
|
- \`open-claudia say "<text>"\` — synthesize the text and send it as a voice note (use this when asked for a voice reply)
|
|
237
238
|
Never print or embed bot tokens in prompts, commands, logs, or messages.
|
|
239
|
+
${hasVoice || hasEars ? `
|
|
240
|
+
## Speech services (TTS / STT)
|
|
241
|
+
Text-to-speech and speech-to-text are general capabilities, not just the voice-note reply path. Synthesis and transcription run inside the credentialed bot process — your own shell holds NO speech token or vendor key, so never call the speech gateway or TTS/STT vendor APIs directly and never conclude "the key is missing"; use these commands (they work from inside any bot-spawned task):
|
|
242
|
+
${hasVoice ? `- \`open-claudia tts "<text>" --out <path>.mp3\` — synthesize narration to a FILE without sending it to chat: video voice-overs, explainer narration, IVR prompts, any audio artifact. The extension picks the container (.ogg native, .mp3/.wav transcoded); prints the final path. \`--voice <id>\` selects a specific gateway voice (e.g. a cloned voice).
|
|
243
|
+
- \`open-claudia say "<text>"\` — synthesize AND send as a voice note to the current chat (listed under Delivery above).` : ""}
|
|
244
|
+
${hasEars ? `- \`open-claudia transcribe <file>\` — speech-to-text: prints the transcript of an audio OR video file (voice notes, meeting recordings, mp3/m4a/wav/ogg, mp4/mkv — the audio track is extracted automatically). Use it whenever the user sends a recording and wants its contents.` : ""}
|
|
245
|
+
For long narration, synthesize per-paragraph with \`tts\` and assemble with ffmpeg.` : ""}
|
|
238
246
|
|
|
239
247
|
## Background work
|
|
240
248
|
You can persist work across turns and wake yourself up later. These survive bot restarts.
|
|
@@ -369,6 +377,20 @@ function buildSpeakerContextBlock() {
|
|
|
369
377
|
const speaker = adapter && channelId
|
|
370
378
|
? people.findByHandle(adapter.type, String(currentUserId() || channelId))
|
|
371
379
|
: null;
|
|
380
|
+
// Without a people record the speaker used to render as "no named profile"
|
|
381
|
+
// even when identity was known: an env-configured owner (KAZEE_OWNER_USER_ID,
|
|
382
|
+
// never auto-contacted) or a not-yet-recorded group member. In group chats
|
|
383
|
+
// the model then rightly refused to attribute their messages. Fall back to
|
|
384
|
+
// the canonical owner resolution the guard stack uses, then to the
|
|
385
|
+
// transport-reported display name. Names never confer privileges (R1):
|
|
386
|
+
// owner standing comes only from the canonical check, and a bare transport
|
|
387
|
+
// name is labelled unverified.
|
|
388
|
+
let canonicalSpeaker = null;
|
|
389
|
+
if (!speaker && adapter && channelId) {
|
|
390
|
+
try { canonicalSpeaker = require("./relationship").speakerFor(adapter.type, String(channelId), currentUserId()); } catch (e) {}
|
|
391
|
+
}
|
|
392
|
+
let transportName = "";
|
|
393
|
+
try { transportName = String(currentSpeakerName() || "").trim(); } catch (e) {}
|
|
372
394
|
if (speaker) {
|
|
373
395
|
const recentNotes = (speaker.notes || [])
|
|
374
396
|
.slice(-3)
|
|
@@ -381,6 +403,23 @@ function buildSpeakerContextBlock() {
|
|
|
381
403
|
speaker.bio ? `Bio: ${speaker.bio}` : "",
|
|
382
404
|
recentNotes ? `Recent notes:\n${recentNotes}` : "",
|
|
383
405
|
);
|
|
406
|
+
} else if (canonicalSpeaker && canonicalSpeaker.isOwner) {
|
|
407
|
+
// Prefer the live transport name: canonicalSpeaker.name falls back to
|
|
408
|
+
// owners()[0], which can be a different owner on multi-owner pods.
|
|
409
|
+
const name = transportName || canonicalSpeaker.name;
|
|
410
|
+
blocks.push(
|
|
411
|
+
"",
|
|
412
|
+
"## Speaker",
|
|
413
|
+
name
|
|
414
|
+
? `You are talking to ${name} (the owner) on ${adapter.type}.`
|
|
415
|
+
: `You are talking to the owner on ${adapter.type}.`,
|
|
416
|
+
);
|
|
417
|
+
} else if (transportName) {
|
|
418
|
+
blocks.push(
|
|
419
|
+
"",
|
|
420
|
+
"## Speaker",
|
|
421
|
+
`The current message is from "${transportName}" on ${adapter.type} — transport-reported name, no saved profile; identity unverified.`,
|
|
422
|
+
);
|
|
384
423
|
} else {
|
|
385
424
|
blocks.push("", "## Speaker", "No named speaker profile is available for this turn.");
|
|
386
425
|
}
|
|
@@ -651,6 +690,22 @@ function formatPackForContext(pack, packsLib, opts = {}) {
|
|
|
651
690
|
return clip(parts.join("\n\n"), PACK_INJECT_MAX_CHARS);
|
|
652
691
|
}
|
|
653
692
|
|
|
693
|
+
// C1 prospective memory: topic-bound reminders ride the recall injection of
|
|
694
|
+
// their target pack/entity. Fetched at render time but CONSUMED only after
|
|
695
|
+
// the budget accepts the committed block — a dropped render must never eat a
|
|
696
|
+
// one-shot reminder. All calls are defensive: reminder machinery failing can
|
|
697
|
+
// never break recall injection itself.
|
|
698
|
+
function pendingRemindersFor(type, slug) {
|
|
699
|
+
try { return require("./prospective").pendingFor(type, slug); } catch (e) { return []; }
|
|
700
|
+
}
|
|
701
|
+
function renderReminders(entries) {
|
|
702
|
+
try { return require("./prospective").renderPendingLines(entries); } catch (e) { return ""; }
|
|
703
|
+
}
|
|
704
|
+
function fireReminders(entries) {
|
|
705
|
+
if (!entries || entries.length === 0) return;
|
|
706
|
+
try { require("./prospective").fire(entries, "recall"); } catch (e) {}
|
|
707
|
+
}
|
|
708
|
+
|
|
654
709
|
function buildPackBlock(matches, budget, opts = {}) {
|
|
655
710
|
try {
|
|
656
711
|
const packsLib = require("./packs");
|
|
@@ -671,17 +726,29 @@ function buildPackBlock(matches, budget, opts = {}) {
|
|
|
671
726
|
used.push(m.dir);
|
|
672
727
|
const key = `${adapter?.id || "?"}:${channelId || "?"}:${m.dir}`;
|
|
673
728
|
const stamp = `${runIdentity}:${pack.updated}`;
|
|
729
|
+
const pending = pendingRemindersFor("pack", m.dir);
|
|
730
|
+
const pendingText = pending.length ? renderReminders(pending) : "";
|
|
674
731
|
// Inject a pack's body once per (channel, session, version). A
|
|
675
732
|
// compaction mints a new session id, which changes the stamp and
|
|
676
733
|
// forces a fresh re-injection on the next turn (same mechanism
|
|
677
734
|
// as the task tree), so the pack survives compaction without paying
|
|
678
735
|
// to re-stamp an anchor on every intervening turn.
|
|
679
|
-
if (packsInjectedFor.get(key) === stamp)
|
|
680
|
-
|
|
736
|
+
if (packsInjectedFor.get(key) === stamp) {
|
|
737
|
+
// C1: a reminder doesn't bump pack.updated, so the dedupe stamp
|
|
738
|
+
// would silently swallow it — surface a tiny standalone block.
|
|
739
|
+
if (pendingText) {
|
|
740
|
+
const note = `### Pack: ${pack.name || m.dir} (${m.dir})\n${pendingText}`;
|
|
741
|
+
if (tryUseRecallBudget(budget, note)) { blocks.push(note); fireReminders(pending); }
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
let block = formatPackForContext(pack, packsLib, { progressive, headline, extractive, matchedLines: extractive ? (matchedByDir.get(m.dir) || []) : undefined });
|
|
746
|
+
if (pendingText) block += `\n\n${pendingText}`;
|
|
681
747
|
if (!tryUseRecallBudget(budget, block)) continue;
|
|
682
748
|
packsInjectedFor.set(key, stamp);
|
|
683
749
|
collector.packs.push(pack.name || m.dir);
|
|
684
750
|
blocks.push(block);
|
|
751
|
+
if (pendingText) fireReminders(pending);
|
|
685
752
|
}
|
|
686
753
|
if (used.length > 0) setImmediate(() => { try { packsLib.touchUsed(used); } catch (e) {} });
|
|
687
754
|
if (blocks.length === 0) return "";
|
|
@@ -738,12 +805,23 @@ function buildEntityBlock(matches, budget, opts = {}) {
|
|
|
738
805
|
seen.push(m.slug);
|
|
739
806
|
const key = `${adapter?.id || "?"}:${channelId || "?"}:${m.slug}`;
|
|
740
807
|
const stamp = `${runIdentity}:${ent.updated}`;
|
|
741
|
-
|
|
742
|
-
const
|
|
808
|
+
const pending = pendingRemindersFor("entity", m.slug);
|
|
809
|
+
const pendingText = pending.length ? renderReminders(pending) : "";
|
|
810
|
+
if (entitiesInjectedFor.get(key) === stamp) {
|
|
811
|
+
// C1: same as packs — a reminder must survive the dedupe stamp.
|
|
812
|
+
if (pendingText) {
|
|
813
|
+
const note = `### ${ent.name} (${m.slug})\n${pendingText}`;
|
|
814
|
+
if (tryUseRecallBudget(budget, note)) { blocks.push(note); fireReminders(pending); }
|
|
815
|
+
}
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
let block = formatEntityForContext(ent, { headline: recallHeadline(), slug: m.slug });
|
|
819
|
+
if (pendingText) block += `\n\n${pendingText}`;
|
|
743
820
|
if (!tryUseRecallBudget(budget, block)) continue;
|
|
744
821
|
entitiesInjectedFor.set(key, stamp);
|
|
745
822
|
collector.entities.push(ent.name || m.slug);
|
|
746
823
|
blocks.push(block);
|
|
824
|
+
if (pendingText) fireReminders(pending);
|
|
747
825
|
}
|
|
748
826
|
if (seen.length > 0) setImmediate(() => { try { entitiesLib.touchSeen(seen); } catch (e) {} });
|
|
749
827
|
if (blocks.length === 0) return "";
|
package/core/tools.js
CHANGED
|
@@ -168,6 +168,82 @@ function fingerprint(name) {
|
|
|
168
168
|
return { status: "changed", current, verified: s.verifiedHash, verifiedAt: s.verifiedAt || "" };
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// ── Skill certification (C3) ─────────────────────────────────────────────────
|
|
172
|
+
// Per-verb trust tier derived purely from the run telemetry above. Earned, not
|
|
173
|
+
// granted: "certified" needs ≥CERTIFY_CLEAN_RUNS clean runs with no failure in
|
|
174
|
+
// the probation window; any recent failure demotes to "probation"; everything
|
|
175
|
+
// else is "learning". Derived data + report-only proposals ONLY — certification
|
|
176
|
+
// never loosens a gate or grants anything by itself; the owner acts through the
|
|
177
|
+
// existing approval-card/guardrails flows or ignores it.
|
|
178
|
+
const CERTIFY_CLEAN_RUNS = 5;
|
|
179
|
+
const PROBATION_WINDOW_MS = 7 * 86400000;
|
|
180
|
+
|
|
181
|
+
// Mirrors recordRunResult's verb extraction so history lines re-attribute to
|
|
182
|
+
// the same verb key they were counted under.
|
|
183
|
+
function historyVerb(argLine) {
|
|
184
|
+
const first = String(argLine || "").split(/\s+/).find((a) => a && !a.startsWith("-")) || "(default)";
|
|
185
|
+
return first.slice(0, 40);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function cleanRunsSince(verb, history, sinceT) {
|
|
189
|
+
let n = 0;
|
|
190
|
+
for (const h of Array.isArray(history) ? history : []) {
|
|
191
|
+
const t = Date.parse(h.at || "");
|
|
192
|
+
if (!Number.isFinite(t) || t <= sinceT) continue;
|
|
193
|
+
if (h.exit !== 0) continue;
|
|
194
|
+
if (historyVerb(h.args) === verb) n += 1;
|
|
195
|
+
}
|
|
196
|
+
return n;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function verbCertification(verb, v = {}, history = [], now = Date.now()) {
|
|
200
|
+
const runs = Number(v.runs) || 0;
|
|
201
|
+
const lastFailureT = Date.parse(v.lastFailure || "");
|
|
202
|
+
const failed = Number.isFinite(lastFailureT);
|
|
203
|
+
if (failed && now - lastFailureT < PROBATION_WINDOW_MS) {
|
|
204
|
+
return { tier: "probation", cleanRuns: cleanRunsSince(verb, history, lastFailureT), runs };
|
|
205
|
+
}
|
|
206
|
+
// No failure ever → every recorded run was clean. After an old failure only
|
|
207
|
+
// successes SINCE it count toward re-certification (history-capped, so this
|
|
208
|
+
// is an honest lower bound).
|
|
209
|
+
const cleanRuns = failed ? cleanRunsSince(verb, history, lastFailureT) : runs;
|
|
210
|
+
return { tier: cleanRuns >= CERTIFY_CLEAN_RUNS ? "certified" : "learning", cleanRuns, runs };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function certificationForState(s = {}, now = Date.now()) {
|
|
214
|
+
const out = {};
|
|
215
|
+
for (const [verb, v] of Object.entries(s.verbs || {})) {
|
|
216
|
+
out[verb] = verbCertification(verb, v, s.history, now);
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function certification(name, now = Date.now()) {
|
|
222
|
+
return certificationForState(readState(name), now);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Dream phase (report-only, max 2/night): certified verbs on gated tiers are
|
|
226
|
+
// candidates for a standing Always-allow grant. Read-only verbs run freely and
|
|
227
|
+
// need no grant; probation/learning verbs haven't earned a proposal. The line
|
|
228
|
+
// is the ENTIRE output — no grant is created here, ever.
|
|
229
|
+
function certificationProposals({ now = Date.now(), max = 2 } = {}) {
|
|
230
|
+
const candidates = [];
|
|
231
|
+
for (const t of listTools()) {
|
|
232
|
+
if (!t.verbs || !t.manifest || !t.manifest.length) continue;
|
|
233
|
+
const certs = certificationForState({ verbs: t.verbs, history: t.history }, now);
|
|
234
|
+
for (const m of t.manifest) {
|
|
235
|
+
const tier = m.tier || t.risk || "write";
|
|
236
|
+
if (tier === "read-only") continue;
|
|
237
|
+
const c = certs[m.name];
|
|
238
|
+
if (!c || c.tier !== "certified") continue;
|
|
239
|
+
candidates.push({ tool: t.name, verb: m.name, tier, cleanRuns: c.cleanRuns });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
candidates.sort((a, b) => b.cleanRuns - a.cleanRuns);
|
|
243
|
+
return candidates.slice(0, max).map((c) =>
|
|
244
|
+
`🎓 ${c.tool} ${c.verb} is certified (${c.cleanRuns} clean ${c.tier} runs) — candidate for a standing Always-allow grant if you want it; grant via the approval card / guardrails, or ignore.`);
|
|
245
|
+
}
|
|
246
|
+
|
|
171
247
|
// Bump a tool's run counter without outcome detail. Still called at end of turn
|
|
172
248
|
// from the runner's transcript parse; for directory tools that's a no-op (the
|
|
173
249
|
// CLI already stamped the authoritative result — double-counting would lie),
|
|
@@ -1018,6 +1094,7 @@ module.exports = {
|
|
|
1018
1094
|
runEnv, missingRequires, toolNameFromPath, sanitizeName, ensureDir, normalizeRisk,
|
|
1019
1095
|
toolPaths, readState, writeState, recordRunResult, runGate, approvedTierFromArgv,
|
|
1020
1096
|
resolveTier, invokedVerb, sourceHash, fingerprint,
|
|
1097
|
+
CERTIFY_CLEAN_RUNS, PROBATION_WINDOW_MS, verbCertification, certificationForState, certification, certificationProposals,
|
|
1021
1098
|
listLib, readLib, writeLib, libDependents,
|
|
1022
1099
|
matchTools, recordRun, recordCreate, readUsage,
|
|
1023
1100
|
lintToolSource, checkSyntax, envRefsIn, assertNoSecrets,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
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-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-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-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-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"
|
|
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-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-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"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -487,8 +487,12 @@ async function main() {
|
|
|
487
487
|
throw new Error("FAKE_AGENT_KIND must be claude or codex");
|
|
488
488
|
}
|
|
489
489
|
if (handleCompatibilityCommand()) return;
|
|
490
|
-
|
|
490
|
+
// Arm signal handlers BEFORE writing the boot capture: the capture is the
|
|
491
|
+
// parent's green light to send SIGTERM, and on a loaded machine the child
|
|
492
|
+
// can be preempted between these calls — a TERM then hits node's default
|
|
493
|
+
// disposition and kills the process before the signal capture ever writes.
|
|
491
494
|
installSignalControls();
|
|
495
|
+
captureInvocation();
|
|
492
496
|
spawnDescendantFixture();
|
|
493
497
|
const stdin = await readStdin();
|
|
494
498
|
captureStdin(stdin);
|
|
@@ -11,7 +11,7 @@ class FakeSession {
|
|
|
11
11
|
}
|
|
12
12
|
function fixture() {
|
|
13
13
|
const events = [], inbound = [];
|
|
14
|
-
const socket = { on() {}, emit(name, payload) { events.push({ name, payload }); } };
|
|
14
|
+
const socket = { connected: true, on() {}, emit(name, payload) { events.push({ name, payload }); } };
|
|
15
15
|
const adapter = { ownerUserId: "owner-1", botUserId: "bot-1", _socket: socket, _emit(name, payload) { inbound.push({ name, payload }); } };
|
|
16
16
|
return { events, inbound, manager: new KazeeCallManager({ adapter, SessionImpl: FakeSession }) };
|
|
17
17
|
}
|
|
@@ -36,5 +36,48 @@ function fixture() {
|
|
|
36
36
|
assert.strictEqual(f.manager.session.spoken, "Your agenda is clear.");
|
|
37
37
|
await f.manager.ended({ roomId: 12 });
|
|
38
38
|
assert.strictEqual(f.manager.session, null);
|
|
39
|
+
|
|
40
|
+
// Outbound: session starts in caller mode, invite goes out, answer clears the ring timer.
|
|
41
|
+
f = fixture();
|
|
42
|
+
const res = await f.manager.outgoing({ chatId: "chat-2" });
|
|
43
|
+
assert.strictEqual(res.toUserId, "owner-1");
|
|
44
|
+
const outSession = f.manager.session;
|
|
45
|
+
assert.strictEqual(outSession.options.asCaller, true);
|
|
46
|
+
assert.ok(outSession.options.greeting);
|
|
47
|
+
assert.strictEqual(outSession.started, true);
|
|
48
|
+
const invite = f.events.find((e) => e.name === "call:invite");
|
|
49
|
+
assert.deepStrictEqual(invite.payload, { toUserId: "owner-1", callType: "audio", chatId: "chat-2", roomId: res.roomId });
|
|
50
|
+
assert.ok(f.manager._ringTimer);
|
|
51
|
+
// A callee device auto-ending its ring must NOT tear down the outbound call —
|
|
52
|
+
// chat-central revives the CallLog if the human answers late.
|
|
53
|
+
await f.manager.ended({ chatId: "chat-2" });
|
|
54
|
+
assert.ok(f.manager.session, "ended-while-ringing is ignored");
|
|
55
|
+
f.manager.accepted({ chatId: "chat-2" });
|
|
56
|
+
assert.strictEqual(f.manager._ringTimer, null);
|
|
57
|
+
// Outbound transcripts dispatch with the remote human as the "from" side.
|
|
58
|
+
outSession.options.onTranscript("Hello there");
|
|
59
|
+
assert.strictEqual(f.inbound[0].payload.channelId, "chat-2");
|
|
60
|
+
assert.strictEqual(f.inbound[0].payload.userId, "owner-1");
|
|
61
|
+
assert.strictEqual(f.inbound[0].payload.raw.ephemeralAudio, true);
|
|
62
|
+
await assert.rejects(() => f.manager.outgoing({ chatId: "chat-2" }), /busy/);
|
|
63
|
+
await f.manager.ended({ roomId: res.roomId });
|
|
64
|
+
assert.strictEqual(f.manager.session, null);
|
|
65
|
+
|
|
66
|
+
// A deliberate decline (call:rejected) still tears down mid-ring.
|
|
67
|
+
f = fixture();
|
|
68
|
+
await f.manager.outgoing({ chatId: "chat-4" });
|
|
69
|
+
assert.ok(f.manager._ringTimer);
|
|
70
|
+
await f.manager.rejected({ chatId: "chat-4" });
|
|
71
|
+
assert.strictEqual(f.manager.session, null, "reject-while-ringing closes the call");
|
|
72
|
+
assert.strictEqual(f.manager._ringTimer, null);
|
|
73
|
+
|
|
74
|
+
// Unanswered: ring timeout tells the server to finalize as missed and hangs up.
|
|
75
|
+
f = fixture();
|
|
76
|
+
const res2 = await f.manager.outgoing({ chatId: "chat-3", toUserId: "owner-1" });
|
|
77
|
+
f.manager._clearRingTimer();
|
|
78
|
+
await f.manager._ringTimeout("owner-1", "chat-3", res2.roomId);
|
|
79
|
+
const end = f.events.find((e) => e.name === "call:end");
|
|
80
|
+
assert.deepStrictEqual(end.payload, { toUserId: "owner-1", chatId: "chat-3" });
|
|
81
|
+
assert.strictEqual(f.manager.session, null);
|
|
39
82
|
console.log("kazee call manager: ok");
|
|
40
83
|
})().catch((error) => { console.error(error); process.exitCode = 1; });
|