@inetafrica/open-claudia 3.1.0 → 3.1.2
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/README.md +3 -2
- package/bin/recall.js +68 -2
- package/core/actions.js +22 -0
- package/core/auth-flow.js +68 -0
- package/core/handlers.js +42 -1
- package/core/recall/discoverer.js +8 -9
- package/core/recall/query.js +242 -0
- package/package.json +3 -3
- package/test-provider-language.js +22 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.1.2 — On-demand recall query + cold-walker fallback fix
|
|
4
|
+
|
|
5
|
+
Two things fold in: a new `recall query` CLI so the working agent can ask long-term memory a question mid-task, and a latent reliability fix in the recall walker's fallback path.
|
|
6
|
+
|
|
7
|
+
- **New `open-claudia recall query "<question>"`** — the agent's side door into the seed → graph → walker pipeline. Where per-turn recall seeds on the *user's* message, this seeds the graph with the agent's OWN working context (via `--context`), walks the typed-edge graph with the **active-provider** judge, and returns the kept pack sections with why-bullets and pointers. It closes the documented cold-start hole: mid-build, when the vocabulary you're using shares no words with the pack that governs the work, ordinary recall never resurfaces it — an explicit query carrying your working context does. Read-only (no edge reinforcement), no pre-gate (an explicit ask always runs full), and fail-open (a failed judge returns the whole candidate list unjudged rather than nothing). Flags: `--context`, `--json`, `--no-walker`, `--no-episodes`, `--limit`, `--timeout`.
|
|
8
|
+
- **Cold-walker outputSchema 400 fix** — the recall walker prefers a warm persistent judge (Claude only) and falls back to a cold spawn otherwise. The cold path was passing an array-typed `outputSchema`, which the Anthropic API turns into a custom-tool `input_schema` and rejects with `400 input_schema.type: Input should be 'object'`. So any time the warm walker errored and fell back, recall silently 400'd and failed-open — judgement quietly disabled on the fallback path. Removed the schema entirely (the warm path never sent one, and `walk()` already extracts the JSON array from freeform text, filters to known ids, and clamps scores), so freeform is the proven contract on both paths.
|
|
9
|
+
- **Agent-space / OpenClaw:** new `core/recall/query.js` (`query()` + `mergeMatches`), `excerptFor` exported from the discoverer, `recall query` wired into `bin/recall.js`. New hermetic `test-recall-query.js` in the pretest chain (unjudged fail-open, judged keep-only, judged-empty, garbage fail-open, context-seed merge, blank-question reject).
|
|
10
|
+
|
|
11
|
+
## v3.1.1 — /logout and switch-account: re-auth is finally reachable from chat
|
|
12
|
+
|
|
13
|
+
Reported live: with Claude or Codex already connected, there was **no way to sign into a different account** — `/login` showed "connected" and tapping a model just switched models, because the auth-aware picker (correctly, since v3.0.36) only starts a login flow when a provider is *not* authenticated. And `/clear_oauth_token` alone didn't help: it clears the bot-held token but the CLI credential store keeps `authStatus` reading "authenticated", so the picker still never offered a sign-in. Re-auth needs a *full* sign-out first — this release adds it.
|
|
14
|
+
|
|
15
|
+
- **New `/logout [claude|codex]`** (owner-only) — a full sign-out: runs `claude auth logout` / `codex logout` against the CLI credential store **and** (for Claude) clears the bot-held OAuth token from `.env`, process env, and vault in the same step. Partial clears were exactly the trap. Reports each step honestly (including "CLI not found — store untouched"), then offers a **Sign in** button so the fresh login is one tap away. Without an argument it shows provider buttons.
|
|
16
|
+
- **`/login` picker grows a 🔁 Switch account button** under each *connected* provider. Tapping it asks first — showing who is currently signed in (email where the CLI exposes it, else the auth method: OAuth token source / ChatGPT account / API key) and warning that the bot loses that provider until the new login completes — then signs out and chains straight into the fresh login flow. A stray tap can't kill a working auth: the destructive step is behind the explicit confirm.
|
|
17
|
+
- **`/clear_oauth_token` is now documented as the narrow tool** (bot token only); `/logout claude` is the full story.
|
|
18
|
+
- **Agent-space / OpenClaw:** new auth-flow primitives `logoutClaude`/`logoutCodex`/`providerLogout`/`canLogout`/`providerAccountSummary` (core/auth-flow.js), shared executor `performProviderLogout` + `startProviderLogin` (core/handlers.js), new callback family `sw:`/`lo:`/`losw:`/`li:` (core/actions.js, owner-gated). New test `test-logout.js` in the pretest chain.
|
|
19
|
+
|
|
3
20
|
## v3.1.0 — Memory becomes required: safe-mode boot, crash-loop rescue, and /downgrade
|
|
4
21
|
|
|
5
22
|
Three waves folded into one release, graduated to a minor because boot semantics change. Wave 0: the memory substrate is now **required** — a missing `node:sqlite` boots into safe mode instead of silently degrading, a boot crash loop lands there too, and a new `/downgrade` completes the rescue-from-chat story. Wave 1: the memory-substrate revival, root-caused from a live incident — a pod ran 16 days repeating corrected branding mistakes because its entire memory substrate silently never initialized. Wave 2: the first fix batch from the 2026-07-20 blind code review (18 verified findings; deeper architectural items ship in later batches).
|
package/README.md
CHANGED
|
@@ -208,10 +208,11 @@ When you select a project, the last conversation is automatically resumed. Tap "
|
|
|
208
208
|
| Command | Description |
|
|
209
209
|
|---------|-------------|
|
|
210
210
|
| `/auth_status` | Reports redacted Claude auth status and whether an OAuth token is configured |
|
|
211
|
-
| `/login` | Starts a Claude login flow, sends the URL/code, accepts paste-back codes (`/auth_code <code>`, `/cancel_auth`) |
|
|
211
|
+
| `/login` | Starts a Claude login flow, sends the URL/code, accepts paste-back codes (`/auth_code <code>`, `/cancel_auth`). Connected providers show a **🔁 Switch account** button that signs out and starts a fresh login |
|
|
212
|
+
| `/logout [claude\|codex]` | Full sign-out: clears the CLI credential store **and** (for Claude) the bot-held OAuth token, then offers a sign-in button. Without an argument, shows provider buttons |
|
|
212
213
|
| `/setup_token` | Runs `claude setup-token`; the resulting OAuth token is stored without echoing it |
|
|
213
214
|
| `/use_oauth_token [<token>]` | Stores `CLAUDE_CODE_OAUTH_TOKEN` for non-interactive runs; the message is deleted when possible. Without an argument, send the token as the next message |
|
|
214
|
-
| `/clear_oauth_token` | Removes the stored OAuth token |
|
|
215
|
+
| `/clear_oauth_token` | Removes the stored OAuth token only (`/logout claude` for a full sign-out) |
|
|
215
216
|
|
|
216
217
|
Tokens are redacted from chat output and logs.
|
|
217
218
|
|
package/bin/recall.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
// open-claudia recall-stats [--json]
|
|
2
2
|
// open-claudia recall graph [--sync] [--json]
|
|
3
|
+
// open-claudia recall query "<question>" [--context "<text>"] [--json]
|
|
3
4
|
//
|
|
4
5
|
// Inspect the recall system: discoverer metrics (precision/miss-rescue/noise,
|
|
5
|
-
// latency, cost)
|
|
6
|
+
// latency, cost), the typed-edge graph (node/edge counts, sync), and the
|
|
7
|
+
// on-demand walker query (ask long-term memory a question mid-task).
|
|
6
8
|
|
|
7
9
|
const path = require("path");
|
|
8
10
|
|
|
9
11
|
const HELP = `
|
|
10
|
-
Inspect the memory recall system.
|
|
12
|
+
Inspect and query the memory recall system.
|
|
11
13
|
|
|
12
14
|
open-claudia recall-stats [--json] Discoverer metrics summary
|
|
13
15
|
open-claudia recall graph [--sync] Recall-graph stats (optionally re-sync structural edges first)
|
|
16
|
+
open-claudia recall query "<question>" On-demand memory lookup: FTS seeds + graph expansion +
|
|
17
|
+
walker judgement → kept sections with why-bullets
|
|
18
|
+
--context "<text>" Working context to seed alongside the question (what you're doing now)
|
|
19
|
+
--json Machine-readable result
|
|
20
|
+
--no-walker Skip the LLM judge; print the raw seed+graph candidate list
|
|
21
|
+
--no-episodes Skip past-conversation transcript hits
|
|
22
|
+
--limit <n> Pack seed width (default 6)
|
|
23
|
+
--timeout <ms> Walker timeout (default 25000)
|
|
14
24
|
|
|
15
25
|
The discoverer engine logs one line per turn to recall-metrics.jsonl and a
|
|
16
26
|
rolling summary to recall-metrics-summary.json. Switch engines per chat with
|
|
@@ -59,10 +69,66 @@ function graph(args) {
|
|
|
59
69
|
for (const [type, n] of Object.entries(s.byType || {})) console.log(` ${type}: ${n}`);
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
async function queryCmd(args) {
|
|
73
|
+
const flags = { context: "", json: false, noWalker: false, noEpisodes: false, limit: 0, timeout: 0 };
|
|
74
|
+
const positional = [];
|
|
75
|
+
for (let i = 0; i < args.length; i++) {
|
|
76
|
+
const a = args[i];
|
|
77
|
+
if (a === "--json") flags.json = true;
|
|
78
|
+
else if (a === "--no-walker") flags.noWalker = true;
|
|
79
|
+
else if (a === "--no-episodes") flags.noEpisodes = true;
|
|
80
|
+
else if (a === "--context") flags.context = args[++i] || "";
|
|
81
|
+
else if (a === "--limit") flags.limit = Number(args[++i]);
|
|
82
|
+
else if (a === "--timeout") flags.timeout = Number(args[++i]);
|
|
83
|
+
else positional.push(a);
|
|
84
|
+
}
|
|
85
|
+
const question = positional.join(" ").trim();
|
|
86
|
+
if (!question) {
|
|
87
|
+
console.error('Usage: open-claudia recall query "<question>" [--context "<text>"] [--json] [--no-walker] [--no-episodes] [--limit <n>] [--timeout <ms>]');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
// The walker reads its timeout from env at module load — set before require.
|
|
91
|
+
if (flags.timeout > 0) process.env.RECALL_DISCOVERER_TIMEOUT_MS = String(flags.timeout);
|
|
92
|
+
const lib = require(path.join(__dirname, "..", "core", "recall", "query"));
|
|
93
|
+
let res;
|
|
94
|
+
try {
|
|
95
|
+
res = await lib.query(question, {
|
|
96
|
+
context: flags.context,
|
|
97
|
+
walker: !flags.noWalker,
|
|
98
|
+
episodes: !flags.noEpisodes,
|
|
99
|
+
limit: flags.limit || undefined,
|
|
100
|
+
});
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.error(`recall query failed: ${e.message}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
if (flags.json) { console.log(JSON.stringify(res, null, 2)); return; }
|
|
106
|
+
const w = res.walker || {};
|
|
107
|
+
console.log(`Recall query — ${res.results.length} kept of ${res.candidates} candidate(s) (seeds: ${res.seeds.packs} pack, ${res.seeds.entities} entity, ${res.seeds.tools} tool; +${res.activated} graph-activated)`);
|
|
108
|
+
console.log(res.judged
|
|
109
|
+
? `Walker: ${w.provider || "?"}${w.model ? `/${w.model}` : ""} (${w.source}, ${w.ms}ms)`
|
|
110
|
+
: `Walker: ${w.source === "off" ? "off" : "failed"} — UNJUDGED candidate list (fail-open)`);
|
|
111
|
+
for (const r of res.results) {
|
|
112
|
+
console.log("");
|
|
113
|
+
const score = r.score == null ? "" : ` (score ${r.score})`;
|
|
114
|
+
const viaTag = r.activated ? ` [via ${r.via || "graph"}]` : "";
|
|
115
|
+
console.log(`■ ${r.kind}: ${r.name}${score}${viaTag}`);
|
|
116
|
+
if (r.why) console.log(` why: ${r.why}`);
|
|
117
|
+
if (r.kind === "pack" && r.lines && r.lines.length) {
|
|
118
|
+
for (const l of r.lines) console.log(` > [${l.section}:${l.lineno}] ${l.text}`);
|
|
119
|
+
} else if (r.excerpt) {
|
|
120
|
+
console.log(` > ${r.excerpt.replace(/\n/g, " ").slice(0, 300)}`);
|
|
121
|
+
}
|
|
122
|
+
if (r.pointer) console.log(` ↳ ${r.pointer}`);
|
|
123
|
+
}
|
|
124
|
+
if (!res.results.length) console.log('\n(nothing relevant — rephrase, add --context, or use --no-walker to see raw candidates)');
|
|
125
|
+
}
|
|
126
|
+
|
|
62
127
|
function run(args) {
|
|
63
128
|
const sub = args[0];
|
|
64
129
|
if (sub === "--help" || sub === "help") { console.log(HELP); process.exit(0); }
|
|
65
130
|
if (sub === "graph") { graph(args.slice(1)); return; }
|
|
131
|
+
if (sub === "query") { queryCmd(args.slice(1)); return; }
|
|
66
132
|
// default / "stats": metrics summary
|
|
67
133
|
stats(args.filter((a) => a !== "stats"));
|
|
68
134
|
}
|
package/core/actions.js
CHANGED
|
@@ -22,6 +22,7 @@ const { finishOnboarding } = require("./onboarding");
|
|
|
22
22
|
const {
|
|
23
23
|
startSession, switchProviderState, startNewConversation,
|
|
24
24
|
endCurrentSession, activeContinueOptions, applyProviderSetting,
|
|
25
|
+
performProviderLogout, startProviderLogin,
|
|
25
26
|
} = require("./handlers");
|
|
26
27
|
const providerRegistry = require("./providers");
|
|
27
28
|
const jobs = require("./jobs");
|
|
@@ -29,6 +30,7 @@ const scheduler = require("./scheduler");
|
|
|
29
30
|
const { sideChatCoordinator } = require("./side-chat");
|
|
30
31
|
const {
|
|
31
32
|
claudeProviderAuthStatus, runClaudeAuthCommand, runCodexLoginStatus, runCodexDeviceLogin,
|
|
33
|
+
canLogout, providerAccountSummary,
|
|
32
34
|
} = require("./auth-flow");
|
|
33
35
|
|
|
34
36
|
const REMOVED_PROVIDER_CALLBACK_MESSAGE = "This button is from an older Open Claudia release. Cursor Agent support has been removed. Open /backend to choose Claude Code or OpenAI Codex.";
|
|
@@ -350,6 +352,26 @@ async function handleAction(envelope) {
|
|
|
350
352
|
await send(switched ? `Switched to ${beLabel}.\nModel: ${model}` : `Model: ${model}`);
|
|
351
353
|
return;
|
|
352
354
|
}
|
|
355
|
+
// Switch-account / sign-out / sign-in buttons (from the /login picker and
|
|
356
|
+
// /logout). sw: asks first — a stray tap must not kill a working auth.
|
|
357
|
+
if (d.startsWith("sw:") || d.startsWith("lo:") || d.startsWith("losw:") || d.startsWith("li:")) {
|
|
358
|
+
if (!isChatOwner(envelope.channelId)) return send("Owner only — provider auth is shared across users.");
|
|
359
|
+
const providerId = d.slice(d.indexOf(":") + 1);
|
|
360
|
+
if (!canLogout(providerId)) return send(`No sign-out flow for "${providerId}". Open /login again.`);
|
|
361
|
+
const label = providerId === "claude" ? "Claude" : "Codex";
|
|
362
|
+
if (d.startsWith("sw:")) {
|
|
363
|
+
const account = providerAccountSummary(providerId);
|
|
364
|
+
return send(
|
|
365
|
+
`${label} is signed in as: ${account}\n\nSign out and start a fresh sign-in? The bot loses ${label} until the new login completes.`,
|
|
366
|
+
{ keyboard: { inline_keyboard: [[
|
|
367
|
+
{ text: `Yes — switch ${label} account`, callback_data: `losw:${providerId}` },
|
|
368
|
+
{ text: "Cancel", callback_data: "noop" },
|
|
369
|
+
]] } },
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (d.startsWith("li:")) return startProviderLogin(providerId);
|
|
373
|
+
return performProviderLogout(providerId, { thenLogin: d.startsWith("losw:") });
|
|
374
|
+
}
|
|
353
375
|
if (d.startsWith("m:")) {
|
|
354
376
|
state.settings.model = d.slice(2) === "default" ? null : d.slice(2);
|
|
355
377
|
await send(`Model: ${state.settings.model || "default"}`);
|
package/core/auth-flow.js
CHANGED
|
@@ -494,6 +494,69 @@ async function runCodexDeviceLogin() {
|
|
|
494
494
|
});
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
+
// ── Logout / switch account ────────────────────────────────────────
|
|
498
|
+
// Full sign-out clears BOTH credential sources. A partial clear is the
|
|
499
|
+
// re-auth trap: authStatus stays "authenticated" via the other source, so
|
|
500
|
+
// the /login picker never offers a fresh sign-in (the v3.0.36 side effect).
|
|
501
|
+
|
|
502
|
+
function logoutClaude(deps = {}) {
|
|
503
|
+
const steps = [];
|
|
504
|
+
const tokenInfo = (deps.getClaudeOAuthToken || getClaudeOAuthToken)();
|
|
505
|
+
if (tokenInfo.value) {
|
|
506
|
+
(deps.clearClaudeOAuthToken || clearClaudeOAuthToken)();
|
|
507
|
+
steps.push(`Bot OAuth token cleared (${tokenInfo.source})`);
|
|
508
|
+
}
|
|
509
|
+
const binary = deps.binary !== undefined ? deps.binary : claudeExecutable();
|
|
510
|
+
if (!binary) {
|
|
511
|
+
steps.push("Claude CLI not found — CLI credential store untouched");
|
|
512
|
+
return { ok: steps.length > 1 || !!tokenInfo.value, steps };
|
|
513
|
+
}
|
|
514
|
+
const result = (deps.runCommand || runCommand)(binary, ["auth", "logout"], { env: claudeSubprocessEnv(), timeout: 15000 });
|
|
515
|
+
steps.push(result.ok ? "Claude CLI store signed out" : `Claude CLI logout: ${summarizeAuthOutput(result.output, false)}`);
|
|
516
|
+
return { ok: result.ok || !!tokenInfo.value, steps };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function logoutCodex(deps = {}) {
|
|
520
|
+
const binary = deps.binary !== undefined ? deps.binary : codexExecutable();
|
|
521
|
+
if (!binary) return { ok: false, steps: ["Codex CLI not found — nothing to sign out"] };
|
|
522
|
+
const result = (deps.runCommand || runCommand)(binary, ["logout"], { env: codexSubprocessEnv(), timeout: 15000 });
|
|
523
|
+
return { ok: result.ok, steps: [result.ok ? "Codex CLI store signed out" : `Codex logout: ${summarizeAuthOutput(result.output, false)}`] };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const PROVIDER_LOGOUTS = { claude: logoutClaude, codex: logoutCodex };
|
|
527
|
+
|
|
528
|
+
function canLogout(providerId) {
|
|
529
|
+
return Object.prototype.hasOwnProperty.call(PROVIDER_LOGOUTS, providerId);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function providerLogout(providerId, deps = {}) {
|
|
533
|
+
const fn = PROVIDER_LOGOUTS[providerId];
|
|
534
|
+
if (!fn) return { ok: false, steps: [`No sign-out flow for provider "${providerId}"`] };
|
|
535
|
+
return fn(deps);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Short "who is signed in" line for confirm prompts — best effort, only
|
|
539
|
+
// what the CLI exposes.
|
|
540
|
+
function providerAccountSummary(providerId) {
|
|
541
|
+
if (providerId === "claude") {
|
|
542
|
+
const tokenInfo = getClaudeOAuthToken();
|
|
543
|
+
if (tokenInfo.value) return `OAuth token (${tokenInfo.source})`;
|
|
544
|
+
const output = runClaudeAuthStatusDiagnostic();
|
|
545
|
+
const email = (String(output).match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i) || [null])[0];
|
|
546
|
+
return email || "CLI credential store";
|
|
547
|
+
}
|
|
548
|
+
if (providerId === "codex") {
|
|
549
|
+
const status = runCodexLoginStatus();
|
|
550
|
+
if (!status.ok) return "not signed in";
|
|
551
|
+
const email = (String(status.output).match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i) || [null])[0];
|
|
552
|
+
if (email) return email;
|
|
553
|
+
if (/chatgpt/i.test(status.output)) return "ChatGPT account";
|
|
554
|
+
if (/api key/i.test(status.output)) return "API key";
|
|
555
|
+
return "CLI credential store";
|
|
556
|
+
}
|
|
557
|
+
return "unknown";
|
|
558
|
+
}
|
|
559
|
+
|
|
497
560
|
module.exports = {
|
|
498
561
|
CLAUDE_OAUTH_TOKEN_KEY, CLAUDE_OAUTH_VAULT_KEY,
|
|
499
562
|
getClaudeOAuthToken,
|
|
@@ -527,4 +590,9 @@ module.exports = {
|
|
|
527
590
|
sendCodexAuthStatusSummary,
|
|
528
591
|
saveCodexApiKeyWithCli,
|
|
529
592
|
runCodexDeviceLogin,
|
|
593
|
+
logoutClaude,
|
|
594
|
+
logoutCodex,
|
|
595
|
+
canLogout,
|
|
596
|
+
providerLogout,
|
|
597
|
+
providerAccountSummary,
|
|
530
598
|
};
|
package/core/handlers.js
CHANGED
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
runClaudeAuthCommand, clearPendingClaudeAuth,
|
|
49
49
|
sendCodexAuthStatusSummary, runCodexDeviceLogin, clearPendingCodexAuth,
|
|
50
50
|
saveCodexApiKeyWithCli,
|
|
51
|
+
canLogout, providerLogout, providerAccountSummary,
|
|
51
52
|
} = require("./auth-flow");
|
|
52
53
|
const providerRegistry = require("./providers");
|
|
53
54
|
|
|
@@ -1086,13 +1087,16 @@ function buildModelPicker({ authAware = false, state = currentState(), registry
|
|
|
1086
1087
|
const rows = [];
|
|
1087
1088
|
for (const provider of registry.listProviders()) {
|
|
1088
1089
|
let authMarker = "";
|
|
1090
|
+
let connected = false;
|
|
1089
1091
|
if (authAware) {
|
|
1090
|
-
let connected = false;
|
|
1091
1092
|
try { connected = provider.authStatus().state === "authenticated"; } catch (_) {}
|
|
1092
1093
|
authMarker = connected ? " · connected" : " · tap to connect";
|
|
1093
1094
|
}
|
|
1094
1095
|
const availableMarker = providerAvailability(provider) ? "" : " · unavailable";
|
|
1095
1096
|
rows.push([{ text: `── ${provider.label}${availableMarker}${authMarker} ──`, callback_data: "noop" }]);
|
|
1097
|
+
if (authAware && connected && canLogout(provider.id)) {
|
|
1098
|
+
rows.push([{ text: "🔁 Switch account", callback_data: `sw:${provider.id}` }]);
|
|
1099
|
+
}
|
|
1096
1100
|
const choices = provider.modelChoices();
|
|
1097
1101
|
for (let index = 0; index < choices.length; index += 3) {
|
|
1098
1102
|
rows.push(choices.slice(index, index + 3).map((model) => ({
|
|
@@ -1928,6 +1932,43 @@ register({
|
|
|
1928
1932
|
},
|
|
1929
1933
|
});
|
|
1930
1934
|
|
|
1935
|
+
async function startProviderLogin(providerId) {
|
|
1936
|
+
if (providerId === "claude") return runClaudeAuthCommand(["auth", "login", "--claudeai"], "Claude login");
|
|
1937
|
+
if (providerId === "codex") return runCodexDeviceLogin();
|
|
1938
|
+
return send(`No login flow for provider "${providerId}".`);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
async function performProviderLogout(providerId, { thenLogin = false } = {}) {
|
|
1942
|
+
const provider = providerFromRegistry(providerId);
|
|
1943
|
+
const label = provider ? provider.label : providerId;
|
|
1944
|
+
const result = providerLogout(providerId);
|
|
1945
|
+
await send(`${label} sign-out:\n${result.steps.map((s) => `• ${s}`).join("\n")}`);
|
|
1946
|
+
if (thenLogin) return startProviderLogin(providerId);
|
|
1947
|
+
await send(`Sign in with a different account when ready — tap below or use /login.`, {
|
|
1948
|
+
keyboard: { inline_keyboard: [[{ text: `Sign in ${label}`, callback_data: `li:${providerId}` }]] },
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
module.exports.startProviderLogin = startProviderLogin;
|
|
1953
|
+
module.exports.performProviderLogout = performProviderLogout;
|
|
1954
|
+
|
|
1955
|
+
register({
|
|
1956
|
+
name: "logout", description: "Sign out of a provider (full: CLI store + bot token)", args: "[claude|codex]", ownerOnly: true,
|
|
1957
|
+
handler: async (env, { tail }) => {
|
|
1958
|
+
if (!ownerEnv(env)) return send("Owner only — provider auth is shared across users.");
|
|
1959
|
+
const raw = (tail || "").trim().toLowerCase();
|
|
1960
|
+
const target = ({ gpt: "codex", openai: "codex", chatgpt: "codex", anthropic: "claude" })[raw] || raw;
|
|
1961
|
+
if (canLogout(target)) return performProviderLogout(target);
|
|
1962
|
+
if (target) return send(`Unknown provider "${target}". Use /logout claude or /logout codex.`);
|
|
1963
|
+
return send("Sign out of which provider? This clears the CLI credential store (and Claude's bot token) so /login offers a fresh sign-in.", {
|
|
1964
|
+
keyboard: { inline_keyboard: [[
|
|
1965
|
+
{ text: "Claude", callback_data: "lo:claude" },
|
|
1966
|
+
{ text: "Codex", callback_data: "lo:codex" },
|
|
1967
|
+
]] },
|
|
1968
|
+
});
|
|
1969
|
+
},
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1931
1972
|
register({
|
|
1932
1973
|
name: "setup_token", description: "Create Claude OAuth token", ownerOnly: true,
|
|
1933
1974
|
handler: async (env) => {
|
|
@@ -282,6 +282,13 @@ async function runWalker(prompt, options = {}) {
|
|
|
282
282
|
};
|
|
283
283
|
} catch (e) { /* fall back to cold spawn below */ }
|
|
284
284
|
}
|
|
285
|
+
// No outputSchema: a provider structured-output schema becomes a custom-tool
|
|
286
|
+
// input_schema, which the Anthropic API requires to be an OBJECT — the
|
|
287
|
+
// walker's top-level array schema is rejected 400 ("input_schema.type: Input
|
|
288
|
+
// should be 'object'"), so the cold path used to fail-open on every fallback.
|
|
289
|
+
// The warm path never sends a schema and the walk() parser already extracts
|
|
290
|
+
// the JSON array from freeform text, filters to known ids, and clamps scores,
|
|
291
|
+
// so freeform is the proven contract on both paths.
|
|
285
292
|
const result = await runUtility(prompt, {
|
|
286
293
|
purpose: "recall",
|
|
287
294
|
provider: selection.providerId,
|
|
@@ -291,14 +298,6 @@ async function runWalker(prompt, options = {}) {
|
|
|
291
298
|
legacyClaudeModelEnv: "RECALL_DISCOVERER_MODEL",
|
|
292
299
|
systemPrompt: WALKER_SYSTEM,
|
|
293
300
|
readOnly: true,
|
|
294
|
-
outputSchema: {
|
|
295
|
-
type: "array",
|
|
296
|
-
items: {
|
|
297
|
-
type: "object",
|
|
298
|
-
required: ["id"],
|
|
299
|
-
properties: { id: { type: "string" }, why: { type: "string" }, score: { type: "number" } },
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
301
|
timeoutMs: WALKER_TIMEOUT_MS,
|
|
303
302
|
cwd: options.cwd || process.cwd(),
|
|
304
303
|
});
|
|
@@ -779,4 +778,4 @@ async function run(ctx) {
|
|
|
779
778
|
};
|
|
780
779
|
}
|
|
781
780
|
|
|
782
|
-
module.exports = { name: "discoverer", run, runWalker, needsRecall, recallTier, walk, cascadeVerdict, cokeptPairs, scoutToolGaps, renderToolGaps, episodesAllowedForSpeaker };
|
|
781
|
+
module.exports = { name: "discoverer", run, runWalker, needsRecall, recallTier, walk, excerptFor, cascadeVerdict, cokeptPairs, scoutToolGaps, renderToolGaps, episodesAllowedForSpeaker };
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// On-demand recall: the working agent's side door into the same
|
|
4
|
+
// seed → graph-expansion → walker pipeline the discoverer runs per turn.
|
|
5
|
+
// `open-claudia recall query "<question>" [--context "<working context>"]`
|
|
6
|
+
//
|
|
7
|
+
// The per-turn engine seeds from the USER's message; mid-build the agent's
|
|
8
|
+
// actual question often shares zero words with the governing pack (measured
|
|
9
|
+
// 2026-07-16: the embeddings-measurement stance never resurfaced during the
|
|
10
|
+
// build it governed). Here the agent seeds with its own question + working
|
|
11
|
+
// context, and graph edges + walker judgement bridge the vocabulary gap.
|
|
12
|
+
//
|
|
13
|
+
// Deliberate differences from the per-turn engine:
|
|
14
|
+
// - no pre-gate: an explicit query is always intentional (always "full").
|
|
15
|
+
// - read-only: never reinforces edges, never warms co-kept pairs — a
|
|
16
|
+
// query is a lookup, not a turn.
|
|
17
|
+
// - cold walker only: a one-shot CLI process must not own a warm child.
|
|
18
|
+
// - fail-open returns ALL candidates marked unjudged — the caller is an
|
|
19
|
+
// agent that can judge for itself; an empty answer helps nobody.
|
|
20
|
+
|
|
21
|
+
const graph = require("./graph");
|
|
22
|
+
const metrics = require("./metrics");
|
|
23
|
+
const tuning = require("./tuning");
|
|
24
|
+
const discoverer = require("./discoverer");
|
|
25
|
+
|
|
26
|
+
let transcriptIndex = null;
|
|
27
|
+
try { transcriptIndex = require("../transcript-index"); } catch (e) {}
|
|
28
|
+
|
|
29
|
+
const COLD_ONLY_WARM = { isEnabled: () => false };
|
|
30
|
+
const EXCERPT_CHARS = 600;
|
|
31
|
+
|
|
32
|
+
function mergeMatches(primary, secondary, keyOf) {
|
|
33
|
+
const out = primary.map((m) => ({ ...m, origin: "user" }));
|
|
34
|
+
const seen = new Set(primary.map(keyOf));
|
|
35
|
+
for (const m of secondary) {
|
|
36
|
+
if (seen.has(keyOf(m))) continue;
|
|
37
|
+
out.push({ ...m, origin: "context" });
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function clip(s, n) { return s && s.length > n ? s.slice(0, n) + "…" : (s || ""); }
|
|
43
|
+
|
|
44
|
+
async function query(question, opts = {}) {
|
|
45
|
+
const packsLib = opts.packsLib || require("../packs");
|
|
46
|
+
const entitiesLib = opts.entitiesLib || require("../entities");
|
|
47
|
+
const toolsLib = opts.toolsLib || require("../tools");
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const q = String(question || "").trim();
|
|
50
|
+
if (!q) throw new Error("recall query needs a question");
|
|
51
|
+
const context = String(opts.context || "").trim();
|
|
52
|
+
const packLimit = Number(opts.limit) > 0 ? Math.min(Number(opts.limit), 12) : 6;
|
|
53
|
+
|
|
54
|
+
// Seed via FTS on the question, then the working context (deduped).
|
|
55
|
+
let packSeeds = [];
|
|
56
|
+
let entSeeds = [];
|
|
57
|
+
try {
|
|
58
|
+
packSeeds = mergeMatches(
|
|
59
|
+
packsLib.matchPacks(q, { limit: packLimit }),
|
|
60
|
+
context ? packsLib.matchPacks(context, { limit: packLimit }) : [],
|
|
61
|
+
(m) => m.dir,
|
|
62
|
+
);
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
try {
|
|
65
|
+
entSeeds = mergeMatches(
|
|
66
|
+
entitiesLib.matchEntities(q, { limit: 4 }),
|
|
67
|
+
context ? entitiesLib.matchEntities(context, { limit: 4 }) : [],
|
|
68
|
+
(m) => m.slug,
|
|
69
|
+
);
|
|
70
|
+
} catch (e) {}
|
|
71
|
+
const toolSeeds = new Map();
|
|
72
|
+
try {
|
|
73
|
+
for (const t of toolsLib.matchTools(q, { limit: 5 })) toolSeeds.set(t.name, { ...t, origin: "user" });
|
|
74
|
+
if (context) {
|
|
75
|
+
for (const t of toolsLib.matchTools(context, { limit: 5 })) {
|
|
76
|
+
if (!toolSeeds.has(t.name)) toolSeeds.set(t.name, { ...t, origin: "context" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
|
|
81
|
+
// Spreading activation from seeds across the typed-edge graph.
|
|
82
|
+
const seedNodes = [
|
|
83
|
+
...packSeeds.map((m) => ({ id: `pack:${m.dir}`, score: m.score || 2 })),
|
|
84
|
+
...entSeeds.map((m) => ({ id: `entity:${m.slug}`, score: m.score || 2 })),
|
|
85
|
+
];
|
|
86
|
+
const seedIds = new Set(seedNodes.map((s) => s.id));
|
|
87
|
+
let activated = new Map();
|
|
88
|
+
if (graph.available()) {
|
|
89
|
+
try { graph.syncFromCorpus(packsLib, entitiesLib); } catch (e) {}
|
|
90
|
+
try { activated = graph.expand(seedNodes, {}); } catch (e) {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Candidates: question seeds by score, context seeds, then graph-activated.
|
|
94
|
+
const seedRank = new Map();
|
|
95
|
+
for (const m of packSeeds) seedRank.set(`pack:${m.dir}`, { score: m.score || 2, origin: m.origin });
|
|
96
|
+
for (const m of entSeeds) seedRank.set(`entity:${m.slug}`, { score: m.score || 2, origin: m.origin });
|
|
97
|
+
const orderedIds = [...seedIds].sort((a, b) => {
|
|
98
|
+
const ra = seedRank.get(a) || {}, rb = seedRank.get(b) || {};
|
|
99
|
+
const ua = ra.origin === "user" ? 0 : 1, ub = rb.origin === "user" ? 0 : 1;
|
|
100
|
+
return ua - ub || (rb.score || 0) - (ra.score || 0);
|
|
101
|
+
});
|
|
102
|
+
for (const [id] of [...activated.entries()].sort((x, y) => (y[1].activation || 0) - (x[1].activation || 0))) {
|
|
103
|
+
if (!seedIds.has(id)) orderedIds.push(id);
|
|
104
|
+
}
|
|
105
|
+
const candidates = [];
|
|
106
|
+
for (const id of orderedIds.slice(0, tuning.get("walkerMaxCandidates"))) {
|
|
107
|
+
const base = discoverer.excerptFor(id, packsLib, entitiesLib);
|
|
108
|
+
if (!base) continue;
|
|
109
|
+
const act = activated.get(id);
|
|
110
|
+
candidates.push({ ...base, activated: !seedIds.has(id), via: act ? act.via : null });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Tools: direct seeds plus tools declared by any candidate pack.
|
|
114
|
+
const candPackDirs = new Set();
|
|
115
|
+
for (const c of candidates) if (c.id.startsWith("pack:")) candPackDirs.add(c.id.slice(5));
|
|
116
|
+
const toolCand = new Map();
|
|
117
|
+
for (const [name] of toolSeeds) toolCand.set(name, { name, via: null });
|
|
118
|
+
if (candPackDirs.size) {
|
|
119
|
+
try {
|
|
120
|
+
for (const t of toolsLib.listTools()) {
|
|
121
|
+
if (t.pack && candPackDirs.has(t.pack) && !toolCand.has(t.name)) {
|
|
122
|
+
toolCand.set(t.name, { name: t.name, via: `pack:${t.pack}` });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {}
|
|
126
|
+
}
|
|
127
|
+
for (const c of toolCand.values()) {
|
|
128
|
+
const base = discoverer.excerptFor(`tool:${c.name}`, packsLib, entitiesLib);
|
|
129
|
+
if (!base) continue;
|
|
130
|
+
candidates.push({ ...base, activated: !!c.via, via: c.via });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Episodes: FTS over past transcripts. The CLI has no speaker context, so
|
|
134
|
+
// the R6 owner gate is open by design (the CLI runs as the owner on their
|
|
135
|
+
// own box); bot-side callers still pass through the per-turn engine's gate.
|
|
136
|
+
if (opts.episodes !== false && transcriptIndex && transcriptIndex.available()) {
|
|
137
|
+
try {
|
|
138
|
+
const { hits } = transcriptIndex.search(q, { limit: tuning.get("episodeLimit") });
|
|
139
|
+
for (const h of (hits || [])) {
|
|
140
|
+
const terms = [...String(h.snip || "").matchAll(/>>(.+?)<</g)].map((m) => m[1]).slice(0, 4);
|
|
141
|
+
candidates.push({
|
|
142
|
+
id: `episode:${require("path").basename(String(h.file || ""), ".jsonl")}:${h.line}`,
|
|
143
|
+
name: `Past conversation${h.project ? ` (${h.project})` : ""}${h.ts ? ` ${String(h.ts).slice(0, 10)}` : ""}`,
|
|
144
|
+
description: "",
|
|
145
|
+
excerpt: clip(String(h.snip || "").replace(/>>|<</g, ""), EXCERPT_CHARS),
|
|
146
|
+
activated: true,
|
|
147
|
+
via: "transcript",
|
|
148
|
+
_terms: terms,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
} catch (e) { /* episodic recall is best-effort */ }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Walker judgement (cold spawn only). opts.walker === false skips the spawn.
|
|
155
|
+
let walkRes = { kept: null, scores: null, source: "off", walkerMs: 0, provider: null, model: null, costUsd: null };
|
|
156
|
+
if (opts.walker !== false && candidates.length) {
|
|
157
|
+
walkRes = await discoverer.walk(q, context, candidates, {
|
|
158
|
+
warmWalker: COLD_ONLY_WARM,
|
|
159
|
+
cwd: opts.cwd,
|
|
160
|
+
...(opts.walkerOptions || {}),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const whyMap = walkRes.kept;
|
|
164
|
+
const scores = walkRes.scores;
|
|
165
|
+
|
|
166
|
+
const results = [];
|
|
167
|
+
for (const c of candidates) {
|
|
168
|
+
if (whyMap && !whyMap.has(c.id)) continue;
|
|
169
|
+
const kind = c.id.startsWith("pack:") ? "pack"
|
|
170
|
+
: c.id.startsWith("entity:") ? "entity"
|
|
171
|
+
: c.id.startsWith("tool:") ? "tool" : "episode";
|
|
172
|
+
const r = {
|
|
173
|
+
id: c.id,
|
|
174
|
+
kind,
|
|
175
|
+
name: c.name,
|
|
176
|
+
why: whyMap ? whyMap.get(c.id) || "" : "",
|
|
177
|
+
score: scores && scores.has(c.id) ? scores.get(c.id) : null,
|
|
178
|
+
activated: !!c.activated,
|
|
179
|
+
via: c.via || null,
|
|
180
|
+
excerpt: c.excerpt || "",
|
|
181
|
+
};
|
|
182
|
+
if (kind === "pack") {
|
|
183
|
+
const dir = c.id.slice(5);
|
|
184
|
+
r.pointer = `open-claudia pack show ${dir}`;
|
|
185
|
+
try {
|
|
186
|
+
const hits = packsLib.searchLines([q, context].filter(Boolean).join(" "), { dir, k: 8 });
|
|
187
|
+
if (hits && hits.length) r.lines = hits.map((h) => ({ section: h.section, lineno: h.lineno, text: h.text }));
|
|
188
|
+
} catch (e) { /* a pack with no line hits still shows its excerpt */ }
|
|
189
|
+
} else if (kind === "entity") {
|
|
190
|
+
r.pointer = `open-claudia entity show ${c.id.slice(7)}`;
|
|
191
|
+
} else if (kind === "tool") {
|
|
192
|
+
r.pointer = `open-claudia tool show ${c.id.slice(5)}`;
|
|
193
|
+
} else {
|
|
194
|
+
const qterms = (c._terms && c._terms.length
|
|
195
|
+
? c._terms.join(" ")
|
|
196
|
+
: String(c.excerpt).split(/\s+/).slice(0, 4).join(" ")).replace(/"/g, "");
|
|
197
|
+
r.pointer = `open-claudia transcript-search "${qterms}" --all`;
|
|
198
|
+
}
|
|
199
|
+
results.push(r);
|
|
200
|
+
}
|
|
201
|
+
results.sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
|
|
202
|
+
|
|
203
|
+
const out = {
|
|
204
|
+
question: q,
|
|
205
|
+
hasContext: !!context,
|
|
206
|
+
seeds: { packs: packSeeds.length, entities: entSeeds.length, tools: toolSeeds.size },
|
|
207
|
+
activated: activated.size,
|
|
208
|
+
candidates: candidates.length,
|
|
209
|
+
judged: !!whyMap,
|
|
210
|
+
walker: {
|
|
211
|
+
source: walkRes.source,
|
|
212
|
+
ms: walkRes.walkerMs || 0,
|
|
213
|
+
provider: walkRes.provider || null,
|
|
214
|
+
model: walkRes.model || null,
|
|
215
|
+
},
|
|
216
|
+
results,
|
|
217
|
+
latencyMs: Date.now() - started,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
metrics.logTurn({
|
|
222
|
+
engine: "query",
|
|
223
|
+
provider: walkRes.provider || "",
|
|
224
|
+
model: walkRes.model || "",
|
|
225
|
+
query: q,
|
|
226
|
+
tier: "query",
|
|
227
|
+
seeds: seedNodes,
|
|
228
|
+
activated: [...activated.entries()].map(([id, a]) => ({ id, activation: a.activation, hop: a.hop })),
|
|
229
|
+
cand: candidates.map((c) => ({ id: c.id, act: !!c.activated })),
|
|
230
|
+
kept: results.map((r) => ({ id: r.id, why: r.why, score: r.score })),
|
|
231
|
+
gated: false,
|
|
232
|
+
latencyMs: out.latencyMs,
|
|
233
|
+
walkerMs: walkRes.walkerMs,
|
|
234
|
+
walker: walkRes.source,
|
|
235
|
+
costUsd: walkRes.costUsd || 0,
|
|
236
|
+
});
|
|
237
|
+
} catch (e) {}
|
|
238
|
+
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = { query, mergeMatches };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
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": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"setup": "node setup.js",
|
|
11
11
|
"start": "node bot.js",
|
|
12
|
-
"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-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",
|
|
13
|
-
"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-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-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-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"
|
|
12
|
+
"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-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",
|
|
13
|
+
"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-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-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"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"bot.js",
|
|
@@ -146,7 +146,28 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
146
146
|
// New /downgrade rolls back to the previously-running version from boot
|
|
147
147
|
// history (strict semver-validated target); /upgrade//downgrade clear the
|
|
148
148
|
// crash counter as deliberate fix attempts. package.json engines >=24.
|
|
149
|
-
|
|
149
|
+
// 3.1.1 approved by Sumeet 2026-07-20: he reported the re-auth trap ("wjen i
|
|
150
|
+
// want to reauth a new account for say claude or gpt and i do /login and
|
|
151
|
+
// select it but i am still logged in it just shows me the same model instead
|
|
152
|
+
// of a reatuh and there is no logut option?") and green-lit the proposed fix
|
|
153
|
+
// (deferred once behind the smart-brenda slash-menu check, then resumed).
|
|
154
|
+
// Ships: /logout [claude|codex] full sign-out — CLI credential store AND the
|
|
155
|
+
// bot-held Claude token in one step (partial clears were the trap: any
|
|
156
|
+
// surviving source keeps authStatus "authenticated" so the /login picker
|
|
157
|
+
// never re-offers a sign-in); a 🔁 Switch account button on connected
|
|
158
|
+
// providers in /login behind an explicit confirm that names the signed-in
|
|
159
|
+
// account where the CLI exposes it, then chains logout → fresh login.
|
|
160
|
+
// 3.1.2 approved by Sumeet 2026-07-20 ("If everything you have works and is
|
|
161
|
+
// ready fold it in and push all out in a new version"): on-demand recall query
|
|
162
|
+
// CLI (`open-claudia recall query "<question>"`) — seeds the graph with the
|
|
163
|
+
// agent's OWN working context (not the user's words), walks it with the
|
|
164
|
+
// active-provider judge, returns pack sections + why-bullets; closes the
|
|
165
|
+
// cold-start hole where mid-build vocabulary shared no words with the governing
|
|
166
|
+
// pack. Also fixes a latent cold-walker bug: the warm→cold fallback sent an
|
|
167
|
+
// array-typed outputSchema the Anthropic API rejects 400, so every fallback
|
|
168
|
+
// silently failed-open — removed; freeform JSON parse is the proven contract on
|
|
169
|
+
// both paths.
|
|
170
|
+
assert.strictEqual(pkg.version, "3.1.2", "release version must remain unchanged without explicit approval");
|
|
150
171
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
151
172
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
152
173
|
}
|