@inetafrica/open-claudia 3.2.1 → 3.3.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 +27 -0
- package/bin/cli.js +1 -1
- package/bin/loopback-client.js +4 -0
- package/bin/schedule.js +24 -4
- package/bot.js +10 -1
- package/channels/kazee/call/audio.js +79 -0
- package/channels/kazee/call/manager.js +6 -0
- package/channels/kazee/call/session.js +359 -48
- package/channels/kazee/call/speech-client.js +38 -3
- package/core/actions.js +80 -0
- package/core/enforcer.js +22 -1
- package/core/handlers.js +52 -4
- package/core/loopback.js +41 -0
- package/core/media.js +71 -18
- package/core/pack-review.js +18 -0
- package/core/providers/utility-policy.js +4 -1
- package/core/relationship.js +18 -2
- package/core/router.js +28 -4
- package/core/runner.js +21 -4
- package/core/scheduler.js +60 -21
- package/core/speaker-attest.js +56 -0
- package/core/system-prompt.js +22 -4
- package/core/verify-contact.js +156 -0
- package/package.json +2 -2
- package/test-kazee-call-manager.js +4 -1
- package/test-kazee-call-session.js +100 -4
- package/test-provider-language.js +29 -1
- package/test-provider-scheduler.js +80 -0
- package/test-utility-provider-policy.js +22 -0
package/core/runner.js
CHANGED
|
@@ -1707,7 +1707,13 @@ function decorateForegroundInvocation(invocation, runContext, bundle) {
|
|
|
1707
1707
|
if (runContext.origin?.adapterId) env.OC_CHANNEL_ADAPTER = runContext.origin.adapterId;
|
|
1708
1708
|
if (runContext.channelId) env.OC_CHANNEL_ID = String(runContext.channelId);
|
|
1709
1709
|
if (runContext.origin?.userId) env.OC_CHANNEL_USER_ID = String(runContext.origin.userId);
|
|
1710
|
-
if (runContext.canonicalUserId)
|
|
1710
|
+
if (runContext.canonicalUserId) {
|
|
1711
|
+
env.OC_CANONICAL_USER_ID = String(runContext.canonicalUserId);
|
|
1712
|
+
// Server-side attestation of the id above: the loopback's owner-gated
|
|
1713
|
+
// kinds only honor a canonicalUserId whose attest verifies, so a shell
|
|
1714
|
+
// that rewrites OC_CANONICAL_USER_ID cannot impersonate the owner.
|
|
1715
|
+
try { env.OC_SPEAKER_ATTEST = require("./speaker-attest").mint(runContext.canonicalUserId); } catch (e) {}
|
|
1716
|
+
}
|
|
1711
1717
|
if (runContext.sessionId) env.OC_LAST_SESSION_ID = String(runContext.sessionId);
|
|
1712
1718
|
if (lb?.url) env.OC_SEND_URL = lb.url;
|
|
1713
1719
|
}
|
|
@@ -1725,17 +1731,24 @@ function decorateForegroundInvocation(invocation, runContext, bundle) {
|
|
|
1725
1731
|
return { ...invocation, env };
|
|
1726
1732
|
}
|
|
1727
1733
|
|
|
1734
|
+
// Singleton per chat state: a new start supersedes any running heartbeat
|
|
1735
|
+
// (pre-turn "thinking" started at message receipt hands off to the run's own
|
|
1736
|
+
// heartbeat here). A stopper only acts while its own interval is current, so
|
|
1737
|
+
// a stale stopper from a superseded start can never kill a newer heartbeat.
|
|
1728
1738
|
function startTypingHeartbeat(state, store) {
|
|
1729
1739
|
const adapter = store?.adapter || currentAdapter();
|
|
1730
1740
|
const channelId = store?.channelId || currentChannelId();
|
|
1731
1741
|
if (!adapter || !channelId) return () => {};
|
|
1742
|
+
if (state.typingHeartbeat) clearInterval(state.typingHeartbeat);
|
|
1732
1743
|
const tick = () => adapter.typing(channelId, state.thinkingPhase ? "thinking" : undefined).catch(() => {});
|
|
1733
1744
|
state.thinkingPhase = true;
|
|
1734
1745
|
tick();
|
|
1735
|
-
|
|
1746
|
+
const interval = setInterval(tick, 4000);
|
|
1747
|
+
state.typingHeartbeat = interval;
|
|
1736
1748
|
return () => {
|
|
1749
|
+
if (state.typingHeartbeat !== interval) return;
|
|
1737
1750
|
state.thinkingPhase = false;
|
|
1738
|
-
|
|
1751
|
+
clearInterval(interval);
|
|
1739
1752
|
state.typingHeartbeat = null;
|
|
1740
1753
|
try { adapter.typingStop?.(channelId); } catch (_) {}
|
|
1741
1754
|
};
|
|
@@ -1880,7 +1893,10 @@ function createForegroundService({ state, store, stopTyping, capture, ownsAdmiss
|
|
|
1880
1893
|
observer.onRecallSurfaced(bundle.recallMetadata);
|
|
1881
1894
|
},
|
|
1882
1895
|
onEvent(event, runContext) {
|
|
1883
|
-
|
|
1896
|
+
// Streaming text reads as "typing"; tool work re-enters "thinking" so
|
|
1897
|
+
// the chip doesn't decay to a stale "typing" through long tool stretches.
|
|
1898
|
+
if (["text_delta", "text_final", "result", "error"].includes(event.type)) state.thinkingPhase = false;
|
|
1899
|
+
else if (event.type === "tool_start") state.thinkingPhase = true;
|
|
1884
1900
|
if (event.type === "tool_start" && ["Shell", "Bash"].includes(event.name)) {
|
|
1885
1901
|
const command = typeof event.detail === "string" ? event.detail : event.detail?.command;
|
|
1886
1902
|
if (command) { try { require("./tool-guard").noteRawOp(command); } catch (_) {} }
|
|
@@ -2248,6 +2264,7 @@ module.exports = {
|
|
|
2248
2264
|
runAgent,
|
|
2249
2265
|
runAgentCapture,
|
|
2250
2266
|
runAgentSilent,
|
|
2267
|
+
startTypingHeartbeat,
|
|
2251
2268
|
shouldRetryRefusedRun,
|
|
2252
2269
|
quarantineRefusedRun,
|
|
2253
2270
|
refusalRetryContext,
|
package/core/scheduler.js
CHANGED
|
@@ -43,6 +43,10 @@ const RETRY_DELAY_MS = 30 * 1000;
|
|
|
43
43
|
const BUSY_DELAY_MS = 30 * 1000;
|
|
44
44
|
// A watch's `check` command is capped so a hung poll can't wedge the tick.
|
|
45
45
|
const WATCH_CHECK_TIMEOUT_MS = 30 * 1000;
|
|
46
|
+
// Every watch carries a hard expiry so none can idle-poll forever: when the
|
|
47
|
+
// deadline lapses with the check still failing, the next poll tick fires the
|
|
48
|
+
// wake anyway (marked expired) instead of skipping silently.
|
|
49
|
+
const DEFAULT_WATCH_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
46
50
|
const WATCH_LOG_FILE = process.env.OPEN_CLAUDIA_WATCH_LOG_FILE || path.join(CONFIG_DIR, "watch.log");
|
|
47
51
|
const WATCHES_ENABLED_FILE = process.env.OPEN_CLAUDIA_WATCHES_ENABLED_FILE || path.join(CONFIG_DIR, "watches-enabled");
|
|
48
52
|
|
|
@@ -107,6 +111,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
107
111
|
const retryDelayMs = Number.isFinite(dependencies.retryDelayMs) ? dependencies.retryDelayMs : RETRY_DELAY_MS;
|
|
108
112
|
const busyDelayMs = Number.isFinite(dependencies.busyDelayMs) ? dependencies.busyDelayMs : BUSY_DELAY_MS;
|
|
109
113
|
const missedGraceMs = Number.isFinite(dependencies.missedGraceMs) ? dependencies.missedGraceMs : MISSED_WAKEUP_GRACE_MS;
|
|
114
|
+
const watchTimeoutMs = Number.isFinite(dependencies.watchTimeoutMs) ? dependencies.watchTimeoutMs : DEFAULT_WATCH_TIMEOUT_MS;
|
|
110
115
|
const occurrenceIdFactory = dependencies.occurrenceIdFactory || (() => uniqueId("occ"));
|
|
111
116
|
const legacyCronsFile = dependencies.legacyCronsFile === undefined
|
|
112
117
|
? (dependencies.jobsStore ? null : CRONS_FILE)
|
|
@@ -273,22 +278,21 @@ function createSchedulerService(dependencies = {}) {
|
|
|
273
278
|
|
|
274
279
|
function wrappedPrompt(job, target) {
|
|
275
280
|
const firedAt = new Date(now()).toISOString();
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
const expired = !!(job.check && job.watchExpired);
|
|
282
|
+
const header = job.check
|
|
283
|
+
? (expired ? "Watch expired" : "Watch triggered")
|
|
284
|
+
: job.kind === "cron" ? "Scheduled cron" : "Scheduled wakeup";
|
|
285
|
+
const lines = [`[${header} "${job.label}" fired at ${firedAt}; occurrence ${job.occurrenceId}]`];
|
|
286
|
+
if (expired) {
|
|
287
|
+
lines.push("[This watch reached its timeout without its check ever passing — the watched condition did NOT happen. This is a deadline wake-up, not a trigger: investigate why the condition never became true, and arm a new watch if it is still worth waiting for.]");
|
|
283
288
|
}
|
|
284
|
-
if (target.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
job.prompt,
|
|
289
|
-
].join("\n");
|
|
289
|
+
if (target.fallbackFrom) {
|
|
290
|
+
lines.push(`[Archived provider-neutral brief: the originating ${target.fallbackFrom} provider is unavailable. Its native session is not being resumed; this explicitly configured fallback starts fresh.]`);
|
|
291
|
+
} else if (target.switchedFrom) {
|
|
292
|
+
lines.push(`[Provider now tracks your current selection (${target.provider}); this was scheduled under ${target.switchedFrom}, whose native session cannot cross providers, so this run starts a fresh thread.]`);
|
|
290
293
|
}
|
|
291
|
-
|
|
294
|
+
lines.push(job.prompt);
|
|
295
|
+
return lines.join("\n");
|
|
292
296
|
}
|
|
293
297
|
|
|
294
298
|
function unscheduleJob(id) {
|
|
@@ -394,7 +398,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
394
398
|
|
|
395
399
|
async function fireJob(idOrJob, options = {}) {
|
|
396
400
|
const requested = typeof idOrJob === "string" ? jobsStore.get(idOrJob) : idOrJob;
|
|
397
|
-
|
|
401
|
+
let job = requested?.id && jobsStore.get(requested.id) ? jobsStore.get(requested.id) : requested;
|
|
398
402
|
if (!job) return { ok: false, status: "missing" };
|
|
399
403
|
if (job.disabled || job.status === "dead-letter" || job.status === "archived") {
|
|
400
404
|
return { ok: false, status: job.status, skipped: true };
|
|
@@ -424,10 +428,35 @@ function createSchedulerService(dependencies = {}) {
|
|
|
424
428
|
catch (_) { check = { code: 1, timedOut: false }; }
|
|
425
429
|
finally { checksInFlight.delete(job.id); }
|
|
426
430
|
if (!check || check.code !== 0) {
|
|
427
|
-
|
|
428
|
-
|
|
431
|
+
// Expiry backstop: a watch whose deadline has lapsed fires the wake
|
|
432
|
+
// anyway (marked expired) so no watch can ever end silently. Watches
|
|
433
|
+
// persisted before expiry existed (no expiresAt) keep polling forever.
|
|
434
|
+
const expiresAt = Number(job.expiresAt || 0);
|
|
435
|
+
if (!(expiresAt > 0 && now() >= expiresAt)) {
|
|
436
|
+
logWatch(job, check && check.timedOut ? "idle (check timed out)" : `idle (check exit ${check ? check.code : "?"})`);
|
|
437
|
+
return { ok: false, status: "watch-idle", skipped: true };
|
|
438
|
+
}
|
|
439
|
+
logWatch(job, `expired without firing (check exit ${check ? check.code : "?"}) — waking agent`);
|
|
440
|
+
// Expiry ends the watch's window: one-shot removal even for --repeat
|
|
441
|
+
// (the woken agent re-arms if still wanted). Persisted so a retry of a
|
|
442
|
+
// failed expiry fire keeps the same semantics across restarts.
|
|
443
|
+
try {
|
|
444
|
+
const marked = jobsStore.update(job.id, { repeat: false, watchExpired: true });
|
|
445
|
+
if (marked) job = marked;
|
|
446
|
+
else { job.repeat = false; job.watchExpired = true; }
|
|
447
|
+
} catch (_) { job.repeat = false; job.watchExpired = true; }
|
|
448
|
+
} else {
|
|
449
|
+
logWatch(job, "triggered (check passed) — waking agent");
|
|
450
|
+
if (job.watchExpired) {
|
|
451
|
+
// A prior expiry fire failed and the condition came true before the
|
|
452
|
+
// retry: report it as a real trigger, not an expiry.
|
|
453
|
+
try {
|
|
454
|
+
const cleared = jobsStore.update(job.id, { watchExpired: false });
|
|
455
|
+
if (cleared) job = cleared;
|
|
456
|
+
else job.watchExpired = false;
|
|
457
|
+
} catch (_) { job.watchExpired = false; }
|
|
458
|
+
}
|
|
429
459
|
}
|
|
430
|
-
logWatch(job, "triggered (check passed) — waking agent");
|
|
431
460
|
}
|
|
432
461
|
|
|
433
462
|
const adapter = resolveAdapter(job);
|
|
@@ -504,7 +533,10 @@ function createSchedulerService(dependencies = {}) {
|
|
|
504
533
|
return { ok: false, status: "failed", error: stableFailure(error) };
|
|
505
534
|
}
|
|
506
535
|
|
|
507
|
-
|
|
536
|
+
const advisory = job.check
|
|
537
|
+
? (job.watchExpired ? `⏰ Watch "${job.label}" timed out without triggering — waking anyway` : `🔔 Watch "${job.label}" triggered`)
|
|
538
|
+
: `Wakeup: ${job.label}`;
|
|
539
|
+
try { await send(advisory); } catch (_) { /* advisory only */ }
|
|
508
540
|
let result;
|
|
509
541
|
try { result = await runInProvider(prompt, runContext.cwd, null, { ...runOptions, runContext }); }
|
|
510
542
|
catch (error) {
|
|
@@ -655,15 +687,21 @@ function createSchedulerService(dependencies = {}) {
|
|
|
655
687
|
}
|
|
656
688
|
|
|
657
689
|
// A watch is a cron carrying a shell `check`; it rides the migration-verified
|
|
658
|
-
// jobs store untouched (check/repeat survive normalizeRunnableJob
|
|
659
|
-
// fields). Default schedule polls every minute; default is one-shot.
|
|
690
|
+
// jobs store untouched (check/repeat/expiresAt survive normalizeRunnableJob
|
|
691
|
+
// as extra fields). Default schedule polls every minute; default is one-shot.
|
|
692
|
+
// Every new watch gets an expiry (caller's expiresAt or the default timeout)
|
|
693
|
+
// so it can never idle-poll silently forever.
|
|
660
694
|
function addWatch(spec) {
|
|
695
|
+
const requestedExpiry = Number(spec.expiresAt);
|
|
661
696
|
return addJob({
|
|
662
697
|
...spec,
|
|
663
698
|
kind: "cron",
|
|
664
699
|
schedule: spec.schedule || "* * * * *",
|
|
665
700
|
check: String(spec.check || "").trim(),
|
|
666
701
|
repeat: !!spec.repeat,
|
|
702
|
+
expiresAt: Number.isFinite(requestedExpiry) && requestedExpiry > 0
|
|
703
|
+
? requestedExpiry
|
|
704
|
+
: now() + watchTimeoutMs,
|
|
667
705
|
label: spec.label || String(spec.prompt || "watch").slice(0, 60),
|
|
668
706
|
});
|
|
669
707
|
}
|
|
@@ -710,6 +748,7 @@ module.exports = {
|
|
|
710
748
|
MISSED_WAKEUP_GRACE_MS,
|
|
711
749
|
RETRY_DELAY_MS,
|
|
712
750
|
BUSY_DELAY_MS,
|
|
751
|
+
DEFAULT_WATCH_TIMEOUT_MS,
|
|
713
752
|
createSchedulerService,
|
|
714
753
|
parseFallbacks,
|
|
715
754
|
terminalRunSucceeded,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Speaker attestation: lets the loopback server trust OC_CANONICAL_USER_ID.
|
|
2
|
+
//
|
|
3
|
+
// The problem: the loopback's owner-gated kinds (people mutations, auth,
|
|
4
|
+
// intros) identified the caller by payload.canonicalUserId, which the CLI read
|
|
5
|
+
// from env — and env is writable by the agent's shell, so a guarded external
|
|
6
|
+
// turn could forge `OC_CANONICAL_USER_ID=<owner-id> open-claudia people link …`
|
|
7
|
+
// and rewrite the identity map the relationship guard depends on.
|
|
8
|
+
//
|
|
9
|
+
// The fix: the RUNNER (which knows the turn's true speaker from the inbound
|
|
10
|
+
// envelope, server-side) mints an HMAC over the canonical user id with a
|
|
11
|
+
// per-boot secret that lives ONLY in bot-process memory — never on disk, never
|
|
12
|
+
// in the loopback info file, stripped from provider env like all control-plane
|
|
13
|
+
// keys would be (it is simply never exported). The provider spawn env carries
|
|
14
|
+
// OC_SPEAKER_ATTEST next to OC_CANONICAL_USER_ID; the CLI forwards both; the
|
|
15
|
+
// loopback verifies the pair before honoring an owner claim. An agent can
|
|
16
|
+
// still LIE about OC_CANONICAL_USER_ID, but it cannot mint a matching
|
|
17
|
+
// signature for any id other than the one the runner stamped for its turn.
|
|
18
|
+
//
|
|
19
|
+
// Scope and honesty: this authenticates "the runner said this turn speaks as
|
|
20
|
+
// X". It is per-boot (a restart invalidates in-flight attests — same blast
|
|
21
|
+
// radius as the loopback port/token rotating) and deliberately unforgeable
|
|
22
|
+
// rather than merely obscure: no attest, wrong attest, or an attest for a
|
|
23
|
+
// different id all fail closed. It does NOT bind adapter/channel — those stay
|
|
24
|
+
// best-effort inputs to the guarded-speaker checks, whose spoof failure modes
|
|
25
|
+
// are self-limiting (buttons/files land on the spoofed channel, i.e. the
|
|
26
|
+
// owner's own chat, never silently on the attacker's).
|
|
27
|
+
|
|
28
|
+
const crypto = require("crypto");
|
|
29
|
+
|
|
30
|
+
// Lazily created so tests get a fresh secret per process, like production.
|
|
31
|
+
let SECRET = null;
|
|
32
|
+
|
|
33
|
+
function secret() {
|
|
34
|
+
if (!SECRET) SECRET = crypto.randomBytes(32);
|
|
35
|
+
return SECRET;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function mint(canonicalUserId) {
|
|
39
|
+
const id = String(canonicalUserId || "");
|
|
40
|
+
if (!id) return "";
|
|
41
|
+
return crypto.createHmac("sha256", secret()).update(id).digest("hex");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Constant-time compare; every malformed shape is a plain false (fail closed).
|
|
45
|
+
function verify(canonicalUserId, attest) {
|
|
46
|
+
const id = String(canonicalUserId || "");
|
|
47
|
+
const sig = String(attest || "");
|
|
48
|
+
if (!id || !sig) return false;
|
|
49
|
+
const expected = mint(id);
|
|
50
|
+
const a = Buffer.from(sig, "utf-8");
|
|
51
|
+
const b = Buffer.from(expected, "utf-8");
|
|
52
|
+
if (a.length !== b.length) return false;
|
|
53
|
+
try { return crypto.timingSafeEqual(a, b); } catch (e) { return false; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { mint, verify };
|
package/core/system-prompt.js
CHANGED
|
@@ -255,6 +255,7 @@ Wake-ups and crons (real schedulers, not hallucinations — use them instead of
|
|
|
255
255
|
|
|
256
256
|
Watches (a silent background monitor — poll a condition WITHOUT waking every tick; wake only when it actually happens). Use this instead of a minute-by-minute cron whenever you'd otherwise fire a turn just to check "is it done yet?" and stay quiet most times:
|
|
257
257
|
- \`open-claudia watch-add "<check-cmd>" "<wake-prompt>"\` — the scheduler polls \`<check-cmd>\` as a shell command (default every minute). The poll is SILENT: nothing reaches the chat while the check keeps failing. The instant \`<check-cmd>\` exits 0, you wake in this same conversation with \`<wake-prompt>\`, exactly like a wakeup. Non-zero exit = still waiting = nothing happens. Default is ONE-SHOT (fires once, then removes itself); add \`--repeat\` to keep polling after each hit. \`--every "<5-field cron>"\` changes the poll cadence.
|
|
258
|
+
- Every watch has a hard timeout — \`--timeout <duration|ISO>\` (default 2h). A watch that reaches its timeout without the check ever passing FIRES anyway on the next poll, with the prompt marked "Watch expired" so you know the condition did NOT happen. No watch can end silently, so you no longer need a separate backup wakeup for every watch — but size \`--timeout\` honestly to the work being watched.
|
|
258
259
|
- \`open-claudia watch-list\` / \`open-claudia watch-remove <id>\` — inspect/cancel watches on this channel.
|
|
259
260
|
- The owner has a global master switch: \`/watch off\` pauses ALL watch polling (and \`/watch on\` resumes). If \`watch-list\` shows the switch OFF, watches won't fire until it's back on.
|
|
260
261
|
- Example: after kicking off a long external job, \`open-claudia watch-add "test -f /tmp/build-done || exit 1" "the build finished — verify and report"\` beats a wakeup guess, because it fires the moment it's actually done, not on a fixed timer. (Never use a watch to poll harness-tracked work you started — that notifies you automatically. Watches are for EXTERNAL state the harness can't see.)
|
|
@@ -864,20 +865,29 @@ function buildSpeakerPersonaBlock(opts = {}) {
|
|
|
864
865
|
if (!speaker.isOwner && (s.Mandate || "").trim()) {
|
|
865
866
|
rows.push(`Mandate — what you may do/discuss with them: ${s.Mandate.trim()}`);
|
|
866
867
|
}
|
|
867
|
-
|
|
868
|
+
// A bare note used to render nothing — which hid the guard's policy source
|
|
869
|
+
// exactly when it mattered most (auto-added stubs are born bare, so the
|
|
870
|
+
// agent couldn't see WHICH file governs the speaker, or that a same-named
|
|
871
|
+
// note elsewhere is a different identity). For non-owners the block always
|
|
872
|
+
// renders; an owner with an empty note still gets the quiet no-op.
|
|
873
|
+
if (rows.length === 0 && speaker.isOwner) return "";
|
|
868
874
|
const runIdentity = promptDedupeIdentity(opts);
|
|
869
875
|
const key = `${adapter.id || "?"}:${channelId}`;
|
|
870
876
|
const stamp = `${runIdentity}:${slug}:${ent.updated}`;
|
|
871
877
|
if (speakerPersonaInjectedFor.get(key) === stamp) return "";
|
|
872
878
|
speakerPersonaInjectedFor.set(key, stamp);
|
|
873
879
|
const rel = ent.relationship || (speaker.isOwner ? "owner" : "");
|
|
880
|
+
const unverified = !speaker.isOwner && !!speaker.autoCreated;
|
|
874
881
|
const clip = (str) => (str.length > SPEAKER_PERSONA_MAX_CHARS ? str.slice(0, SPEAKER_PERSONA_MAX_CHARS) + "\n…[truncated — `open-claudia entity show " + slug + "`]" : str);
|
|
875
882
|
return clip([
|
|
876
|
-
`\n\n## Who you're speaking with: ${speaker.name}${rel ? ` (${rel})` : ""}`,
|
|
883
|
+
`\n\n## Who you're speaking with: ${speaker.name}${rel ? ` (${rel})` : ""}${unverified ? " — UNVERIFIED auto-added contact" : ""}`,
|
|
877
884
|
`Their persona pack (source: ${entitiesLib.ENTITIES_DIR}/${slug}.md) — adapt your voice and scope to it. Edit that file to refine how you treat them.`,
|
|
885
|
+
...(speaker.isOwner ? [] : [
|
|
886
|
+
`That file is the ONLY note the relationship guard reads for them — a same-named note anywhere else belongs to a DIFFERENT identity and grants nothing here.${unverified ? " The owner has not yet confirmed who they are (verification happens on the owner's approval card or /people verify)." : ""}`,
|
|
887
|
+
]),
|
|
878
888
|
"",
|
|
879
889
|
rows.join("\n"),
|
|
880
|
-
].join("\n"));
|
|
890
|
+
].join("\n")).trimEnd();
|
|
881
891
|
}
|
|
882
892
|
|
|
883
893
|
// External-mode posture block (Phase 3). For a non-owner (guarded) speaker,
|
|
@@ -895,9 +905,17 @@ function buildExternalModeBlock() {
|
|
|
895
905
|
const who = speaker.name || "this person";
|
|
896
906
|
const mandate = (speaker.mandate || "").trim();
|
|
897
907
|
const unverified = !!(speaker.person && speaker.person.autoCreated);
|
|
908
|
+
let sourceLine = "";
|
|
909
|
+
if (speaker.slug) {
|
|
910
|
+
try {
|
|
911
|
+
const entitiesLib = require("./entities");
|
|
912
|
+
sourceLine = `Guard policy source: ${entitiesLib.ENTITIES_DIR}/${speaker.slug}.md — the ONLY note the guard reads for ${who}. A same-named note elsewhere is a different identity and grants nothing here.`;
|
|
913
|
+
} catch (e) {}
|
|
914
|
+
}
|
|
898
915
|
const lines = [
|
|
899
916
|
`\n\n## External speaker — restricted mode`,
|
|
900
|
-
`You are talking to ${who}, who is NOT the owner. You are operating under a strict, owner-defined scope
|
|
917
|
+
`You are talking to ${who}, who is NOT the owner. You are operating under a strict, owner-defined scope.${unverified ? ` Their identity is UNVERIFIED (auto-added on first contact — only the owner can verify them, via the approval card or /people verify).` : ""}`,
|
|
918
|
+
...(sourceLine ? [sourceLine] : []),
|
|
901
919
|
"",
|
|
902
920
|
mandate
|
|
903
921
|
? `Mandate — the ONLY things you are cleared to do or discuss with ${who}:\n${mandate}`
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Owner verification of auto-added contacts — the second half of the
|
|
2
|
+
// ensureContact story. ensureContact deliberately NEVER links a first-time
|
|
3
|
+
// speaker to an existing note by display name (names are claimable, so that
|
|
4
|
+
// would be an impersonation path); it mints an unverified stub instead. This
|
|
5
|
+
// module gives the OWNER the explicit, audited act that closes the loop:
|
|
6
|
+
// confirm who the speaker is, optionally bind them to the existing trusted
|
|
7
|
+
// note, retire the stub, and clear the unverified flag the enforcer surfaces.
|
|
8
|
+
//
|
|
9
|
+
// Security invariants (each refusal below is one of them):
|
|
10
|
+
// - Verification is an OWNER decision arriving via a chat-envelope surface
|
|
11
|
+
// (approval-card button / /people slash command). There is deliberately no
|
|
12
|
+
// CLI or loopback verb: identity decisions never run on agent-forgeable
|
|
13
|
+
// input. Callers must gate on the transport envelope BEFORE calling here.
|
|
14
|
+
// - Verification is NOT trust: it settles who the person is. Relationship
|
|
15
|
+
// upgrades and mandate lines remain separate explicit owner grants.
|
|
16
|
+
// - Never link to an owner-relationship note (R1 defense-in-depth: speakerFor
|
|
17
|
+
// already coerces such a claim, but this surface refuses to create it).
|
|
18
|
+
// - Never link two people to one note: a persona pack carries a Mandate, and
|
|
19
|
+
// sharing one across identities would silently share granted capabilities.
|
|
20
|
+
// - Only auto-added (autoCreated) contacts are verifiable here. Re-pointing an
|
|
21
|
+
// already-verified person is intentionally NOT offered, so this surface can
|
|
22
|
+
// never be used as a general re-identity tool.
|
|
23
|
+
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
const people = require("./people");
|
|
27
|
+
const entities = require("./entities");
|
|
28
|
+
const audit = require("./audit");
|
|
29
|
+
|
|
30
|
+
// Same-name suggestions for the owner surfaces: existing person-type notes
|
|
31
|
+
// this unverified contact could plausibly BE. Excludes the contact's own stub,
|
|
32
|
+
// owner-relationship notes, and notes already bound to another person record.
|
|
33
|
+
// Suggestion-only — verifyContact re-validates whatever the owner picks.
|
|
34
|
+
function candidateEntitiesFor(person) {
|
|
35
|
+
if (!person) return [];
|
|
36
|
+
const stubSlug = people.resolveEntitySlug(person);
|
|
37
|
+
const wanted = String(person.name || "").trim().toLowerCase();
|
|
38
|
+
if (!wanted) return [];
|
|
39
|
+
const taken = new Set(
|
|
40
|
+
people.list()
|
|
41
|
+
.filter((p) => p.id !== person.id)
|
|
42
|
+
.map((p) => people.resolveEntitySlug(p))
|
|
43
|
+
.filter(Boolean),
|
|
44
|
+
);
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const e of entities.listEntities()) {
|
|
47
|
+
if (e.type !== "person") continue;
|
|
48
|
+
if (e.slug === stubSlug) continue;
|
|
49
|
+
if (e.relationship === "owner") continue;
|
|
50
|
+
if (taken.has(e.slug)) continue;
|
|
51
|
+
const names = [e.name, ...(e.aliases || [])]
|
|
52
|
+
.map((n) => String(n || "").trim().toLowerCase())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
if (names.includes(wanted)) out.push(e);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function refuse(reason, extra = {}) {
|
|
60
|
+
return { ok: false, reason, ...extra };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Verify (and optionally link) one auto-added contact.
|
|
64
|
+
// personId — the people.json record to verify
|
|
65
|
+
// linkSlug — optional existing note to bind them to; omitted = "this stub
|
|
66
|
+
// IS the right note, just mark them verified"
|
|
67
|
+
// by / via — provenance for the audit trail (owner id, surface name)
|
|
68
|
+
function verifyContact({ personId, linkSlug = null, by = "", via = "" } = {}) {
|
|
69
|
+
const person = people.findById(String(personId || ""));
|
|
70
|
+
if (!person) return refuse("person not found");
|
|
71
|
+
if (person.isOwner) return refuse("the owner record is never routed through contact verification");
|
|
72
|
+
if (!person.autoCreated) return refuse(`${person.name} is already verified`, { already: true });
|
|
73
|
+
|
|
74
|
+
const stubSlug = people.resolveEntitySlug(person);
|
|
75
|
+
let targetSlug = stubSlug;
|
|
76
|
+
let supersededStub = false;
|
|
77
|
+
|
|
78
|
+
if (linkSlug) {
|
|
79
|
+
const wantedSlug = entities.slugify(String(linkSlug));
|
|
80
|
+
if (wantedSlug && wantedSlug !== stubSlug) {
|
|
81
|
+
const target = entities.readEntity(wantedSlug);
|
|
82
|
+
if (!target) return refuse(`no note found at ${wantedSlug}`);
|
|
83
|
+
if (target.type !== "person") return refuse(`${wantedSlug} is a ${target.type} note, not a person`);
|
|
84
|
+
if (target.relationship === "owner") {
|
|
85
|
+
return refuse("refusing to link a contact to an owner-relationship note — ownership only ever comes from the people record");
|
|
86
|
+
}
|
|
87
|
+
const holder = people.list().find(
|
|
88
|
+
(p) => p.id !== person.id && people.resolveEntitySlug(p) === target.slug,
|
|
89
|
+
);
|
|
90
|
+
if (holder) {
|
|
91
|
+
return refuse(`that note already belongs to ${holder.name} — two people can never share one persona pack`);
|
|
92
|
+
}
|
|
93
|
+
targetSlug = target.slug;
|
|
94
|
+
|
|
95
|
+
// Provenance onto the surviving note, then retire the stub file as
|
|
96
|
+
// .superseded (kept on disk — the merge is inspectable, not destructive).
|
|
97
|
+
entities.upsertEntity({
|
|
98
|
+
slug: target.slug,
|
|
99
|
+
name: target.name,
|
|
100
|
+
log: `owner verified ${person.name} as this contact (absorbed auto-added stub ${stubSlug})`,
|
|
101
|
+
}, "owner-verify");
|
|
102
|
+
if (stubSlug) {
|
|
103
|
+
const stubFile = path.join(entities.ENTITIES_DIR, `${stubSlug}.md`);
|
|
104
|
+
try {
|
|
105
|
+
if (fs.existsSync(stubFile)) {
|
|
106
|
+
fs.renameSync(stubFile, `${stubFile}.superseded`);
|
|
107
|
+
supersededStub = true;
|
|
108
|
+
}
|
|
109
|
+
} catch (e) { /* best-effort: a stranded stub is inert once unlinked */ }
|
|
110
|
+
try { entities.markIndexDirty(); } catch (e) {}
|
|
111
|
+
}
|
|
112
|
+
people.setEntitySlug(person.id, target.slug);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (targetSlug === stubSlug && stubSlug) {
|
|
117
|
+
// Verify-only: the stub becomes the canonical note for this person. The
|
|
118
|
+
// description keeps its first-seen provenance, minus the unverified label.
|
|
119
|
+
const stub = entities.readEntity(stubSlug);
|
|
120
|
+
const description = stub && stub.description
|
|
121
|
+
? stub.description
|
|
122
|
+
.replace(/^Auto-added contact \(unverified\)/, "Verified contact")
|
|
123
|
+
.replace(/\s*Possible existing note:.*$/, "")
|
|
124
|
+
: "Verified contact — identity confirmed by the owner.";
|
|
125
|
+
entities.upsertEntity({
|
|
126
|
+
slug: stubSlug,
|
|
127
|
+
name: person.name,
|
|
128
|
+
description,
|
|
129
|
+
log: "owner verified this contact; this note is now their canonical persona pack",
|
|
130
|
+
}, "owner-verify");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const fresh = people.findById(person.id) || person;
|
|
134
|
+
const updated = people.update(person.id, {
|
|
135
|
+
autoCreated: false,
|
|
136
|
+
verifiedAt: new Date().toISOString(),
|
|
137
|
+
verifiedBy: String(by || "owner"),
|
|
138
|
+
verifiedVia: String(via || ""),
|
|
139
|
+
handles: (fresh.handles || []).map((h) => (
|
|
140
|
+
h.approvedBy === "auto-contact" ? { ...h, approvedBy: String(by || "owner") } : h
|
|
141
|
+
)),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
audit.log("people.verify", {
|
|
145
|
+
by: String(by || "owner"),
|
|
146
|
+
via: String(via || ""),
|
|
147
|
+
personId: person.id,
|
|
148
|
+
name: person.name,
|
|
149
|
+
slug: targetSlug,
|
|
150
|
+
supersededStub: supersededStub ? stubSlug : null,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return { ok: true, person: updated, slug: targetSlug, superseded: supersededStub ? stubSlug : null };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { verifyContact, candidateEntitiesFor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.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": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"setup": "node setup.js",
|
|
12
12
|
"start": "node bot.js",
|
|
13
13
|
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-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"
|
|
14
|
+
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -25,7 +25,10 @@ function fixture() {
|
|
|
25
25
|
assert.strictEqual(f.events[0].payload.reason, "unsupported_media");
|
|
26
26
|
f = fixture();
|
|
27
27
|
await f.manager.incoming({ fromUserId: "owner-1", fromUserName: "Owner", chatId: "chat", roomId: 12, callType: "audio" });
|
|
28
|
-
|
|
28
|
+
// Ringing must go out first (caller UI feedback), then the accept.
|
|
29
|
+
assert.strictEqual(f.events[0].name, "call:ringing");
|
|
30
|
+
assert.deepStrictEqual(f.events[0].payload, { toUserId: "owner-1", chatId: "chat" });
|
|
31
|
+
assert.strictEqual(f.events[1].name, "call:accept");
|
|
29
32
|
f.manager.session.options.onTranscript("What is on my agenda?");
|
|
30
33
|
assert.strictEqual(f.inbound[0].payload.raw.ephemeralAudio, true);
|
|
31
34
|
// Call turns must mute observer banners (spoken otherwise) and flag live-call
|