@inetafrica/open-claudia 3.5.19 → 3.5.20

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.
@@ -98,13 +98,15 @@ function listSessions({ dirs = socketDirs(), alive = processAlive } = {}) {
98
98
  } catch (_) { /* try the next file */ }
99
99
  }
100
100
  seen.add(name);
101
- sessions.push({ name, port, page, updatedAt });
101
+ // dir: the socket folder, so the bot's own agent-browser calls (core/browser-teach.js) reach
102
+ // this session and never start a new one. Internal like the port.
103
+ sessions.push({ name, port, page, updatedAt, dir });
102
104
  }
103
105
  }
104
106
  return sessions.sort((a, b) => String(b.updatedAt || "").localeCompare(String(a.updatedAt || "")));
105
107
  }
106
108
 
107
- // Public view of a session: the stream port stays inside the bot.
109
+ // Public view of a session: the stream port and socket folder stay inside the bot.
108
110
  function describeSessions(options) {
109
111
  return listSessions(options).map(({ name, page, updatedAt }) => ({ name, page, updatedAt }));
110
112
  }
@@ -189,6 +191,7 @@ function serveStream(res, sessionName, { fps, sessions, connect, keepaliveMs = K
189
191
  module.exports = {
190
192
  DEFAULT_FPS,
191
193
  MAX_FPS,
194
+ SESSION_NAME,
192
195
  clampFps,
193
196
  describeSessions,
194
197
  listSessions,
package/core/cards.js CHANGED
@@ -255,7 +255,9 @@ function describeResponse(rec, payload, now = Date.now()) {
255
255
  // ---------------------------------------------------------------------------
256
256
  // Waking the conversation a card (or decision) came from
257
257
 
258
- function wakeOrigin(origin, prompt, label, { scheduler, delayMs = 1500 } = {}) {
258
+ // `source` tags the job for job lists and audits: "card" for card answers,
259
+ // "browser" for the Browser app's hand-backs and Teach-me recordings.
260
+ function wakeOrigin(origin, prompt, label, { scheduler, delayMs = 1500, source = "card" } = {}) {
259
261
  if (!origin || !origin.channelId || !origin.provider || !origin.project || !origin.canonicalUserId) {
260
262
  throw new Error("the card's origin conversation is unknown");
261
263
  }
@@ -282,7 +284,7 @@ function wakeOrigin(origin, prompt, label, { scheduler, delayMs = 1500 } = {}) {
282
284
  },
283
285
  prompt,
284
286
  label: String(label).slice(0, 60),
285
- source: "card",
287
+ source,
286
288
  fireAt: Date.now() + delayMs,
287
289
  });
288
290
  }
@@ -239,6 +239,16 @@ Raw shell one-liners and heredocs are for probing and diagnosis only — never t
239
239
  ${listing}`;
240
240
  }
241
241
 
242
+ // Kazee Chat's Browser app (core/bot-apps.js): only on a bot that offers it, so a bot without
243
+ // Agent Space is never told to send an Open Browser button that cannot open.
244
+ function buildBrowserAppBlock() {
245
+ let apps = null;
246
+ try { apps = require("./bot-apps").appsToRegister(process.env); } catch (_) { apps = null; }
247
+ if (!apps || !apps.some((app) => app.key === "browser")) return "";
248
+ return `
249
+ Kazee Chat Browser app: people in a direct chat with you can watch this browser live, take it over, and teach you a task (Teach me). When you need a person in the browser (a login, a captcha, a one-time code, a judgement call), send a card in their direct chat with an Open Browser button, say what you need, then wait: you are woken in that chat when they hand it back. For example \`open-claudia card send '{"v":1,"kind":"report","title":"I need you in the browser","body":"<what you need and why>","open":{"app":"browser","label":"Open Browser"}}'\`. While someone drives, your agent-browser commands other than get, snapshot and screenshot are refused. Never ask for passwords in chat.`;
250
+ }
251
+
242
252
  function buildCoreInstructionsUncached() {
243
253
  const soul = loadSoul();
244
254
  // Gate on actual TTS capability, not local Whisper: hosted pods hear and
@@ -368,7 +378,7 @@ Core workflow — open, snapshot to get element refs, then act on those refs:
368
378
  - \`agent-browser eval "<js>"\` — run JS in the page when no command fits.
369
379
  - \`agent-browser close\` — end the session when done.
370
380
 
371
- Notes: a background daemon persists between commands, so navigation state carries across calls in a turn. Prefer \`snapshot -i\` refs over guessing selectors. Keep output lean by acting on refs rather than dumping full HTML. This replaced the old Playwright MCP — do not look for a playwright tool.
381
+ Notes: a background daemon persists between commands, so navigation state carries across calls in a turn. Prefer \`snapshot -i\` refs over guessing selectors. Keep output lean by acting on refs rather than dumping full HTML. This replaced the old Playwright MCP — do not look for a playwright tool.${buildBrowserAppBlock()}
372
382
 
373
383
  If you tell the user "I'll check back in N minutes" or "I'll run this every morning", you MUST schedule it with one of the commands above in the same turn. Otherwise nothing happens.
374
384
 
@@ -659,6 +659,340 @@ function hookFailure(toolName, reason) {
659
659
  };
660
660
  }
661
661
 
662
+ // ── Browser app: the bot pauses while a person drives (bot apps v1, C7) ────
663
+ // While someone drives the bot's browser from Kazee Chat, the driver lock file
664
+ // (core/browser-lock.js) is live, and the bot's own agent-browser commands
665
+ // would fight their clicks. So an agent-browser call at a command position is
666
+ // refused, except the read-only ones: get, snapshot, screenshot, session list.
667
+ // A command position is the start of a command: the start of the line, or
668
+ // after ; & | && || ( $( a backtick or a newline, behind env assignments
669
+ // (FOO=1), shell words (if, then, do, !, {) and wrappers (npx, sudo, env,
670
+ // timeout, nice, xargs...). What a shell runs from a string is read the same
671
+ // way: bash/sh -c '...', eval, a heredoc or here-string fed to a shell, and
672
+ // $(...) or backticks, inside double quotes too. Any other string is data, so
673
+ // `echo "agent-browser click"`, `grep agent-browser`, a commit message or a
674
+ // heredoc written to a file never match. An expired lock never blocks, and any
675
+ // error here allows: this is a pause for a person's sake, not a security gate.
676
+ const AGENT_BROWSER_READ_ONLY = new Set(["get", "snapshot", "screenshot"]);
677
+ const AGENT_BROWSER_VALUE_FLAGS = new Set([
678
+ "--session", "--restore-save", "--restore-check-url", "--restore-check-text", "--restore-check-fn",
679
+ "--namespace", "--session-name", "--profile", "--state", "--headers", "--executable-path", "--extension",
680
+ "--init-script", "--enable", "--args", "--user-agent", "--proxy", "--proxy-bypass", "--ca-cert",
681
+ "--hide-scrollbars", "-p", "--provider", "--device", "--threshold", "--screenshot-dir", "--screenshot-quality",
682
+ "--screenshot-format", "--cdp", "--color-scheme", "--download-path", "--max-output", "--allowed-domains",
683
+ "--action-policy", "--confirm-actions", "--engine", "--input-mode", "--idle-timeout", "--model", "--config",
684
+ "--port", "--allowed-origins",
685
+ ]);
686
+ // agent-browser, agent-browser.js, agent-browser@latest, agent-browser-linux-x64...
687
+ const AGENT_BROWSER_BIN = /^agent-browser(?:$|[.@-])/;
688
+ const SHELL_WORDS = new Set(["!", "{", "if", "then", "else", "elif", "do", "while", "until"]);
689
+ const SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "mksh", "fish"]);
690
+ // Wrappers that run the command after them, with their options that take a value.
691
+ const COMMAND_WRAPPERS = new Map([
692
+ ["sudo", ["-u", "-g", "-C", "-h", "-p", "-r", "-t", "-U", "-D", "-R", "-T", "--user", "--group", "--host", "--prompt", "--role", "--type", "--other-user", "--chdir", "--chroot", "--command-timeout", "--close-from"]],
693
+ ["doas", ["-u", "-C"]],
694
+ ["env", ["-u", "-C", "-S", "--unset", "--chdir", "--split-string"]],
695
+ ["command", []], ["builtin", []], ["exec", ["-a"]], ["nohup", []], ["setsid", []],
696
+ ["time", ["-f", "-o", "--format", "--output"]],
697
+ ["nice", ["-n", "--adjustment"]],
698
+ ["timeout", ["-s", "-k", "--signal", "--kill-after"]],
699
+ ["stdbuf", ["-i", "-o", "-e", "--input", "--output", "--error"]],
700
+ ["xargs", ["-I", "-n", "-L", "-P", "-d", "-E", "-s", "-a", "--max-args", "--max-lines", "--max-procs", "--delimiter", "--eof", "--max-chars", "--arg-file"]],
701
+ ["npx", ["-p", "--package", "-c", "--call"]],
702
+ ["pnpx", ["-p", "--package"]],
703
+ ["bunx", ["-p", "--package"]],
704
+ ]);
705
+ // Wrapper options whose value is itself a command line: npx -c, env -S.
706
+ const WRAPPER_SCRIPT_OPTIONS = new Set(["-c", "--call", "-S", "--split-string"]);
707
+ const ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
708
+
709
+ const baseName = (word) => String(word).split("/").pop();
710
+
711
+ // Where $( ... ) that opens at `start` (just after "$(") closes: the index of
712
+ // its ")", or the end of the text. Nested parentheses and quotes are skipped.
713
+ function substitutionEnd(s, start) {
714
+ let depth = 1;
715
+ let i = start;
716
+ while (i < s.length) {
717
+ const c = s[i];
718
+ if (c === "\\") { i += 2; continue; }
719
+ if (c === "'") { const end = s.indexOf("'", i + 1); i = end < 0 ? s.length : end + 1; continue; }
720
+ if (c === '"') {
721
+ let j = i + 1;
722
+ while (j < s.length && s[j] !== '"') j += s[j] === "\\" ? 2 : 1;
723
+ i = j + 1;
724
+ continue;
725
+ }
726
+ if (c === "(") depth += 1;
727
+ else if (c === ")") { depth -= 1; if (depth === 0) return i; }
728
+ i += 1;
729
+ }
730
+ return s.length;
731
+ }
732
+
733
+ // The commands of a shell command line, the way a shell splits them: each is
734
+ // { words, input } with quotes removed, redirection targets and comments left
735
+ // out, and `input` the heredoc bodies and here-strings it reads. `nested` holds
736
+ // the text of every $(...) and backtick, which the shell also runs.
737
+ function shellCommands(command) {
738
+ const s = String(command || "");
739
+ const commands = [];
740
+ const nested = [];
741
+ let words = [];
742
+ let input = [];
743
+ let word = null;
744
+ let target = false; // the next word is a redirection's file or descriptor
745
+ let hereString = false; // the next word is a here-string (<<<)
746
+ let heredocs = []; // heredocs whose bodies start after the next newline
747
+ const endWord = () => {
748
+ if (word === null) return;
749
+ if (target) target = false;
750
+ else if (hereString) { hereString = false; input.push(word); }
751
+ else words.push(word);
752
+ word = null;
753
+ };
754
+ const endCommand = () => {
755
+ endWord();
756
+ if (words.length) commands.push({ words, input });
757
+ words = [];
758
+ input = [];
759
+ target = false;
760
+ hereString = false;
761
+ };
762
+ const backtick = (i) => {
763
+ let j = i + 1;
764
+ while (j < s.length && s[j] !== "`") j += s[j] === "\\" ? 2 : 1;
765
+ nested.push(s.slice(i + 1, Math.min(j, s.length)));
766
+ return j + 1;
767
+ };
768
+ let i = 0;
769
+ while (i < s.length) {
770
+ const c = s[i];
771
+ if (c === "\\") {
772
+ if (s[i + 1] === "\n") { i += 2; continue; } // a line continuation
773
+ if (i + 1 < s.length) word = (word || "") + s[i + 1];
774
+ i += 2;
775
+ continue;
776
+ }
777
+ if (c === "#" && word === null) { // a comment, to the end of the line
778
+ while (i < s.length && s[i] !== "\n") i += 1;
779
+ continue;
780
+ }
781
+ if (c === "'") {
782
+ const end = s.indexOf("'", i + 1) < 0 ? s.length : s.indexOf("'", i + 1);
783
+ word = (word || "") + s.slice(i + 1, end);
784
+ i = end + 1;
785
+ continue;
786
+ }
787
+ if (c === '"') {
788
+ let j = i + 1;
789
+ let text = "";
790
+ while (j < s.length && s[j] !== '"') {
791
+ if (s[j] === "\\" && j + 1 < s.length) {
792
+ if (s[j + 1] !== "\n") text += /[$`"\\]/.test(s[j + 1]) ? s[j + 1] : s[j] + s[j + 1];
793
+ j += 2;
794
+ continue;
795
+ }
796
+ if (s[j] === "$" && s[j + 1] === "(") {
797
+ const end = substitutionEnd(s, j + 2);
798
+ nested.push(s.slice(j + 2, end));
799
+ text += s.slice(j, end + 1);
800
+ j = end + 1;
801
+ continue;
802
+ }
803
+ if (s[j] === "`") {
804
+ const after = backtick(j);
805
+ text += s.slice(j, after);
806
+ j = after;
807
+ continue;
808
+ }
809
+ text += s[j];
810
+ j += 1;
811
+ }
812
+ word = (word || "") + text;
813
+ i = j + 1;
814
+ continue;
815
+ }
816
+ if (c === "`") {
817
+ const after = backtick(i);
818
+ word = (word || "") + s.slice(i, after);
819
+ i = after;
820
+ continue;
821
+ }
822
+ if (c === "$" && s[i + 1] === "(") {
823
+ const end = substitutionEnd(s, i + 2);
824
+ nested.push(s.slice(i + 2, end));
825
+ word = (word || "") + s.slice(i, end + 1);
826
+ i = end + 1;
827
+ continue;
828
+ }
829
+ if (c === "<" && s.startsWith("<<<", i)) { endWord(); hereString = true; i += 3; continue; }
830
+ if (c === "<" && s[i + 1] === "<") { // a heredoc: its body follows the next newline
831
+ endWord();
832
+ i += 2;
833
+ const strip = s[i] === "-";
834
+ if (strip) i += 1;
835
+ while (s[i] === " " || s[i] === "\t") i += 1;
836
+ let delim = "";
837
+ while (i < s.length && !/[\s;&|<>()]/.test(s[i])) {
838
+ if (s[i] === "'" || s[i] === '"') {
839
+ const q = s[i];
840
+ const end = s.indexOf(q, i + 1) < 0 ? s.length : s.indexOf(q, i + 1);
841
+ delim += s.slice(i + 1, end);
842
+ i = end + 1;
843
+ } else {
844
+ if (s[i] !== "\\") delim += s[i];
845
+ i += 1;
846
+ }
847
+ }
848
+ if (delim) heredocs.push({ delim, strip, input });
849
+ continue;
850
+ }
851
+ if (c === "<" || c === ">") { // a redirection: the word after it is a file, not an argument
852
+ if (word !== null && /^\d+$/.test(word)) word = null; // the descriptor of 2>, 1>&...
853
+ endWord();
854
+ i += /[>&|]/.test(s[i + 1] || "") ? 2 : 1;
855
+ target = true;
856
+ continue;
857
+ }
858
+ if (c === "\n" || c === "\r") {
859
+ endCommand();
860
+ i += 1;
861
+ if (c === "\n" && heredocs.length) {
862
+ for (const doc of heredocs) {
863
+ const body = [];
864
+ while (i < s.length) {
865
+ const eol = s.indexOf("\n", i) < 0 ? s.length : s.indexOf("\n", i);
866
+ let line = s.slice(i, eol);
867
+ if (doc.strip) line = line.replace(/^\t+/, "");
868
+ i = eol + 1;
869
+ if (line === doc.delim) break;
870
+ body.push(line);
871
+ }
872
+ doc.input.push(body.join("\n"));
873
+ }
874
+ heredocs = [];
875
+ }
876
+ continue;
877
+ }
878
+ if (/[;&|()]/.test(c)) { endCommand(); i += 1; continue; }
879
+ if (/\s/.test(c)) { endWord(); i += 1; continue; }
880
+ word = (word || "") + c;
881
+ i += 1;
882
+ }
883
+ endCommand();
884
+ return { commands, nested };
885
+ }
886
+
887
+ // The index of the command's own name, behind env assignments, shell words and
888
+ // wrappers with their options (-1 when there is none). A wrapper option whose
889
+ // value is a command line (npx -c, env -S) is added to `scripts`.
890
+ function commandWordIndex(words, scripts) {
891
+ let k = 0;
892
+ for (let guard = 0; guard < 16 && k < words.length; guard += 1) {
893
+ const w = words[k];
894
+ if (ENV_ASSIGNMENT.test(w) || SHELL_WORDS.has(w)) { k += 1; continue; }
895
+ const name = baseName(w);
896
+ const valueOptions = COMMAND_WRAPPERS.get(name);
897
+ if (!valueOptions) return k;
898
+ k += 1;
899
+ let positional = name === "timeout" ? 1 : 0; // timeout DURATION command
900
+ while (k < words.length) {
901
+ const arg = words[k];
902
+ if (arg === "--") { k += 1; break; }
903
+ if (name === "env" && ENV_ASSIGNMENT.test(arg)) { k += 1; continue; }
904
+ if (arg.length > 1 && arg.startsWith("-")) {
905
+ if (valueOptions.includes(arg)) {
906
+ if (WRAPPER_SCRIPT_OPTIONS.has(arg) && words[k + 1] !== undefined) scripts.push(words[k + 1]);
907
+ k += 2;
908
+ } else {
909
+ k += 1;
910
+ }
911
+ continue;
912
+ }
913
+ if (positional > 0) { positional -= 1; k += 1; continue; }
914
+ break;
915
+ }
916
+ }
917
+ return k < words.length ? k : -1;
918
+ }
919
+
920
+ // agent-browser's subcommand and the word after it (sub is null when there is
921
+ // none, e.g. --help or --version).
922
+ function agentBrowserCall(words, at) {
923
+ let j = at + 1;
924
+ while (j < words.length) {
925
+ const arg = words[j];
926
+ if (arg === "--") { j += 1; continue; }
927
+ if (arg.startsWith("-")) {
928
+ if (!arg.includes("=") && AGENT_BROWSER_VALUE_FLAGS.has(arg)) j += 2;
929
+ else j += /^(true|false)$/.test(words[j + 1] || "") ? 2 : 1; // boolean flags take an optional true/false
930
+ continue;
931
+ }
932
+ return { sub: arg, sub2: words[j + 1] === undefined ? null : words[j + 1] };
933
+ }
934
+ return { sub: null, sub2: null };
935
+ }
936
+
937
+ // Every agent-browser invocation in a command: [{ sub, sub2 }].
938
+ function agentBrowserInvocations(command, depth = 0) {
939
+ const calls = [];
940
+ if (depth > 4) return calls;
941
+ const { commands, nested } = shellCommands(command);
942
+ const scripts = [...nested];
943
+ for (const { words, input } of commands) {
944
+ const at = commandWordIndex(words, scripts);
945
+ if (at < 0) continue;
946
+ const name = baseName(words[at]);
947
+ if (AGENT_BROWSER_BIN.test(name)) {
948
+ calls.push(agentBrowserCall(words, at));
949
+ } else if (name === "eval") {
950
+ scripts.push(words.slice(at + 1).join(" "));
951
+ } else if (SHELLS.has(name)) {
952
+ // sh -c '<script>' [name args], else the script a shell reads from its heredoc/here-string.
953
+ let script = null;
954
+ for (let j = at + 1; j < words.length; j += 1) {
955
+ const w = words[j];
956
+ if (/^-[A-Za-z]*c[A-Za-z]*$/.test(w)) {
957
+ script = words.slice(j + 1).find((x) => !x.startsWith("-"));
958
+ break;
959
+ }
960
+ if (w === "-o" || w === "+o" || w === "-O" || w === "+O") { j += 1; continue; }
961
+ if (!w.startsWith("-") && !w.startsWith("+")) break; // a script file
962
+ }
963
+ if (script != null) scripts.push(script);
964
+ else scripts.push(...input);
965
+ }
966
+ }
967
+ for (const script of scripts) calls.push(...agentBrowserInvocations(script, depth + 1));
968
+ return calls;
969
+ }
970
+
971
+ function readOnlyBrowserCall(call) {
972
+ if (!call.sub) return true; // no subcommand: usage or version
973
+ if (AGENT_BROWSER_READ_ONLY.has(call.sub)) return true;
974
+ return call.sub === "session" && call.sub2 === "list";
975
+ }
976
+
977
+ // null, or { subcommand, message } when a live driver lock refuses this command.
978
+ function browserDriverDenial(command, now = Date.now()) {
979
+ try {
980
+ const cmd = String(command || "");
981
+ if (!cmd.includes("agent-browser")) return null;
982
+ const lock = require("./browser-lock").liveLock(now);
983
+ if (!lock) return null;
984
+ const blocked = agentBrowserInvocations(cmd).find((call) => !readOnlyBrowserCall(call));
985
+ if (!blocked) return null;
986
+ const name = String(lock.name || "").replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 60) || "Someone";
987
+ return {
988
+ subcommand: String(blocked.sub || "").slice(0, 40),
989
+ message: `${name} is driving the browser right now. Wait for them to hand it back; you will be woken in their chat when they do.`,
990
+ };
991
+ } catch (e) {
992
+ return null;
993
+ }
994
+ }
995
+
662
996
  // Claude sends the shell command as a string; accept an argv array too.
663
997
  function shellCommandOf(input) {
664
998
  if (!input || typeof input !== "object") return null;
@@ -727,6 +1061,14 @@ function runHook(payload) {
727
1061
  logGuardEvent({ kind: "deny", rule: "external-secret-read", pattern: secretDenial.rule, command: String(command).slice(0, 300) });
728
1062
  return { exit: 2, stderr: secretDenial.message };
729
1063
  }
1064
+ // A person is driving the browser (Browser app): ahead of the tooling-mode
1065
+ // switch, so relaxed mode cannot drive over them either. Only the
1066
+ // subcommand is logged: the bot's own fill/type commands can carry text.
1067
+ const driving = browserDriverDenial(command);
1068
+ if (driving) {
1069
+ logGuardEvent({ kind: "deny", rule: "browser-driver", subcommand: driving.subcommand });
1070
+ return { exit: 2, stderr: driving.message };
1071
+ }
730
1072
  // Relaxed tooling mode: the gate observes but never blocks. The event is
731
1073
  // still logged (as relaxed-pass, not deny) so the dream KPI can report
732
1074
  // what WOULD have been denied — visibility survives the mode switch.
@@ -761,4 +1103,5 @@ module.exports = {
761
1103
  externalFileMutationDenial, externalControlPlaneDenial, guardedSpeakerFromEnv,
762
1104
  protectedReadRoots, isProtectedReadPath, isProtectedSearchRoot, secretReadRule,
763
1105
  matchRawOp, noteRawOp, readGuardEvents,
1106
+ agentBrowserInvocations, browserDriverDenial,
764
1107
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.5.19",
3
+ "version": "3.5.20",
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-codex-output-schema.js && node test-codex-device-login.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-group-status-notes.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
14
- "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js && node test-kazee-cards.js && node test-decision-upkeep.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-kazee-bot-handoffs.js && node test-placeholder-reply.js && node test-media-speech.js && node test-passive-replies-router.js && node test-loopback-cards.js && node test-task-injection.js && node test-speaker-extras.js && node test-token-diet.js && node test-dream-tasks.js && node test-task-archive.js && node test-cron-tolerance.js && node test-dream-schedule.js && node test-job-failures.js && node test-scheduled-placeholder.js && node test-lessons-since.js && node test-pack-reinject.js && node test-turn-journal.js && node test-usage-limits.js && node test-limit-pickup.js && node test-browser-view.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-view.js && node test-safe-filename.js && node test-media-filename-paths.js && node test-router-document-guard.js && OPEN_CLAUDIA_TEST=1 node test-web-dashboard-escape.js && OPEN_CLAUDIA_TEST=1 node test-web-bad-requests.js && OPEN_CLAUDIA_TEST=1 node test-kazee-download-limit.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-auth-routes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-otp-link-preview.js"
14
+ "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js && node test-kazee-cards.js && node test-decision-upkeep.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-kazee-bot-handoffs.js && node test-placeholder-reply.js && node test-media-speech.js && node test-passive-replies-router.js && node test-loopback-cards.js && node test-task-injection.js && node test-speaker-extras.js && node test-token-diet.js && node test-dream-tasks.js && node test-task-archive.js && node test-cron-tolerance.js && node test-dream-schedule.js && node test-job-failures.js && node test-scheduled-placeholder.js && node test-lessons-since.js && node test-pack-reinject.js && node test-turn-journal.js && node test-usage-limits.js && node test-limit-pickup.js && node test-browser-view.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-view.js && node test-browser-control.js && node test-browser-teach.js && node test-kazee-bot-apps.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-app.js && node test-safe-filename.js && node test-media-filename-paths.js && node test-router-document-guard.js && OPEN_CLAUDIA_TEST=1 node test-web-dashboard-escape.js && OPEN_CLAUDIA_TEST=1 node test-web-bad-requests.js && OPEN_CLAUDIA_TEST=1 node test-kazee-download-limit.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-auth-routes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-otp-link-preview.js"
15
15
  },
16
16
  "files": [
17
17
  "bot.js",
@@ -591,7 +591,11 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
591
591
  // dedup judge and the classic recall filter reply in an object (the API
592
592
  // refused their bare-array formats on every Claude call), and the utility
593
593
  // agent refuses any reply format without an object root. This Mac bot first.
594
- assert.strictEqual(pkg.version, "3.5.19", "release version must remain unchanged without explicit approval");
594
+ // 3.5.20 approved by Sumeet 2026-10-09 12:02 EAT ("fine go ahead and push but
595
+ // also ensure you clear the images after they all done"): the Browser app in
596
+ // Kazee Chat (bot apps v1): Take over, Teach me with its pictures deleted when
597
+ // the work is done, and the bot paused while a person drives. A canary bot first.
598
+ assert.strictEqual(pkg.version, "3.5.20", "release version must remain unchanged without explicit approval");
595
599
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
596
600
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
597
601
  }
package/web.js CHANGED
@@ -375,6 +375,63 @@ async function liveChannels() {
375
375
 
376
376
  const USER_ACCESS_ROUTES = new Set(["/api/auth/approve", "/api/auth/deny", "/api/auth/remove"]);
377
377
 
378
+ // Browser app routes (core/bot-apps.js, core/browser-control.js, core/browser-teach.js):
379
+ // POST /api/apps/check may this person open the app (C1)
380
+ // POST /api/browser/control GET /api/browser/control take / renew / release, status (C2)
381
+ // POST /api/browser/input the driver's input events (C3)
382
+ // POST /api/browser/teach Teach me: start / stop / cancel (C4)
383
+ // GET /api/browser/teach[/<id>[/pictures/<n>]]?userId= that person's recordings only
384
+ const BROWSER_APP_ROUTE = /^\/api\/(?:apps\/check|browser\/(?:control|input|teach))(?:[/?]|$)/;
385
+ const BROWSER_APP_MAX_BODY = 64 * 1024;
386
+ const BROWSER_APP_USER_ID = /^[A-Za-z0-9._:-]{1,100}$/;
387
+
388
+ async function handleBrowserApp(req, res, body) {
389
+ const send = (status, obj) => {
390
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
391
+ return res.end(JSON.stringify(obj));
392
+ };
393
+ const answer = (result) => send(result.status, result.body);
394
+ const parsedUrl = new URL(req.url, "http://localhost");
395
+ const route = parsedUrl.pathname;
396
+ let parsed = {};
397
+ if (req.method === "POST") {
398
+ if (Buffer.byteLength(body || "") > BROWSER_APP_MAX_BODY) return send(413, { error: "too-large" });
399
+ try { parsed = JSON.parse(body || "{}"); } catch (e) { return send(400, { error: "bad-json" }); }
400
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return send(400, { error: "bad-json" });
401
+ }
402
+ const control = require("./core/browser-control");
403
+ if (route === "/api/apps/check" && req.method === "POST") return send(200, require("./core/bot-apps").checkAccess(parsed));
404
+ if (route === "/api/browser/control" && req.method === "POST") return answer(await control.control(parsed));
405
+ if (route === "/api/browser/control" && req.method === "GET") return answer(control.status());
406
+ if (route === "/api/browser/input" && req.method === "POST") return answer(await control.input(parsed));
407
+ if (route === "/api/browser/teach" && req.method === "POST") return answer(await control.teachAction(parsed));
408
+ if (req.method === "GET" && (route === "/api/browser/teach" || route.startsWith("/api/browser/teach/"))) {
409
+ const teach = require("./core/browser-teach");
410
+ const userId = parsedUrl.searchParams.get("userId") || "";
411
+ if (!BROWSER_APP_USER_ID.test(userId)) return send(400, { error: "userId required" });
412
+ if (route === "/api/browser/teach") return send(200, teach.listRecordings({ userId }));
413
+ const match = route.match(/^\/api\/browser\/teach\/([a-z0-9-]{1,64})(?:\/pictures\/(\d{1,4}))?$/);
414
+ if (!match) return send(404, { error: "not-found" });
415
+ if (!match[2]) {
416
+ const rec = teach.getRecording(match[1], { userId });
417
+ return rec ? send(200, rec) : send(404, { error: "not-found" });
418
+ }
419
+ const file = teach.picturePath(match[1], Number(match[2]), { userId });
420
+ if (!file) return send(404, { error: "not-found" });
421
+ let data;
422
+ // Deleted a moment ago (teach finish, cancel, the sweeper): gone, like any deleted picture.
423
+ try { data = fs.readFileSync(file); } catch (_) { return send(404, { error: "not-found" }); }
424
+ res.writeHead(200, {
425
+ "Content-Type": "image/jpeg",
426
+ "Content-Length": data.length,
427
+ "Cache-Control": "no-store",
428
+ "X-Content-Type-Options": "nosniff",
429
+ });
430
+ return res.end(data);
431
+ }
432
+ return send(404, { error: "Not found" });
433
+ }
434
+
378
435
  async function handleAPI(req, res, body, dependencies = {}) {
379
436
  const url = req.url;
380
437
 
@@ -643,6 +700,17 @@ async function handleAPI(req, res, body, dependencies = {}) {
643
700
  return res.end(JSON.stringify({ content: loadLogs() }));
644
701
  }
645
702
 
703
+ // Browser app in Kazee Chat (bot apps v1): who may open it, Take over, input and Teach me.
704
+ // Machine-to-machine for Agent Space, so the control token only: a dashboard session is
705
+ // refused, like /api/otp/mint.
706
+ if (BROWSER_APP_ROUTE.test(url)) {
707
+ if (!checkBearerAuth(req)) {
708
+ res.writeHead(403, { "Content-Type": "application/json" });
709
+ return res.end(JSON.stringify({ error: "Control token required" }));
710
+ }
711
+ return handleBrowserApp(req, res, body);
712
+ }
713
+
646
714
  // Live view of the bot's own browser, watch-only (core/browser-view.js): the running
647
715
  // agent-browser sessions, and one session's screen as server-sent events.
648
716
  if (url.split("?")[0] === "/api/browser/sessions" && req.method === "GET") {