@inetafrica/open-claudia 3.5.0 → 3.5.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 +17 -2
- package/bin/loopback-client.js +19 -5
- package/bin/owner-gate.js +2 -1
- package/bin/tool.js +2 -2
- package/core/bot-endpoint.js +194 -0
- package/core/runner.js +11 -6
- package/core/scheduler.js +19 -29
- package/core/tool-guard.js +15 -0
- package/package.json +2 -2
- package/test-provider-language.js +6 -1
- package/test-provider-parity-e2e.js +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.5.1
|
|
4
|
+
|
|
5
|
+
Hardening after an adversarial review of 3.5.0, before any of it reaches the fleet. Canary first.
|
|
6
|
+
|
|
7
|
+
- **A CLI believes only its own bot about who it acts for.** `whoami` and the staff `tool-gate` answer are asked only of a loopback that the CLI process itself or one of its ancestors is listening on, and never of one started inside a staff turn. An address in `OC_SEND_URL` is no longer enough: in 3.5.0 a staff turn could run its own server that answered "owner", and the keyring and the other owner-only verbs believed it. Linux reads `/proc`; macOS asks `/bin/ps` and `/usr/sbin/lsof`. Anything that cannot be checked is unattributed, which is never the owner.
|
|
8
|
+
- **A staff member's watch check gets only the shell essentials.** It runs with an allowlist (PATH, HOME, user, locale, terminal, temp dirs, the config dir), its run stamp and `OC_GUARDED_RUN=1`. 3.5.0 dropped credentials by name pattern, so ones like `DATABASE_URL` or `MONGO_URI` still reached staff checks.
|
|
9
|
+
- **The owner's watch checks get the bot's environment again,** so an owner watch that reads a credential from env works as it did before 3.5.0.
|
|
10
|
+
- **A run with an incomplete chat origin is not the owner.** An origin that names a person or room but not the transport and room it came from counts as a non-owner. A run with no origin at all is still bot-internal work.
|
|
11
|
+
- **The staff shell screen is wider, and it is not a boundary.** A staff turn's shell command may not name process environments or argument blocks, the keyring, the bot's config dir, per-process `/proc` paths, or credential stores. Correction to 3.5.0: the shell does not reliably refuse credential reads. A staff turn that has a shell can still read what the bot's OS user can read. Phase 2 takes the shell away from staff turns.
|
|
12
|
+
- **The owner's routes outside a chat turn** (terminal, ssh, system cron), where 3.5.0 refuses owner-only work:
|
|
13
|
+
- the keyring: `/keyring set`, `/keyring get` and `/keyring remove` in the owner's chat;
|
|
14
|
+
- tool, pack, lessons, entity and ideas edits, and `open-claudia agent`: ask in the owner's chat;
|
|
15
|
+
- a write or destructive tool run: run it from a chat turn, or re-run it with the one-shot `--approval <id>` from a card the owner approved.
|
|
16
|
+
- **Tests:** the provider parity test names the suite that timed out and allows 180 s per suite. A loaded CI runner overran 90 s on canary-v3.5.0's first build.
|
|
17
|
+
|
|
3
18
|
## 3.5.0
|
|
4
19
|
|
|
5
20
|
Security fixes for the permission system: phase 0 of the redesign in the owner's 25 Sep review. The owner's own chat turns work as before. Staff and external turns lose the ways around the checks. First shipped as a canary on two bots.
|
|
@@ -18,7 +33,7 @@ Security fixes for the permission system: phase 0 of the redesign in the owner's
|
|
|
18
33
|
- entity, pack, lessons and ideas writes;
|
|
19
34
|
- every keyring verb, including `list`;
|
|
20
35
|
- `open-claudia agent`.
|
|
21
|
-
From a plain terminal
|
|
36
|
+
From a plain terminal these are refused; 3.5.1 lists the owner's routes.
|
|
22
37
|
- **Follow-ups set in a staff turn run at that person's level, in that room.** This covers wakeups, crons and watches. The job's creator is the attested run. Before, it came from fields the run's own shell could forge, with a fallback to the owner. Watch checks run without the bot's secrets, as their creator.
|
|
23
38
|
- **People notes and tasks are owner-only through the bot's commands.**
|
|
24
39
|
- **History stays in the caller's room.** For a non-owner, `recent`, `transcript-search` and `transcript-window` return only the caller's room. `recent-fetch`'s channel filter, which matched nothing, is fixed.
|
|
@@ -29,7 +44,7 @@ Security fixes for the permission system: phase 0 of the redesign in the owner's
|
|
|
29
44
|
- SSH, cloud and kube credentials;
|
|
30
45
|
- other processes' environments.
|
|
31
46
|
- Paths are resolved through symlinks.
|
|
32
|
-
- The shell
|
|
47
|
+
- The shell screens a few common equivalents. This is not a boundary (corrected in 3.5.1).
|
|
33
48
|
- A permission hook that cannot run now refuses on a staff turn instead of letting the tool through.
|
|
34
49
|
- **Destructive is never a standing permission.**
|
|
35
50
|
- "Always" on a destructive card is refused.
|
package/bin/loopback-client.js
CHANGED
|
@@ -7,6 +7,7 @@ const fs = require("fs");
|
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const http = require("http");
|
|
9
9
|
const configDir = require(path.join(__dirname, "..", "config-dir"));
|
|
10
|
+
const botEndpoint = require(path.join(__dirname, "..", "core", "bot-endpoint"));
|
|
10
11
|
|
|
11
12
|
function readLoopbackInfo() {
|
|
12
13
|
try {
|
|
@@ -71,9 +72,19 @@ function requireContext() {
|
|
|
71
72
|
return ctx;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
// `verified: true` is for answers this process acts on (tool-gate): the
|
|
76
|
+
// request only goes to an endpoint served by this process's own bot (see
|
|
77
|
+
// core/bot-endpoint), never to whatever OC_SEND_URL names.
|
|
78
|
+
function postJson(kind, body, { verified = false } = {}) {
|
|
75
79
|
const ctx = requireContext();
|
|
76
|
-
|
|
80
|
+
let u;
|
|
81
|
+
if (verified) {
|
|
82
|
+
const check = botEndpoint.verifyEndpointCached(ctx.sendUrl);
|
|
83
|
+
if (!check.ok) return Promise.reject(new Error(`the bot's address could not be verified: ${check.reason}`));
|
|
84
|
+
u = new URL(check.url);
|
|
85
|
+
} else {
|
|
86
|
+
u = new URL(ctx.sendUrl);
|
|
87
|
+
}
|
|
77
88
|
const requestedProject = body && body.project ? String(body.project) : ctx.project;
|
|
78
89
|
const sameProject = !ctx.project || !requestedProject || requestedProject === ctx.project;
|
|
79
90
|
const payload = JSON.stringify({
|
|
@@ -134,14 +145,17 @@ function postJson(kind, body) {
|
|
|
134
145
|
// Who this process acts for, as the RUNNING BOT decides it. Needs no chat
|
|
135
146
|
// context: a process with no live run behind it (stripped env, detached from
|
|
136
147
|
// a finished turn, started outside the bot) comes back unattributed, and so
|
|
137
|
-
// does every failure to ask. Unattributed is never the owner.
|
|
148
|
+
// does every failure to ask. Unattributed is never the owner. The question
|
|
149
|
+
// only goes to an endpoint served by this process's own bot — a server the
|
|
150
|
+
// env points at is not believed (core/bot-endpoint).
|
|
138
151
|
const UNATTRIBUTED = Object.freeze({ attributed: false, owner: false, guarded: true, kind: "unattributed" });
|
|
139
152
|
|
|
140
153
|
function whoami({ timeoutMs = 5000 } = {}) {
|
|
141
154
|
const ctx = context();
|
|
142
155
|
if (!ctx.runId || !ctx.runAttest || !ctx.sendUrl || !ctx.sendToken) return Promise.resolve(UNATTRIBUTED);
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
const check = botEndpoint.verifyEndpointCached(ctx.sendUrl);
|
|
157
|
+
if (!check.ok) return Promise.resolve(Object.freeze({ ...UNATTRIBUTED, reason: check.reason }));
|
|
158
|
+
const u = new URL(check.url);
|
|
145
159
|
const payload = JSON.stringify({ runId: ctx.runId, originRunId: ctx.runId, runAttest: ctx.runAttest });
|
|
146
160
|
return new Promise((resolve) => {
|
|
147
161
|
const req = http.request({
|
package/bin/owner-gate.js
CHANGED
|
@@ -12,7 +12,8 @@ function hasRunAttest(env = process.env) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function refusal(what, caller) {
|
|
15
|
-
const why = !(caller && caller.attributed)
|
|
15
|
+
const why = !(caller && caller.attributed)
|
|
16
|
+
? `this process is not attributed to an owner chat turn${caller && caller.reason ? ` (${caller.reason})` : ""}`
|
|
16
17
|
: caller.name ? `this turn acts for ${caller.name}, not the owner`
|
|
17
18
|
: "this turn does not act for the owner";
|
|
18
19
|
return `⛔ Owner only: ${what} — ${why}.`;
|
package/bin/tool.js
CHANGED
|
@@ -438,7 +438,7 @@ async function run(args) {
|
|
|
438
438
|
const caller = await client.whoami();
|
|
439
439
|
if (!caller.attributed) {
|
|
440
440
|
if (!approvalId) {
|
|
441
|
-
console.error(`⛔ ${t.name} (${gate.tier}) refused — not attributed to a live chat turn; write actions need a chat turn or an owner approval token
|
|
441
|
+
console.error(`⛔ ${t.name} (${gate.tier}) refused — not attributed to a live chat turn; write actions need a chat turn or an owner approval token.${caller.reason ? ` (${caller.reason})` : ""}`);
|
|
442
442
|
process.exitCode = 3; return;
|
|
443
443
|
}
|
|
444
444
|
} else if (!caller.owner && !approvalId) {
|
|
@@ -448,7 +448,7 @@ async function run(args) {
|
|
|
448
448
|
if (!ctx.sendUrl || !ctx.sendToken || !ctx.channelId || !ctx.adapterId) throw new Error("no chat context to ask the bot from");
|
|
449
449
|
decision = await client.postJson("tool-gate", {
|
|
450
450
|
tool: t.name, verb: tools.invokedVerb(toolArgs), tier: gate.tier, command: commandLine,
|
|
451
|
-
});
|
|
451
|
+
}, { verified: true });
|
|
452
452
|
} catch (e) {
|
|
453
453
|
decision = { decision: "blocked", reason: `the bot could not vet this action (${e.message})` };
|
|
454
454
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Which loopback endpoint may a CLI believe about WHO it acts for?
|
|
2
|
+
//
|
|
3
|
+
// Owner-only CLI verbs and staff tool writes are decided by asking the bot
|
|
4
|
+
// (whoami, tool-gate). The address to ask comes from this process's env
|
|
5
|
+
// (OC_SEND_URL), and a staff turn's shell can point that at a server of its
|
|
6
|
+
// own that answers "owner" (Phase 0 review, CRITICAL). So an endpoint is
|
|
7
|
+
// believed for those answers only when:
|
|
8
|
+
// 1. the process LISTENING on it is this process or one of its ancestors —
|
|
9
|
+
// the bot spawns every run, so the real bot is always above the CLI,
|
|
10
|
+
// while a server a turn starts is beside it at best; and
|
|
11
|
+
// 2. nothing above that listener was STARTED with OC_GUARDED_RUN=1 in its
|
|
12
|
+
// environment — the listener was not itself started inside a staff turn
|
|
13
|
+
// (such a server could run the CLI as its own child, making itself an
|
|
14
|
+
// ancestor). A process's start-up environment is fixed by whoever spawned
|
|
15
|
+
// it; a turn's shell cannot rewrite its provider's.
|
|
16
|
+
// Linux reads /proc; macOS asks /bin/ps and /usr/sbin/lsof by absolute path
|
|
17
|
+
// (PATH belongs to the caller). Anything that cannot be established is a
|
|
18
|
+
// refusal, and a refused caller is unattributed — never the owner.
|
|
19
|
+
//
|
|
20
|
+
// Residual, closed by Phase 2's tool-only staff turns: a staff shell that
|
|
21
|
+
// detaches a server from its turn and runs the CLI beneath that server is no
|
|
22
|
+
// longer below the guarded provider, so rule 2 cannot see it.
|
|
23
|
+
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const { execFileSync } = require("child_process");
|
|
26
|
+
|
|
27
|
+
const MAX_DEPTH = 64;
|
|
28
|
+
const PS = "/bin/ps";
|
|
29
|
+
const LSOF = "/usr/sbin/lsof";
|
|
30
|
+
const EXEC_TIMEOUT_MS = 5000;
|
|
31
|
+
|
|
32
|
+
function parsePort(url) {
|
|
33
|
+
let parsed;
|
|
34
|
+
try { parsed = new URL(String(url)); } catch (_) { return null; }
|
|
35
|
+
if (parsed.protocol !== "http:") return null;
|
|
36
|
+
if (parsed.hostname !== "127.0.0.1" && parsed.hostname !== "localhost") return null;
|
|
37
|
+
const port = Number(parsed.port);
|
|
38
|
+
return Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Linux: /proc ─────────────────────────────────────────────────────────────
|
|
42
|
+
// The loopback binds 127.0.0.1 itself, never a wildcard, and nothing else can
|
|
43
|
+
// bind that exact address and port while it listens (no SO_REUSEPORT), so the
|
|
44
|
+
// only listener that counts is an IPv4 127.0.0.1 socket. /proc/net/tcp shows
|
|
45
|
+
// the address as little-endian hex.
|
|
46
|
+
const LINUX_LOOPBACK_HEX = "0100007F";
|
|
47
|
+
|
|
48
|
+
function linuxSystem(procRoot = "/proc") {
|
|
49
|
+
return {
|
|
50
|
+
parentOf(pid) {
|
|
51
|
+
const stat = fs.readFileSync(`${procRoot}/${pid}/stat`, "utf8");
|
|
52
|
+
// "pid (comm) state ppid ..." — comm may hold spaces and parentheses.
|
|
53
|
+
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
54
|
+
const ppid = Number(fields[1]);
|
|
55
|
+
return Number.isInteger(ppid) ? ppid : null;
|
|
56
|
+
},
|
|
57
|
+
// The pid in `pids` that owns the 127.0.0.1:port listening socket, else null.
|
|
58
|
+
listenerAmong(port, pids) {
|
|
59
|
+
const inodes = new Set();
|
|
60
|
+
const text = fs.readFileSync(`${procRoot}/net/tcp`, "utf8");
|
|
61
|
+
for (const line of text.split("\n").slice(1)) {
|
|
62
|
+
const cols = line.trim().split(/\s+/);
|
|
63
|
+
if (cols.length < 10 || cols[3] !== "0A") continue; // 0A = LISTEN
|
|
64
|
+
const [addr, portHex] = cols[1].split(":");
|
|
65
|
+
if (addr === LINUX_LOOPBACK_HEX && parseInt(portHex, 16) === port) inodes.add(cols[9]);
|
|
66
|
+
}
|
|
67
|
+
if (!inodes.size) return null;
|
|
68
|
+
for (const pid of pids) {
|
|
69
|
+
let fds = [];
|
|
70
|
+
try { fds = fs.readdirSync(`${procRoot}/${pid}/fd`); } catch (_) { continue; }
|
|
71
|
+
for (const fd of fds) {
|
|
72
|
+
let link = "";
|
|
73
|
+
try { link = fs.readlinkSync(`${procRoot}/${pid}/fd/${fd}`); } catch (_) { continue; }
|
|
74
|
+
const match = /^socket:\[(\d+)\]$/.exec(link);
|
|
75
|
+
if (match && inodes.has(match[1])) return pid;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
},
|
|
80
|
+
startEnvHas(pid, key, value) {
|
|
81
|
+
const raw = fs.readFileSync(`${procRoot}/${pid}/environ`, "utf8");
|
|
82
|
+
return raw.split("\0").includes(`${key}=${value}`);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── macOS: /bin/ps + /usr/sbin/lsof ─────────────────────────────────────────
|
|
88
|
+
function run(file, args) {
|
|
89
|
+
return execFileSync(file, args, {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
92
|
+
timeout: EXEC_TIMEOUT_MS,
|
|
93
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
94
|
+
env: { PATH: "/usr/bin:/bin:/usr/sbin:/sbin", LANG: "C", LC_ALL: "C" },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function darwinSystem() {
|
|
99
|
+
let parents = null;
|
|
100
|
+
return {
|
|
101
|
+
parentOf(pid) {
|
|
102
|
+
if (!parents) {
|
|
103
|
+
parents = new Map();
|
|
104
|
+
for (const line of run(PS, ["-Ao", "pid=,ppid="]).split("\n")) {
|
|
105
|
+
const [p, pp] = line.trim().split(/\s+/).map(Number);
|
|
106
|
+
if (Number.isInteger(p) && Number.isInteger(pp)) parents.set(p, pp);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return parents.has(pid) ? parents.get(pid) : null;
|
|
110
|
+
},
|
|
111
|
+
listenerAmong(port, pids) {
|
|
112
|
+
let out = "";
|
|
113
|
+
try { out = run(LSOF, ["-nP", "-a", "-p", pids.join(","), `-iTCP@127.0.0.1:${port}`, "-sTCP:LISTEN", "-Fpn"]); }
|
|
114
|
+
catch (error) {
|
|
115
|
+
// Exit 1 whenever some listed pid matched nothing (or is another
|
|
116
|
+
// user's); what did match is still on stdout.
|
|
117
|
+
if (!error || error.status !== 1) throw error;
|
|
118
|
+
out = String(error.stdout || "");
|
|
119
|
+
}
|
|
120
|
+
const owners = new Set();
|
|
121
|
+
let current = null;
|
|
122
|
+
for (const line of out.split("\n")) {
|
|
123
|
+
if (line.startsWith("p")) current = Number(line.slice(1));
|
|
124
|
+
else if (line === `n127.0.0.1:${port}` && current != null) owners.add(current);
|
|
125
|
+
}
|
|
126
|
+
for (const pid of pids) if (owners.has(pid)) return pid;
|
|
127
|
+
return null;
|
|
128
|
+
},
|
|
129
|
+
// ps -E prints the command line followed by the environment the process
|
|
130
|
+
// started with; the environment is what follows the plain command line.
|
|
131
|
+
startEnvHas(pid, key, value) {
|
|
132
|
+
const withEnv = run(PS, ["-E", "-ww", "-o", "command=", "-p", String(pid)]).replace(/\n+$/, "");
|
|
133
|
+
const bare = run(PS, ["-ww", "-o", "command=", "-p", String(pid)]).replace(/\n+$/, "");
|
|
134
|
+
if (!withEnv.startsWith(bare)) return false;
|
|
135
|
+
return withEnv.slice(bare.length).split(" ").includes(`${key}=${value}`);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function currentSystem() {
|
|
141
|
+
if (process.platform === "linux") return linuxSystem();
|
|
142
|
+
if (process.platform === "darwin") return darwinSystem();
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function ancestry(pid, system) {
|
|
147
|
+
const chain = [pid];
|
|
148
|
+
let current = pid;
|
|
149
|
+
while (chain.length < MAX_DEPTH) {
|
|
150
|
+
const parent = system.parentOf(current);
|
|
151
|
+
if (!Number.isInteger(parent) || parent <= 0 || chain.includes(parent)) break;
|
|
152
|
+
chain.push(parent);
|
|
153
|
+
current = parent;
|
|
154
|
+
}
|
|
155
|
+
return chain;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function refuse(reason) {
|
|
159
|
+
return { ok: false, reason };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// { ok: true, url, port, listenerPid } when `url` may be believed about who
|
|
163
|
+
// this process acts for; { ok: false, reason } otherwise.
|
|
164
|
+
function verifyEndpoint(url, { pid = process.pid, system = currentSystem() } = {}) {
|
|
165
|
+
const port = parsePort(url);
|
|
166
|
+
if (!port) return refuse("the loopback address is not a 127.0.0.1 endpoint");
|
|
167
|
+
if (!system) return refuse(`cannot inspect processes on ${process.platform}`);
|
|
168
|
+
let chain;
|
|
169
|
+
let listener;
|
|
170
|
+
try {
|
|
171
|
+
chain = ancestry(pid, system);
|
|
172
|
+
listener = system.listenerAmong(port, chain);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return refuse(`cannot read the process tree (${error && error.message ? error.message : error})`);
|
|
175
|
+
}
|
|
176
|
+
if (listener == null) return refuse("the loopback endpoint is not served by this process or an ancestor");
|
|
177
|
+
for (const above of chain.slice(chain.indexOf(listener) + 1)) {
|
|
178
|
+
let guarded = false;
|
|
179
|
+
// Unreadable = another user's process, never a guarded provider (the bot
|
|
180
|
+
// starts providers as its own user).
|
|
181
|
+
try { guarded = system.startEnvHas(above, "OC_GUARDED_RUN", "1") === true; } catch (_) { guarded = false; }
|
|
182
|
+
if (guarded) return refuse("the loopback endpoint was started inside a guarded (non-owner) run");
|
|
183
|
+
}
|
|
184
|
+
return { ok: true, url: `http://127.0.0.1:${port}`, port, listenerPid: listener };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const verified = new Map();
|
|
188
|
+
function verifyEndpointCached(url) {
|
|
189
|
+
const key = String(url || "");
|
|
190
|
+
if (!verified.has(key)) verified.set(key, verifyEndpoint(url));
|
|
191
|
+
return verified.get(key);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { verifyEndpoint, verifyEndpointCached, parsePort, linuxSystem, darwinSystem, ancestry };
|
package/core/runner.js
CHANGED
|
@@ -1771,11 +1771,13 @@ async function deliverForegroundResult(result, runContext, state = currentState(
|
|
|
1771
1771
|
return { ok: true, chunks: chunks.length };
|
|
1772
1772
|
}
|
|
1773
1773
|
|
|
1774
|
-
// The principal a run acts for. With no chat origin (bot-internal work)
|
|
1775
|
-
// is the owner. With a chat origin it is the speaker the relationship
|
|
1776
|
-
// resolves from the inbound envelope — also for a compaction stage, which
|
|
1777
|
-
// summarises that speaker's conversation; if the speaker cannot be resolved
|
|
1778
|
-
// the
|
|
1774
|
+
// The principal a run acts for. With no chat origin at all (bot-internal work)
|
|
1775
|
+
// that is the owner. With a chat origin it is the speaker the relationship
|
|
1776
|
+
// layer resolves from the inbound envelope — also for a compaction stage, which
|
|
1777
|
+
// summarises that speaker's conversation; if the speaker cannot be resolved,
|
|
1778
|
+
// or the origin names someone but not the transport and room it came from, the
|
|
1779
|
+
// run is treated as a non-owner (fail closed).
|
|
1780
|
+
const ORIGIN_FIELDS = Object.freeze(["adapterId", "transport", "channelId", "userId", "canonicalUserId"]);
|
|
1779
1781
|
function runPrincipal(runContext) {
|
|
1780
1782
|
const origin = runContext.origin || {};
|
|
1781
1783
|
const channelId = runContext.channelId != null ? String(runContext.channelId) : origin.channelId;
|
|
@@ -1788,7 +1790,10 @@ function runPrincipal(runContext) {
|
|
|
1788
1790
|
channelId: channelId || null,
|
|
1789
1791
|
userId: origin.userId || null,
|
|
1790
1792
|
};
|
|
1791
|
-
if (!base.adapterType || !base.channelId)
|
|
1793
|
+
if (!base.adapterType || !base.channelId) {
|
|
1794
|
+
const partial = ORIGIN_FIELDS.some((key) => origin[key] != null && origin[key] !== "");
|
|
1795
|
+
return { ...base, owner: !partial };
|
|
1796
|
+
}
|
|
1792
1797
|
let speaker = null;
|
|
1793
1798
|
try { speaker = require("./relationship").speakerFor(base.adapterType, base.channelId, base.userId); }
|
|
1794
1799
|
catch (error) { speaker = null; }
|
package/core/scheduler.js
CHANGED
|
@@ -74,15 +74,17 @@ function uniqueId(prefix = "occ") {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// ── watch-check environment ────────────────────────────────────────────────
|
|
77
|
-
// A watch's `check` runs a shell command
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
77
|
+
// A watch's `check` runs a shell command in the bot's process tree. Whose
|
|
78
|
+
// environment it gets depends on who set the watch:
|
|
79
|
+
// • a staff member's check gets an ALLOWLIST — the shell essentials and the
|
|
80
|
+
// Open Claudia config dir, nothing else — plus OC_GUARDED_RUN=1 so hooks
|
|
81
|
+
// and CLIs below it know they run for a non-owner. A denylist was not
|
|
82
|
+
// enough: any credential whose name matched no pattern (DATABASE_URL,
|
|
83
|
+
// MONGO_URI, …) reached a staff check (Phase 0 review, MEDIUM);
|
|
84
|
+
// • the owner's check keeps the bot's environment as it always had, so an
|
|
85
|
+
// owner watch that reads a credential from env keeps working.
|
|
86
|
+
// Either way a short-lived, creator-scoped run is stamped on top (below) so
|
|
87
|
+
// the check's reusable tools attribute to whoever created the watch.
|
|
86
88
|
const WATCH_CHECK_ENV_KEEP = new Set([
|
|
87
89
|
"PATH", "HOME", "USER", "LOGNAME", "SHELL",
|
|
88
90
|
"LANG", "LANGUAGE", "LC_ALL", "LC_CTYPE", "TZ",
|
|
@@ -91,31 +93,19 @@ const WATCH_CHECK_ENV_KEEP = new Set([
|
|
|
91
93
|
"OPEN_CLAUDIA_CONFIG_DIR",
|
|
92
94
|
"SystemRoot", "ComSpec", "PATHEXT", "WINDIR",
|
|
93
95
|
]);
|
|
94
|
-
|
|
95
|
-
let KNOWN_SECRET_ENV_KEYS = null;
|
|
96
|
-
function knownSecretEnvKeys() {
|
|
97
|
-
if (KNOWN_SECRET_ENV_KEYS) return KNOWN_SECRET_ENV_KEYS;
|
|
98
|
-
const set = new Set();
|
|
99
|
-
try { for (const key of require("./providers/env").ALWAYS_STRIPPED_KEYS) set.add(key); } catch (_) {}
|
|
100
|
-
KNOWN_SECRET_ENV_KEYS = set;
|
|
101
|
-
return set;
|
|
102
|
-
}
|
|
103
|
-
function isSecretCheckEnvKey(key) {
|
|
104
|
-
if (WATCH_CHECK_ENV_KEEP.has(key)) return false;
|
|
105
|
-
if (knownSecretEnvKeys().has(key)) return true;
|
|
106
|
-
return WATCH_CHECK_SECRET_PATTERN.test(key);
|
|
107
|
-
}
|
|
108
|
-
function buildWatchCheckEnv(source, stamp = {}) {
|
|
96
|
+
function buildWatchCheckEnv(source, stamp = {}, { owner = false } = {}) {
|
|
109
97
|
const env = {};
|
|
110
98
|
for (const [key, value] of Object.entries(source || {})) {
|
|
111
99
|
if (value == null) continue;
|
|
112
|
-
if (
|
|
100
|
+
if (!owner && !WATCH_CHECK_ENV_KEEP.has(key)) continue;
|
|
113
101
|
env[key] = String(value);
|
|
114
102
|
}
|
|
115
103
|
for (const [key, value] of Object.entries(stamp)) {
|
|
116
104
|
if (value == null || value === "") continue;
|
|
117
105
|
env[key] = String(value);
|
|
118
106
|
}
|
|
107
|
+
if (owner) delete env.OC_GUARDED_RUN;
|
|
108
|
+
else env.OC_GUARDED_RUN = "1";
|
|
119
109
|
return env;
|
|
120
110
|
}
|
|
121
111
|
|
|
@@ -248,7 +238,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
248
238
|
}
|
|
249
239
|
// Poll a watch's `check`: exit 0 → the watched condition is met, wake the
|
|
250
240
|
// agent; non-zero → still idle, skip silently. Runs in the job's project
|
|
251
|
-
// dir with
|
|
241
|
+
// dir with its creator's env (buildWatchCheckEnv: an allowlist for staff)
|
|
252
242
|
// and a short-lived run is registered for the job's creator so reusable
|
|
253
243
|
// tools attribute correctly — output discarded (exit code is the contract),
|
|
254
244
|
// hard-capped by WATCH_CHECK_TIMEOUT_MS. Injectable so tests stay hermetic.
|
|
@@ -256,13 +246,14 @@ function createSchedulerService(dependencies = {}) {
|
|
|
256
246
|
const cwd = (job && projectDescriptor(job).dir) || workspace || process.cwd();
|
|
257
247
|
const attest = require("./speaker-attest");
|
|
258
248
|
const checkRunId = uniqueId("watch-check");
|
|
249
|
+
const principal = watchCheckPrincipal(job);
|
|
259
250
|
let runAttest = "";
|
|
260
|
-
try { runAttest = attest.registerRun(checkRunId,
|
|
251
|
+
try { runAttest = attest.registerRun(checkRunId, principal); } catch (_) { runAttest = ""; }
|
|
261
252
|
let lb = null;
|
|
262
253
|
try { lb = require("./loopback").info(); } catch (_) { lb = null; }
|
|
263
254
|
const stamp = { OC_RUN_ID: checkRunId, OC_RUN_ATTEST: runAttest };
|
|
264
255
|
if (lb && lb.url) { stamp.OC_SEND_URL = lb.url; stamp.OC_SEND_TOKEN = lb.token; }
|
|
265
|
-
const env = buildWatchCheckEnv(process.env, stamp);
|
|
256
|
+
const env = buildWatchCheckEnv(process.env, stamp, { owner: principal.owner === true });
|
|
266
257
|
let settled = false;
|
|
267
258
|
const finish = (code, timedOut) => {
|
|
268
259
|
if (settled) return;
|
|
@@ -1080,7 +1071,6 @@ module.exports = {
|
|
|
1080
1071
|
parseFallbacks,
|
|
1081
1072
|
terminalRunSucceeded,
|
|
1082
1073
|
buildWatchCheckEnv,
|
|
1083
|
-
isSecretCheckEnvKey,
|
|
1084
1074
|
watchCheckPrincipal,
|
|
1085
1075
|
setAdapters,
|
|
1086
1076
|
initScheduler,
|
package/core/tool-guard.js
CHANGED
|
@@ -606,6 +606,19 @@ function mentionsCredentialPath(cmd) {
|
|
|
606
606
|
});
|
|
607
607
|
}
|
|
608
608
|
|
|
609
|
+
// Words a guarded turn's command may not contain at all (Phase 0 review,
|
|
610
|
+
// HIGH): the path-anchored rules above only see one spelling of a read, so the
|
|
611
|
+
// names themselves are refused — process environments and argument blocks, the
|
|
612
|
+
// keyring, the bot's own config dir, and the credential stores above as path
|
|
613
|
+
// components — and so is any per-process /proc path, as the read gate already
|
|
614
|
+
// refuses /proc to Read/Grep/Glob. This narrows the shell; it is not a
|
|
615
|
+
// boundary. Phase 2 takes the shell away from staff turns.
|
|
616
|
+
const SECRET_WORD_RE = /\b(?:environ|procargs\d*|keyring)\b/i;
|
|
617
|
+
const PROC_PROCESS_RE = /\/proc\/(?:\d|self\b|thread-self\b|[$*?[{])/;
|
|
618
|
+
const SECRET_COMPONENT_RE = new RegExp(
|
|
619
|
+
`(?:^|[\\s/'"=:(\`])(?:\\.open|${HOME_SECRET_PATHS.map((rel) => rel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?![A-Za-z0-9_-])`,
|
|
620
|
+
);
|
|
621
|
+
|
|
609
622
|
// Pure: null, or the id of the secret-read pattern the command matches.
|
|
610
623
|
function secretReadRule(command) {
|
|
611
624
|
const cmd = String(command || "");
|
|
@@ -614,6 +627,8 @@ function secretReadRule(command) {
|
|
|
614
627
|
if (psShowsEnvironment(cmd)) return "ps-environment";
|
|
615
628
|
if (KEYCHAIN_RE.test(cmd)) return "os-keychain";
|
|
616
629
|
if (mentionsCredentialPath(cmd)) return "credential-store";
|
|
630
|
+
if (PROC_PROCESS_RE.test(cmd)) return "proc-process";
|
|
631
|
+
if (SECRET_WORD_RE.test(cmd) || SECRET_COMPONENT_RE.test(cmd)) return "secret-word";
|
|
617
632
|
return null;
|
|
618
633
|
}
|
|
619
634
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.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-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
|
|
14
|
-
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-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-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-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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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"
|
|
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-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-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-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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -514,7 +514,12 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
514
514
|
// setter's level; notes/tasks owner-only and search stays in the room; reads
|
|
515
515
|
// screened and the hook fails closed on staff turns; no standing destructive
|
|
516
516
|
// or anonymous grants; canary release tags. Shipped first as canary-v3.5.0.
|
|
517
|
-
|
|
517
|
+
// 3.5.1 under the same approval (Sumeet 2026-09-25 14:43 EAT, phase 0 hardening
|
|
518
|
+
// from the adversarial review before any fleet roll): a CLI believes only its
|
|
519
|
+
// own bot (ancestor listener, not started in a staff turn); staff watch checks
|
|
520
|
+
// get an allowlist env and owner checks the bot's env again; a partial chat
|
|
521
|
+
// origin is a non-owner; wider staff shell screen, documented as no boundary.
|
|
522
|
+
assert.strictEqual(pkg.version, "3.5.1", "release version must remain unchanged without explicit approval");
|
|
518
523
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
519
524
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
520
525
|
}
|
|
@@ -459,7 +459,9 @@ function runProviderChild(kind) {
|
|
|
459
459
|
function runSupportSuites(provider) {
|
|
460
460
|
assert.ok(["claude", "codex"].includes(provider));
|
|
461
461
|
const passed = new Set();
|
|
462
|
-
|
|
462
|
+
// 180 s per suite by default: on a loaded CI runner a suite that takes 20 s
|
|
463
|
+
// locally overran 90 s (canary-v3.5.0 build job 44463; the retry passed).
|
|
464
|
+
for (const [key, [script, marker, suiteTimeoutMs = 180000]] of Object.entries(SUPPORT_SUITES)) {
|
|
463
465
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), `open-claudia-parity-suite-${key}-`));
|
|
464
466
|
try {
|
|
465
467
|
fs.mkdirSync(path.join(root, "home"));
|
|
@@ -471,7 +473,11 @@ function runSupportSuites(provider) {
|
|
|
471
473
|
timeout: suiteTimeoutMs,
|
|
472
474
|
maxBuffer: 8 * 1024 * 1024,
|
|
473
475
|
});
|
|
474
|
-
|
|
476
|
+
if (result.error) {
|
|
477
|
+
// Name the suite: a bare "spawnSync ETIMEDOUT" does not say which one.
|
|
478
|
+
const tail = `${result.stdout || ""}\n${result.stderr || ""}`.trim().split(/\r?\n/).slice(-20).join("\n");
|
|
479
|
+
assert.fail(`${key} (${script}) did not finish: ${result.error.code || result.error.message} after ${suiteTimeoutMs} ms\n${tail}`);
|
|
480
|
+
}
|
|
475
481
|
assert.strictEqual(result.status, 0, `${script}\n${result.stderr || result.stdout}`);
|
|
476
482
|
assert.ok(`${result.stdout}\n${result.stderr}`.includes(marker), `${script}: missing ${marker}`);
|
|
477
483
|
passed.add(key);
|