@inetafrica/open-claudia 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.1.1 — /logout and switch-account: re-auth is finally reachable from chat
4
+
5
+ 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.
6
+
7
+ - **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.
8
+ - **`/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.
9
+ - **`/clear_oauth_token` is now documented as the narrow tool** (bot token only); `/logout claude` is the full story.
10
+ - **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.
11
+
3
12
  ## v3.1.0 — Memory becomes required: safe-mode boot, crash-loop rescue, and /downgrade
4
13
 
5
14
  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/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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@
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",
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
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"
14
14
  },
15
15
  "files": [
@@ -146,7 +146,18 @@ 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
- assert.strictEqual(pkg.version, "3.1.0", "release version must remain unchanged without explicit approval");
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
+ assert.strictEqual(pkg.version, "3.1.1", "release version must remain unchanged without explicit approval");
150
161
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
151
162
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
152
163
  }