@inetafrica/open-claudia 3.1.23 → 3.1.25
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 -0
- package/bin/cli.js +2 -2
- package/bin/keyring.js +19 -0
- package/bin/recall.js +9 -1
- package/bin/schedule.js +4 -3
- package/core/actions.js +140 -15
- package/core/context.js +22 -2
- package/core/dream.js +23 -3
- package/core/enforcer.js +184 -72
- package/core/entities.js +5 -1
- package/core/handlers.js +16 -6
- package/core/mandates.js +160 -0
- package/core/pack-review.js +8 -6
- package/core/providers/claude-hook.js +10 -10
- package/core/providers/codex-hook.js +5 -1
- package/core/recall/discoverer.js +125 -15
- package/core/recall/query.js +27 -2
- package/core/recall/tuning.js +5 -0
- package/core/relationship.js +4 -1
- package/core/router.js +51 -13
- package/core/scheduler.js +62 -2
- package/core/tool-guard.js +100 -1
- package/docs/permissions-audit-2026-07.md +324 -0
- package/package.json +4 -2
- package/test-approval-cards.js +184 -0
- package/test-enforcer.js +137 -0
- package/test-external-rule-scopes.js +8 -0
- package/test-fixtures/fake-agent-cli.js +1 -1
- package/test-mandate-hygiene.js +147 -0
- package/test-persona-pipeline.js +22 -8
- package/test-provider-dream.js +14 -2
- package/test-provider-enforcer.js +5 -1
- package/test-provider-gateway.js +16 -0
- package/test-provider-language.js +32 -1
- package/test-provider-pack-review.js +12 -1
- package/test-provider-scheduler.js +55 -22
- package/test-provider-tool-hooks.js +4 -0
- package/test-tool-guard.js +118 -0
package/core/scheduler.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
// Provider-aware durable scheduler.
|
|
2
|
-
//
|
|
1
|
+
// Provider-aware durable scheduler. Each occurrence runs in the user/project/
|
|
2
|
+
// channel it was created for, but its PROVIDER and MODEL track the owner's
|
|
3
|
+
// CURRENT selection at fire time — a wakeup pinned to a since-changed (or
|
|
4
|
+
// quota-exhausted) model must not keep firing that model. The origin provider
|
|
5
|
+
// and session are still recorded and are resumed only when the current provider
|
|
6
|
+
// still matches the origin; switching providers can't resume the other
|
|
7
|
+
// provider's native session, so that case starts a fresh thread.
|
|
3
8
|
// One-shots are removed only after a terminal successful RunResult and a
|
|
4
9
|
// durable success marker; failures retain the same occurrence ID for bounded
|
|
5
10
|
// retry and eventually become explicit dead letters.
|
|
@@ -194,7 +199,50 @@ function createSchedulerService(dependencies = {}) {
|
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
|
|
202
|
+
// Current provider settings (model/effort/…) without throwing or mutating
|
|
203
|
+
// caller state past what getProviderSettings already does; cloned so the
|
|
204
|
+
// target owns its copy.
|
|
205
|
+
function currentProviderSettings(state, provider) {
|
|
206
|
+
try { return structuredClone(getProviderSettings(state, provider) || {}); }
|
|
207
|
+
catch (_) { return {}; }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Resolve which provider + model actually runs this occurrence. It tracks the
|
|
211
|
+
// owner's CURRENT selection rather than the tuple pinned at creation:
|
|
212
|
+
// 1. Current provider (if runnable): run under it with its current settings.
|
|
213
|
+
// Same provider as the job's origin → resume the recorded session (only
|
|
214
|
+
// the model may have changed). Different provider → fresh thread, since
|
|
215
|
+
// the origin provider's native session can't cross providers.
|
|
216
|
+
// 2. No runnable current selection → honour the job's origin provider if it
|
|
217
|
+
// is still runnable, resuming its session (prior behaviour preserved).
|
|
218
|
+
// 3. Otherwise an explicitly configured fallback provider, fresh.
|
|
197
219
|
function executionTarget(job, state) {
|
|
220
|
+
const currentProvider = getActiveProvider(state);
|
|
221
|
+
if (currentProvider && providerIsRunnable(providerStatusSafe(currentProvider))) {
|
|
222
|
+
const settings = currentProviderSettings(state, currentProvider);
|
|
223
|
+
if (currentProvider === job.provider) {
|
|
224
|
+
const lineage = resolveCompactionLineage(state, job);
|
|
225
|
+
return {
|
|
226
|
+
provider: currentProvider,
|
|
227
|
+
providerSettings: settings,
|
|
228
|
+
sessionId: lineage.sessionId,
|
|
229
|
+
attachSession: lineage.attachSession,
|
|
230
|
+
fallbackFrom: null,
|
|
231
|
+
switchedFrom: null,
|
|
232
|
+
fresh: !lineage.sessionId,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
provider: currentProvider,
|
|
237
|
+
providerSettings: settings,
|
|
238
|
+
sessionId: null,
|
|
239
|
+
attachSession: false,
|
|
240
|
+
fallbackFrom: null,
|
|
241
|
+
switchedFrom: job.provider,
|
|
242
|
+
fresh: true,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
198
246
|
if (providerIsRunnable(providerStatusSafe(job.provider))) {
|
|
199
247
|
const lineage = resolveCompactionLineage(state, job);
|
|
200
248
|
return {
|
|
@@ -203,6 +251,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
203
251
|
sessionId: lineage.sessionId,
|
|
204
252
|
attachSession: lineage.attachSession,
|
|
205
253
|
fallbackFrom: null,
|
|
254
|
+
switchedFrom: null,
|
|
206
255
|
fresh: !lineage.sessionId,
|
|
207
256
|
};
|
|
208
257
|
}
|
|
@@ -215,6 +264,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
215
264
|
sessionId: null,
|
|
216
265
|
attachSession: false,
|
|
217
266
|
fallbackFrom: job.provider,
|
|
267
|
+
switchedFrom: null,
|
|
218
268
|
fresh: true,
|
|
219
269
|
};
|
|
220
270
|
}
|
|
@@ -231,6 +281,13 @@ function createSchedulerService(dependencies = {}) {
|
|
|
231
281
|
job.prompt,
|
|
232
282
|
].join("\n");
|
|
233
283
|
}
|
|
284
|
+
if (target.switchedFrom) {
|
|
285
|
+
return [
|
|
286
|
+
`[${header} "${job.label}" fired at ${firedAt}; occurrence ${job.occurrenceId}]`,
|
|
287
|
+
`[Provider now tracks your current selection (${target.provider}); this was scheduled under ${target.switchedFrom}, whose native session cannot cross providers, so this run starts a fresh thread.]`,
|
|
288
|
+
job.prompt,
|
|
289
|
+
].join("\n");
|
|
290
|
+
}
|
|
234
291
|
return `[${header} "${job.label}" fired at ${firedAt}; occurrence ${job.occurrenceId}]\n${job.prompt}`;
|
|
235
292
|
}
|
|
236
293
|
|
|
@@ -391,6 +448,9 @@ function createSchedulerService(dependencies = {}) {
|
|
|
391
448
|
// shared session. Matches captured project.name (projectDescriptor(job).name)
|
|
392
449
|
// so sameCapturedProject / getProjectKey resolve the right thread mid-run.
|
|
393
450
|
conversationKey: projectDescriptor(job).name,
|
|
451
|
+
// Rides only on triage-approval wakeups; the enforcer's reply vet honours
|
|
452
|
+
// the owner's one-off "handle this request" grant through it.
|
|
453
|
+
triageApprovalId: job.triageApprovalId || "",
|
|
394
454
|
};
|
|
395
455
|
|
|
396
456
|
return runInChat(chat, async () => {
|
package/core/tool-guard.js
CHANGED
|
@@ -233,6 +233,82 @@ function readGuardEvents(sinceTs) {
|
|
|
233
233
|
return out;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// ── guarded-speaker file-write gate ────────────────────────────────────────
|
|
237
|
+
// Provider file-mutation tools (Write/Edit/NotebookEdit) are denied outright on
|
|
238
|
+
// guarded EXTERNAL turns. The guard system prompt already says externals get
|
|
239
|
+
// answers, not edits — this makes it fixed code behaviour instead of guidance.
|
|
240
|
+
// Channel identity comes from the OC_CHANNEL_* env the runner stamps on every
|
|
241
|
+
// provider spawn; without it (owner-local CLI, tests, cron) the gate is
|
|
242
|
+
// dormant, and the owner resolves unguarded via the same speakerFor call
|
|
243
|
+
// bin/tool.js uses — so owner paths stay byte-for-byte unchanged.
|
|
244
|
+
//
|
|
245
|
+
// Fail direction: UNLIKE the deny-gate's fail-open doctrine, a resolution
|
|
246
|
+
// error here stays guarded (deny). This branch only activates when channel
|
|
247
|
+
// context is present, and speakerFor itself already resolves internal failures
|
|
248
|
+
// as external — never widen access on failure.
|
|
249
|
+
// apply_patch is Codex's native file-edit tool (its shell tool arrives as
|
|
250
|
+
// "Bash"); it carries the patch body in tool_input.command rather than a
|
|
251
|
+
// file_path, and the codex transport matcher forwards it here.
|
|
252
|
+
const FILE_MUTATION_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit", "apply_patch"]);
|
|
253
|
+
|
|
254
|
+
// Shared by the speaker-keyed gates: resolve the turn's speaker from the
|
|
255
|
+
// OC_CHANNEL_* env. null → no channel context (dormant) or an unguarded
|
|
256
|
+
// (owner) speaker; otherwise { name } for the guarded external. Resolution
|
|
257
|
+
// errors stay guarded (fail closed) — never widen access on failure.
|
|
258
|
+
function guardedSpeakerFromEnv() {
|
|
259
|
+
const adapter = process.env.OC_CHANNEL_ADAPTER;
|
|
260
|
+
const channelId = process.env.OC_CHANNEL_ID;
|
|
261
|
+
if (!adapter || !channelId) return null; // no channel context → ungated
|
|
262
|
+
let guarded = true;
|
|
263
|
+
let name = "";
|
|
264
|
+
try {
|
|
265
|
+
const relationship = require("./relationship");
|
|
266
|
+
const sp = relationship.speakerFor(adapter, channelId, process.env.OC_CHANNEL_USER_ID);
|
|
267
|
+
guarded = relationship.guardActive(sp);
|
|
268
|
+
name = (sp && (sp.name || sp.relationship)) || "";
|
|
269
|
+
} catch (e) { /* fail closed: stay guarded */ }
|
|
270
|
+
return guarded ? { name } : null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function externalFileMutationDenial(toolName) {
|
|
274
|
+
const speaker = guardedSpeakerFromEnv();
|
|
275
|
+
if (!speaker) return null;
|
|
276
|
+
return (
|
|
277
|
+
`⛔ External-speaker guard: ${toolName} is blocked on this turn — the speaker${speaker.name ? ` (${speaker.name})` : ""} is not the owner.\n` +
|
|
278
|
+
`Do not create or edit files for an external request. Describe what you would change instead, or run the change through a write-tier tool (open-claudia tool run …) so it escalates to the owner for approval.`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── guarded-speaker control-plane gate ─────────────────────────────────────
|
|
283
|
+
// On guarded EXTERNAL turns the agent's shell must not touch the bot's own
|
|
284
|
+
// control plane at all — the config dir (packs, tools, entity mandates,
|
|
285
|
+
// people.json, crons, lessons, approvals) or the installed package source. A
|
|
286
|
+
// write there is privilege escalation that OUTLIVES the turn (a planted cron,
|
|
287
|
+
// tool source, or mandate line later runs in owner context), and the F2
|
|
288
|
+
// Write/Edit denial could otherwise be routed around with one Bash heredoc.
|
|
289
|
+
// Reads are denied too: no legitimate external request needs the shell inside
|
|
290
|
+
// owner memory, and read-then-quote leaks policy into the vetted surface.
|
|
291
|
+
// Same fail directions as the file gate; deliberately outside relaxed
|
|
292
|
+
// tooling-mode. The authorised channels (tool run / keyring exec) stay exempt
|
|
293
|
+
// — bin/tool.js speaker-gates external runs itself.
|
|
294
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
295
|
+
|
|
296
|
+
function controlPlaneAnchors() {
|
|
297
|
+
return [CONFIG_DIR, PACKAGE_ROOT, ".open-claudia"];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function externalControlPlaneDenial(command) {
|
|
301
|
+
const cmd = String(command || "");
|
|
302
|
+
if (!cmd.trim() || isAuthorizedChannelCommand(cmd)) return null;
|
|
303
|
+
if (!controlPlaneAnchors().some((a) => a && cmd.includes(a))) return null;
|
|
304
|
+
const speaker = guardedSpeakerFromEnv();
|
|
305
|
+
if (!speaker) return null;
|
|
306
|
+
return (
|
|
307
|
+
`⛔ External-speaker guard: this command touches Open Claudia's own control plane (config, memory, tools, schedules) and the speaker${speaker.name ? ` (${speaker.name})` : ""} is not the owner.\n` +
|
|
308
|
+
`Answer from the conversation instead, or go through open-claudia CLI verbs — write-tier ones escalate to the owner for approval.`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
236
312
|
// Hook entry, pure enough to test: takes the PreToolUse stdin payload (object
|
|
237
313
|
// or JSON string), returns { exit, stderr }. exit 2 = deny (stderr is fed back
|
|
238
314
|
// to the model); exit 0 = allow. Anything unexpected → allow (fail-open).
|
|
@@ -244,8 +320,30 @@ function runHook(payload) {
|
|
|
244
320
|
return { exit: 0, stderr: "" };
|
|
245
321
|
}
|
|
246
322
|
try {
|
|
323
|
+
// File-mutation tools are gated by SPEAKER, not by command content — and
|
|
324
|
+
// deliberately outside the relaxed tooling-mode bypass below: tooling mode
|
|
325
|
+
// is an owner workflow preference, this is a security control.
|
|
326
|
+
if (data && FILE_MUTATION_TOOLS.has(data.tool_name)) {
|
|
327
|
+
const denial = externalFileMutationDenial(data.tool_name);
|
|
328
|
+
if (!denial) return { exit: 0, stderr: "" };
|
|
329
|
+
const input = data.tool_input || {};
|
|
330
|
+
logGuardEvent({
|
|
331
|
+
kind: "deny",
|
|
332
|
+
rule: "external-file-write",
|
|
333
|
+
tool: data.tool_name,
|
|
334
|
+
file: String(input.file_path || input.notebook_path || input.command || "").slice(0, 300),
|
|
335
|
+
});
|
|
336
|
+
return { exit: 2, stderr: denial };
|
|
337
|
+
}
|
|
247
338
|
if (!data || data.tool_name !== "Bash") return { exit: 0, stderr: "" };
|
|
248
339
|
const command = data.tool_input && data.tool_input.command;
|
|
340
|
+
// Control-plane references are speaker-gated ahead of the tooling-mode
|
|
341
|
+
// switch — a security control, not a workflow preference.
|
|
342
|
+
const cpDenial = externalControlPlaneDenial(command);
|
|
343
|
+
if (cpDenial) {
|
|
344
|
+
logGuardEvent({ kind: "deny", rule: "external-control-plane", command: String(command).slice(0, 300) });
|
|
345
|
+
return { exit: 2, stderr: cpDenial };
|
|
346
|
+
}
|
|
249
347
|
// Relaxed tooling mode: the gate observes but never blocks. The event is
|
|
250
348
|
// still logged (as relaxed-pass, not deny) so the dream KPI can report
|
|
251
349
|
// what WOULD have been denied — visibility survives the mode switch.
|
|
@@ -275,7 +373,8 @@ function ensureHookSettings() {
|
|
|
275
373
|
}
|
|
276
374
|
|
|
277
375
|
module.exports = {
|
|
278
|
-
RULES, RAW_OP_PATTERNS, GUARD_LOG, SETTINGS_FILE,
|
|
376
|
+
RULES, RAW_OP_PATTERNS, GUARD_LOG, SETTINGS_FILE, FILE_MUTATION_TOOLS,
|
|
279
377
|
isAuthorizedChannelCommand, matchRule, denyMessage, evaluateCommandPolicy, logGuardEvent, runHook, hookSettings, ensureHookSettings,
|
|
378
|
+
externalFileMutationDenial, externalControlPlaneDenial, guardedSpeakerFromEnv,
|
|
280
379
|
matchRawOp, noteRawOp, readGuardEvents,
|
|
281
380
|
};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Permissions system — production-readiness audit & redesign plan
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-24 · Auditor: Open Claudia (session-reviewed, all fixes test-pinned)
|
|
4
|
+
Scope: the external-speaker guardrail stack — relationship classification, the
|
|
5
|
+
three policy layers, the enforcer (reply/action/triage vets), approvals +
|
|
6
|
+
owner cards, the tool deny-gate, mandates, and identity/people records.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Verdict
|
|
11
|
+
|
|
12
|
+
**Conditionally production-ready.** The core is sound: default-deny
|
|
13
|
+
classification, an independent model judge that fails closed, owner-only
|
|
14
|
+
approval, and a hardcoded security floor no note can override. This pass
|
|
15
|
+
closed the five gaps that were outright exploitable or operationally broken
|
|
16
|
+
(F1–F6 below). What remains open is not exploitable-by-message but is
|
|
17
|
+
*organizational* debt that will bite as more people use the bot: fragmented
|
|
18
|
+
identities, no group layer, and always-allow grants that are broader than the
|
|
19
|
+
owner probably intends. Those are designed (§6) but deliberately NOT built
|
|
20
|
+
until the plan is approved.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. The system today (post-fix architecture)
|
|
25
|
+
|
|
26
|
+
Layers consulted for every guarded turn, narrowing in scope; union of grants,
|
|
27
|
+
any prohibition wins, default-deny:
|
|
28
|
+
|
|
29
|
+
1. **Security floor** — hardcoded in `GUARD_SYSTEM_PROMPT` (enforcer.js).
|
|
30
|
+
Credentials/secrets/infra access can never be granted by any layer below.
|
|
31
|
+
2. **Global guardrails** — owner-authored, applies to every external.
|
|
32
|
+
3. **Channel rules** — per group chat / task thread.
|
|
33
|
+
4. **Person mandate** — per-person grants on their entity pack (≤4000 chars,
|
|
34
|
+
deduped, slug-keyed; judge reads at most 6000 defensively).
|
|
35
|
+
5. **Turn grant** — one-off "handle this request" approval; reply vets only.
|
|
36
|
+
|
|
37
|
+
Enforcement points on a guarded (non-owner) turn:
|
|
38
|
+
|
|
39
|
+
- **Inbound triage** (router → `guardInboundTriage`): the request itself is
|
|
40
|
+
judged BEFORE any agent work. Held → owner card ("should I handle it?"),
|
|
41
|
+
external gets the holding line, no generation happens at all.
|
|
42
|
+
- **Provider file tools** (PreToolUse hook → tool-guard): Write/Edit/
|
|
43
|
+
MultiEdit/NotebookEdit are denied outright for external speakers, with a
|
|
44
|
+
redirect to write-tier tools (which escalate to the owner).
|
|
45
|
+
- **Bash deny-gate** (unchanged): prod surfaces require the covering
|
|
46
|
+
open-claudia tool; write/destructive tool runs escalate with exact-command
|
|
47
|
+
approval, one-shot consume, byte-bound.
|
|
48
|
+
- **Outbound reply vet** (`guardOutboundReply`): the authoritative gate on
|
|
49
|
+
anything the external actually receives. Triage is advisory; this is final.
|
|
50
|
+
|
|
51
|
+
Identity: `speakerFor` fails closed to "external"; owner status comes only
|
|
52
|
+
from `people.isOwner` via canonical identity (a note can never confer it).
|
|
53
|
+
Unknown authed speakers get an auto-contact (fresh record, hashed entity slug,
|
|
54
|
+
never name-linked). Meet-greet lane covers UNVERIFIED first contacts.
|
|
55
|
+
|
|
56
|
+
Fail direction map (deliberate asymmetries):
|
|
57
|
+
|
|
58
|
+
| Gate | On internal error | Why |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| speakerFor | closed (external) | never widen on failure |
|
|
61
|
+
| Enforcer verdict | closed (escalate), never cached, 1 bounded retry on transient | transport blips ≠ verdicts |
|
|
62
|
+
| Triage (guarded speaker) | closed (held) | reply vet still downstream |
|
|
63
|
+
| Triage (unresolvable speaker) | open (proceed) | owner UX; output vet authoritative |
|
|
64
|
+
| File-mutation gate, no channel env | dormant (allow) | owner-local turns untouched (R9/R14) |
|
|
65
|
+
| File-mutation gate, env + resolve error | closed (deny) | env present ⇒ real channel turn |
|
|
66
|
+
| Bash deny-gate | open (allow) | availability guard, not identity guard; logged |
|
|
67
|
+
| Turn grant validation | closed (no grant) | any mismatch/expiry/read error |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. Findings fixed in this pass
|
|
72
|
+
|
|
73
|
+
| ID | Sev | Finding | Fix (all uncommitted, test-pinned) |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| F1 | High (ops) | Guard model cold-starts overran the old timeout, failing closed on legitimate replies → spurious owner cards | 60s timeout (`ENFORCER_TIMEOUT_MS`) + exactly one retry on transient transport errors; non-transient never retried; persistent failure still closes |
|
|
76
|
+
| F2 | **Critical** | Only Bash was hooked: an external speaker could have the agent write/edit files directly via provider Write/Edit tools — bypassing the whole approval path | Second PreToolUse matcher `^(Write\|Edit\|MultiEdit\|NotebookEdit)$` → same deny-gate CLI → speaker-gated denial; deliberately outside relaxed tooling-mode (it's a security control, not a workflow preference); audited as `external-file-write` |
|
|
77
|
+
| F3 | Med (UX/authz clarity) | Approval cards led with the bot's output, not the person — the owner was approving text, unclear they were authorizing a PERSON's request | Cards reworded: "**X** asked me something — should I act on it?" / "wants me to handle a request — should I?" / "wants me to run a **write** action — should I?"; ask shown first |
|
|
78
|
+
| F4 | High | Work happened BEFORE any approval: the agent fully processed an external request, only the reply was vetted (cost, prompt-injection surface, side-effect risk) | Up-front triage gate in the router: inbound judged first; held turns generate nothing; approval re-runs enter via the scheduler so they can't re-triage into a loop |
|
|
79
|
+
| F5 | High (friction→risk) | Without a grant concept, every triage-approved request structurally produced a SECOND approval card for its reply — double-approval fatigue trains blanket-allow habits | Turn grant: the approval id rides the scheduler job → bot-process AsyncLocalStorage (agent subprocess can never forge it) → honored in reply vets ONLY, validated fail-closed (approved + triage-kind + adapter/channel match + <24h), quotes one request, never actions |
|
|
80
|
+
| F6 | High (latent bug) | `appendMandate` upserted by NAME: an always-allow for auto-contact `sharon-a1b2c3` could land on a different `sharon` entity — cross-person grant leakage; mandates also grew unbounded with duplicate lines | Slug-keyed upsert; whitespace-normalized dedup; 4000-char write cap (refused, never truncated — it's security text); 6000-char defensive read clip in the judge; same hygiene in the /guardrails person-rule path |
|
|
81
|
+
|
|
82
|
+
## 4. Known gaps — documented, not yet closed
|
|
83
|
+
|
|
84
|
+
| ID | Sev | Gap | Position |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| G1 | Med | Codex `apply_patch` file writes: hook matcher for Codex remains Bash-only; whether Codex file-mutation surfaces are covered is UNVERIFIED | Left unchanged rather than guessing hook names; verify against a live Codex fleet bot before relying on F2 parity there |
|
|
87
|
+
| G2 | Med | Media turns (voice/photo/document) from externals bypass inbound triage (text-only hook) | Output vet still covers everything they receive; close alongside G1 in a follow-up |
|
|
88
|
+
| G3 | ~~Low~~ CLOSED (P3) | ~~The `apr:` button handler (actions.js) has no test harness~~ Closed with P3: test-approval-cards.js pins the handler end-to-end (real approvals + entities + mandates, stubbed transport) — reply Always-allow delivery + scoped grant + ack ids, double-tap idempotence, deny hold, owner-only gating, triage approve wake job + deny decline, make-a-rule scope buttons, expired-card ack | Was: pre-existing posture; grant validation, card content, and record shapes were already pinned in test-enforcer.js |
|
|
89
|
+
| G4 | High (org) | Identity fragmentation: auto-contacts key on (adapter, userId) — the same human on Telegram + Kazee + Spaces mints separate records with separate mandates. An owner grant lands on one-third of a person (mechanism verified in code; live counts to be confirmed on the deployed store during the repair pass) | Redesign P1 |
|
|
90
|
+
| G5 | Med | No group layer: nothing between "this channel" and "this person" — can't say "the dev team may deploy to staging" | Redesign P2 |
|
|
91
|
+
| G6 | High (authz creep) | "Always allow" writes a verbatim-ask mandate line, forever, with no scope or expiry — over time a person accretes broad standing permissions the owner never reviewed as a set | Cap+dedup (F6) bound the bleeding; real fix is scoped grants, Redesign P3 |
|
|
92
|
+
|
|
93
|
+
## 5. Test coverage after this pass
|
|
94
|
+
|
|
95
|
+
All green locally (`npm test`; one known loaded-runner flake class in
|
|
96
|
+
test-utility-provider-policy re-verified 3× clean):
|
|
97
|
+
|
|
98
|
+
- **test-enforcer.js** (+137 lines): triage kind end-to-end (owner no-op,
|
|
99
|
+
allow, block→card with handle/decline buttons + pinned record, fail-closed),
|
|
100
|
+
turn grant (reply-only, quotes the ask, cache-key isolation, and rejection
|
|
101
|
+
of pending / wrong-channel / wrong-kind ids), retry semantics, fail-closed
|
|
102
|
+
+ never-cache, audit hygiene (no payloads/mandates in logs).
|
|
103
|
+
- **test-tool-guard.js** (+60): file gate dormant without channel env, denies
|
|
104
|
+
unknown-external Write/Edit/NotebookEdit, reads+benign Bash stay free,
|
|
105
|
+
owner turns untouched, audited rule, settings carry both matchers on one
|
|
106
|
+
hook command.
|
|
107
|
+
- **test-mandate-hygiene.js** (new): slug-keying (two Sharons never share),
|
|
108
|
+
dedup, cap-refusal leaves bytes identical, name-fallback for legacy records.
|
|
109
|
+
- **test-provider-gateway.js**: router consults triage with the routed text +
|
|
110
|
+
provider/project/dir pinning on every text turn.
|
|
111
|
+
- Plus the previously-passing enforcer/scheduler/hook/approval suites.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 6. Redesign plan (approval requested — nothing below is built)
|
|
116
|
+
|
|
117
|
+
### P1 — One person, many surfaces (identity unification)
|
|
118
|
+
|
|
119
|
+
Anchor: **people.id stays the canonical key; Kazee userId becomes the
|
|
120
|
+
preferred anchor handle** (it is unique across all Kazee tasks, so every task
|
|
121
|
+
thread resolves to the same person — no per-task duplicates).
|
|
122
|
+
|
|
123
|
+
- **Agent-proposed, owner-confirmed merges.** When a new identity appears (or
|
|
124
|
+
at repair time), the agent gathers signals — same display name, same email
|
|
125
|
+
in profile, cross-references in conversation ("this is Sharon from
|
|
126
|
+
accounts") — and posts a card: *"Is `telegram: Sharon N. (…111)` the same
|
|
127
|
+
person as `kazee: sharon@… `? [Same person] [Different] [Not sure]"*.
|
|
128
|
+
NEVER auto-merge, and never merge on display name alone (names are
|
|
129
|
+
spoofable — that invariant already exists in ensureContact and stays).
|
|
130
|
+
- **Merge = handles fold into one people record**; entities merge Mandate
|
|
131
|
+
(union, deduped, capped — over-cap requires owner trim), Notes/Log append.
|
|
132
|
+
The losing record is tombstoned (`mergedInto: <id>`) so old approval
|
|
133
|
+
records/audit lines still resolve. Merges are reversible from the tombstone.
|
|
134
|
+
- **Repair pass** for existing stores: one-time sweep proposes merge cards for
|
|
135
|
+
probable duplicates (e.g. the fragmented Sharon records on the deployed
|
|
136
|
+
bot); owner confirms each. Nothing merges silently.
|
|
137
|
+
- Guard changes: `speakerFor` unchanged (already person-keyed); the win is
|
|
138
|
+
that one mandate follows the human across surfaces.
|
|
139
|
+
|
|
140
|
+
### P2 — Groups as a policy layer
|
|
141
|
+
|
|
142
|
+
Precedence becomes: **floor > global > channel > group > person > turn-grant**
|
|
143
|
+
(same rule: grants union, any prohibition wins, floor absolute).
|
|
144
|
+
|
|
145
|
+
- A group is an owner-curated named set of person ids (`groups.json`), e.g.
|
|
146
|
+
`dev-team`, `family`. People can be in several.
|
|
147
|
+
- `/guardrails` gains group scope; the "Make a rule…" card offers *this
|
|
148
|
+
person / this channel / this group / everyone*.
|
|
149
|
+
- The enforcer prompt gains one GROUP RULES section (union of the speaker's
|
|
150
|
+
groups, clipped like mandates). Cache key includes it.
|
|
151
|
+
- No nesting, no inheritance between groups in v1 — flat sets only.
|
|
152
|
+
|
|
153
|
+
### P3 — Capability-scoped approvals (kills the blanket)
|
|
154
|
+
|
|
155
|
+
Replaces free-text "Always-allowed by owner: <verbatim ask>" with a
|
|
156
|
+
structured grant the owner explicitly shapes at approval time:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
- grant: {capability} · scope: {this-exact-thing | this-kind-of-thing}
|
|
160
|
+
· for: {person | group} · expires: {7d | 30d | never} · granted: 2026-07-24
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- The card's "Make a rule…" flow asks two more taps: *what* ("just this" vs
|
|
164
|
+
"requests like this — <agent-drafted capability line the owner can edit>")
|
|
165
|
+
and *how long* (7d / 30d / no expiry). Default is the NARROW option.
|
|
166
|
+
- Stored in the same Mandate section (human-readable lines, same cap), so the
|
|
167
|
+
judge consumes them without a format break; expired lines are skipped at
|
|
168
|
+
read time and swept by the nightly dream pass with a chat notice.
|
|
169
|
+
- `/guardrails` lists a person's active grants with per-line revoke — the
|
|
170
|
+
owner finally sees the accumulated permission set in one place.
|
|
171
|
+
- Rationale: an approval should carry WHO + WHAT KIND + HOW LONG, not become
|
|
172
|
+
an eternal verbatim precedent (this is the "blanket approve" concern raised
|
|
173
|
+
on the always-allow flow, solved at the model layer the judge already reads).
|
|
174
|
+
|
|
175
|
+
### P4 — Rollout order
|
|
176
|
+
|
|
177
|
+
1. **P3** first (smallest blast radius, immediate authz-creep fix; pure
|
|
178
|
+
card-flow + mandate-format change, judge prompt already compatible).
|
|
179
|
+
2. **P1** second (unification + repair pass; needs the merge card UI and the
|
|
180
|
+
tombstone plumbing; highest data-migration care — every step
|
|
181
|
+
owner-confirmed, reversible, backed up like dream merges).
|
|
182
|
+
3. **P2** last (new layer; cleanest once identities are unified so group
|
|
183
|
+
membership means one person, not one fragment).
|
|
184
|
+
4. Close G1/G2 (Codex file-tool verification, media-turn triage) alongside.
|
|
185
|
+
(G3, the `apr:` handler harness, shipped with P3 — see §4/§9.)
|
|
186
|
+
|
|
187
|
+
Risks: P1 merge cards interrupt the owner (batched, max a few per day);
|
|
188
|
+
P3 makes approvals two taps longer (only on "Make a rule", never on
|
|
189
|
+
approve-once); all three change nothing for the owner's own turns.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 7. Pass 2 — 360° dangling-pathway sweep (2026-07-24, post-go)
|
|
194
|
+
|
|
195
|
+
After the go, one full pass enumerating every EXISTING entry point that
|
|
196
|
+
reaches the same concerns as the new gates (the /intros lesson: a new system
|
|
197
|
+
must reconcile every old pathway, not just the one it was built on). Findings,
|
|
198
|
+
all fixed and test-pinned this pass:
|
|
199
|
+
|
|
200
|
+
- **D1 · CRITICAL — model output could write guard policy.** The per-turn
|
|
201
|
+
pack reviewer and the nightly dream both passed `relationship` + `mandate`
|
|
202
|
+
from model JSON into `upsertEntity`. Both run over mixed-provenance content
|
|
203
|
+
(including external chatter), so a prompt-injected turn could have granted
|
|
204
|
+
itself a mandate. Writers now strip both fields (owner actions remain the
|
|
205
|
+
only writers), prompts/schemas updated, attack fixtures added
|
|
206
|
+
(test-provider-pack-review, test-provider-dream). Introspection's
|
|
207
|
+
entity_edits applier verified notes-only.
|
|
208
|
+
- **D2 — media turns bypassed inbound triage (closes G2).** Voice, audio,
|
|
209
|
+
photo, and document handlers ran the agent without `guardInboundTriage`;
|
|
210
|
+
only text was triaged. All five inbound shapes now enter through one
|
|
211
|
+
`admitGuardedTurn` helper; media asks carry saved file paths so an
|
|
212
|
+
owner-approved re-run can still find them, and the resolved ask is
|
|
213
|
+
back-filled into the turn scope (`setCurrentInboundText`) so owner cards
|
|
214
|
+
and F5 turn-grant validation see the real request, not an empty string.
|
|
215
|
+
- **D3 — dream auto-merges could delete guard policy.** An entity merge
|
|
216
|
+
could remove a mandate-carrying entity or orphan a people-record
|
|
217
|
+
`entitySlug` pointer (silent speaker-resolution break). Merges touching
|
|
218
|
+
either are now skipped (`guardPinned`) — owner-confirmed merges only
|
|
219
|
+
(arrives with P1).
|
|
220
|
+
- **D4 — Bash was an open side door around F2/F6.** On a guarded turn one
|
|
221
|
+
heredoc could edit config, packs, crons, tool sources, or the installed
|
|
222
|
+
package. New speaker-gated control-plane gate in the PreToolUse hook:
|
|
223
|
+
any command referencing the config dir, package root, or `.open-claudia`
|
|
224
|
+
is denied for externals (reads too — read-then-quote leaks policy), ahead
|
|
225
|
+
of the tooling-mode switch, audited as `external-control-plane`.
|
|
226
|
+
Authorised channels stay exempt (bin/tool.js gates externals itself).
|
|
227
|
+
- **D5 — Codex file edits were unhooked (closes G1, live-probed).** Probed
|
|
228
|
+
real codex 0.144.0 with a log-everything hook: shell arrives as tool_name
|
|
229
|
+
`Bash`, file edits as `apply_patch` — and normal runs bypass the sandbox,
|
|
230
|
+
so the `^Bash$` matcher left externals able to write ANY file under Codex.
|
|
231
|
+
Matcher widened to `^(Bash|apply_patch)$`, `apply_patch` added to
|
|
232
|
+
FILE_MUTATION_TOOLS, fixture + pins updated.
|
|
233
|
+
- **D6 — two mandate writers, drift risk.** The always-allow grant writer
|
|
234
|
+
(actions.appendMandate) and the rule-card writer (router) each implemented
|
|
235
|
+
dedup/cap/slug-keying. Unified on one `appendMandateLine` so hygiene can't
|
|
236
|
+
drift.
|
|
237
|
+
- **D7 — keyring CLI was speaker-blind.** `keyring exec` runs an arbitrary
|
|
238
|
+
command with the FULL keyring in env, `get` prints raw values — and the
|
|
239
|
+
control-plane gate exempts `keyring exec` as an authorised channel, so on
|
|
240
|
+
an external turn it was a creds-plus-config-write side door around D4. The
|
|
241
|
+
keyring CLI now refuses guarded external speakers outright (every verb,
|
|
242
|
+
audited as `external-keyring`), dormant owner-local, denied on resolution
|
|
243
|
+
failure.
|
|
244
|
+
|
|
245
|
+
Verified-OK pathways (no change needed): scheduler re-runs stamp
|
|
246
|
+
`OC_CHANNEL_USER_ID` (file gate stays live on approved external re-runs);
|
|
247
|
+
Spaces turns flow through the common router (triage covered); sub-agents
|
|
248
|
+
spawn read-only and inherit channel env; codex transport confinement
|
|
249
|
+
(hooks on, plugins/unified_exec off, untrusted trust) is mandatory-or-refuse.
|
|
250
|
+
|
|
251
|
+
G3 closed with P3 (test-approval-cards.js pins the `apr:` handler). `tool run`
|
|
252
|
+
remains the one authorised channel reachable on external turns — by design:
|
|
253
|
+
bin/tool.js vets write/destructive tiers per speaker (mandate-allow or
|
|
254
|
+
owner escalation; vet logic pinned in test-enforcer.js /
|
|
255
|
+
test-provider-enforcer.js), and read-only verbs answer without side effects.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 8. Ship state
|
|
260
|
+
|
|
261
|
+
Go received 2026-07-24. All §3/§5 fixes plus the §7 sweep are committed and
|
|
262
|
+
pushed (v3.1.25, 3025f53); fleet roll/publish is confirmed separately
|
|
263
|
+
(restarting the bot kills the live session). §6 starts next: P3 → P1 → P2.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 9. P3 — capability-scoped approvals (2026-07-24)
|
|
268
|
+
|
|
269
|
+
The owner's driving concern, verbatim: "the approval all is a blanket for all
|
|
270
|
+
users or how can we get it to be for the user plus maybe what we want them to
|
|
271
|
+
be able to do as a guardrail vs a clanket approve everything going forward for
|
|
272
|
+
all or event that person." A mandate line is now a scoped, expiring, revocable
|
|
273
|
+
GRANT — not a permanent blanket.
|
|
274
|
+
|
|
275
|
+
New module `core/mandates.js`; the three policy layers now share one
|
|
276
|
+
lifecycle (id → list → revoke):
|
|
277
|
+
|
|
278
|
+
- **Ids.** Every mandate bullet has a stable 8-char id =
|
|
279
|
+
sha1(slug + normalized text) — slug-scoped, so the same capability granted
|
|
280
|
+
to two people revokes independently. Ids ignore case, whitespace, and the
|
|
281
|
+
expiry marker.
|
|
282
|
+
- **Expiry.** A trailing ` [until YYYY-MM-DD]` marker (UTC, inclusive).
|
|
283
|
+
Tap-grants ("Always allow" on an approval card) default to a bounded TTL —
|
|
284
|
+
`MANDATE_GRANT_TTL_DAYS`, 30 by default, 0 = permanent. Owner-authored rule
|
|
285
|
+
cards stay permanent unless the text ends "for Nd" / "until YYYY-MM-DD"
|
|
286
|
+
(`extractExpiry`). Filtering is deterministic and read-side:
|
|
287
|
+
`relationship.speakerFor` (the single choke point every consumer of
|
|
288
|
+
`speaker.mandate` goes through — enforcer, external-mode prompt, tool vets)
|
|
289
|
+
drops expired bullets via `activeMandateText`, so the judge never sees a
|
|
290
|
+
lapsed capability and never does date math. The enforcer verdict cache keys
|
|
291
|
+
on the filtered text, so expiry busts it naturally.
|
|
292
|
+
- **Refresh, not stacking.** `appendMandateLine` dedups on the
|
|
293
|
+
marker-stripped text: re-granting the same capability replaces the line
|
|
294
|
+
with the new expiry (`refreshed: true`). Successful writes also prune
|
|
295
|
+
expired bullets and collapse legacy duplicates; refused/duplicate writes
|
|
296
|
+
mutate nothing (cap-refusal pin unchanged). Non-bullet hand-written text
|
|
297
|
+
survives rewrites.
|
|
298
|
+
- **Revoke.** `/guardrails` now lists per-person grants (`👤 name (slug)`,
|
|
299
|
+
ids + until-dates) alongside global + channel rules; `remove|rm|revoke <id>`
|
|
300
|
+
resolves across all three layers (global → channel → person). Revoking the
|
|
301
|
+
last line empties the section via direct `writeEntity` (upsertEntity treats
|
|
302
|
+
"" as no-op). Approval-card acks quote the id + expiry and the revoke
|
|
303
|
+
command.
|
|
304
|
+
|
|
305
|
+
Pathways reconciled (the §7 discipline applied to the new system):
|
|
306
|
+
`bin/entity.js persona --mandate` is a deliberate owner-only WHOLESALE editor
|
|
307
|
+
(same class as hand-editing the entity file) — the parser tolerates whatever
|
|
308
|
+
lands there and the read filter still governs what the judge sees. Dream and
|
|
309
|
+
pack-review remain stripped of mandate writes (D1). All other writers funnel
|
|
310
|
+
through `appendMandateLine` (D6).
|
|
311
|
+
|
|
312
|
+
Pinned in test-mandate-hygiene.js (expiry marker, refresh, prune-on-write,
|
|
313
|
+
id scoping, extractExpiry, list/revoke, last-line clear) and
|
|
314
|
+
test-external-rule-scopes.js (speakerFor drops expired lines while the file
|
|
315
|
+
still holds them). G3 closes here too: test-approval-cards.js exercises the
|
|
316
|
+
`apr:` button handler end-to-end with the real approvals + entities +
|
|
317
|
+
mandates modules and stubbed transport — Always-allow delivers AND writes the
|
|
318
|
+
scoped grant (TTL marker, ack quotes id + revoke command), double-tap can't
|
|
319
|
+
flip a decision or duplicate a grant, deny holds with no delivery and no
|
|
320
|
+
mandate write, non-owner taps bounce, triage approve wakes the agent in the
|
|
321
|
+
origin channel with the approval id as a one-off turn grant (request framed
|
|
322
|
+
strictly as data), triage deny sends the polite decline without waking the
|
|
323
|
+
agent, "Make a rule" delivers and offers person/channel/global scope buttons,
|
|
324
|
+
and stale cards get a clean ack.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.25",
|
|
4
4
|
"description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
|
|
5
5
|
"main": "bot.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"setup": "node setup.js",
|
|
12
12
|
"start": "node bot.js",
|
|
13
13
|
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-kazee-call-session.js",
|
|
14
|
-
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-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-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js"
|
|
14
|
+
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -69,7 +69,9 @@
|
|
|
69
69
|
"test-recall-relationship-gate.js",
|
|
70
70
|
"test-relationship.js",
|
|
71
71
|
"test-enforcer.js",
|
|
72
|
+
"test-mandate-hygiene.js",
|
|
72
73
|
"test-external-rule-scopes.js",
|
|
74
|
+
"test-approval-cards.js",
|
|
73
75
|
"test-identity-prune.js",
|
|
74
76
|
"test-kazee-message-id.js",
|
|
75
77
|
"test-kazee-channel-health.js",
|