arisa 2.0.4 → 2.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Arisa - dynamic agent runtime with daemon/core architecture that evolves through user interaction",
5
5
  "preferGlobal": true,
6
6
  "bin": {
@@ -29,8 +29,7 @@
29
29
  "dependencies": {
30
30
  "croner": "^9.0.0",
31
31
  "crypto-js": "^4.2.0",
32
- "deepbase": "^3.4.6",
33
- "deepbase-json": "^3.4.6",
32
+ "deepbase": "^3.4.9",
34
33
  "elevenlabs": "^1.59.0",
35
34
  "grammy": "^1.21.0",
36
35
  "openai": "^6.19.0",
package/src/core/index.ts CHANGED
@@ -49,16 +49,25 @@ function defaultBackend(): "claude" | "codex" {
49
49
  }
50
50
 
51
51
  function getBackend(chatId: string): "claude" | "codex" {
52
+ const deps = checkDeps();
53
+
54
+ const preferInstalled = (candidate: "claude" | "codex"): "claude" | "codex" => {
55
+ if (candidate === "claude" && !deps.claude && deps.codex) return "codex";
56
+ if (candidate === "codex" && !deps.codex && deps.claude) return "claude";
57
+ return candidate;
58
+ };
59
+
52
60
  const current = backendState.get(chatId);
53
- if (current) return current;
61
+ if (current) return preferInstalled(current);
54
62
 
55
63
  const fromHistory = getLastBackend(chatId);
56
64
  if (fromHistory) {
57
- backendState.set(chatId, fromHistory);
58
- return fromHistory;
65
+ const resolved = preferInstalled(fromHistory);
66
+ backendState.set(chatId, resolved);
67
+ return resolved;
59
68
  }
60
69
 
61
- return defaultBackend();
70
+ return preferInstalled(defaultBackend());
62
71
  }
63
72
 
64
73
  // Initialize auth + scheduler + attachments
@@ -329,9 +338,15 @@ ${messageText}`;
329
338
  return Response.json(response);
330
339
  }
331
340
 
341
+ const deps = checkDeps();
342
+ if (!deps.claude && !deps.codex) {
343
+ return Response.json({
344
+ text: "No AI CLI is installed. Install at least one:\n<code>bun add -g @anthropic-ai/claude-code</code>\n<code>bun add -g @openai/codex</code>",
345
+ } as CoreResponse);
346
+ }
347
+
332
348
  // Route based on current backend state
333
349
  const backend = getBackend(msg.chatId);
334
- const deps = checkDeps();
335
350
  const canFallback = backend === "codex" ? deps.claude : deps.codex;
336
351
  let agentResponse: string;
337
352
  let historyResponse: string | null = null;
@@ -363,6 +378,11 @@ ${messageText}`;
363
378
  } else {
364
379
  try {
365
380
  agentResponse = await processWithClaude(enrichedMessage, msg.chatId);
381
+ if (agentResponse.startsWith("Error:") && canFallback) {
382
+ log.warn("Claude failed, falling back to Codex");
383
+ agentResponse = await processWithCodex(enrichedMessage);
384
+ usedBackend = "codex";
385
+ }
366
386
  if (isClaudeRateLimitResponse(agentResponse) && canFallback) {
367
387
  log.warn("Claude credits exhausted, falling back to Codex");
368
388
  const codexResponse = await processWithCodex(enrichedMessage);
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { config } from "../shared/config";
13
13
  import { createLogger } from "../shared/logger";
14
+ import { buildBunWrappedAgentCliCommand, resolveAgentCliPath } from "../shared/ai-cli";
14
15
 
15
16
  const log = createLogger("core");
16
17
 
@@ -51,9 +52,15 @@ Rules:
51
52
 
52
53
  function buildCmd(cli: "claude" | "codex", prompt: string): string[] {
53
54
  if (cli === "claude") {
54
- return ["claude", "--dangerously-skip-permissions", "--model", "haiku", "-p", prompt];
55
+ return buildBunWrappedAgentCliCommand(
56
+ "claude",
57
+ ["--dangerously-skip-permissions", "--model", "haiku", "-p", prompt],
58
+ );
55
59
  }
56
- return ["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "-C", config.projectDir, prompt];
60
+ return buildBunWrappedAgentCliCommand(
61
+ "codex",
62
+ ["exec", "--dangerously-bypass-approvals-and-sandbox", "-C", config.projectDir, prompt],
63
+ );
57
64
  }
58
65
 
59
66
  // Track which CLI actually works (not just Bun.which, which can find broken shims)
@@ -75,8 +82,8 @@ async function trySpawn(prompt: string, cli: "claude" | "codex"): Promise<string
75
82
  function getCliOrder(): Array<"claude" | "codex"> {
76
83
  if (verifiedCli) return [verifiedCli];
77
84
  const order: Array<"claude" | "codex"> = [];
78
- if (Bun.which("claude") !== null) order.push("claude");
79
- if (Bun.which("codex") !== null) order.push("codex");
85
+ if (resolveAgentCliPath("claude") !== null) order.push("claude");
86
+ if (resolveAgentCliPath("codex") !== null) order.push("codex");
80
87
  return order;
81
88
  }
82
89
 
@@ -13,6 +13,7 @@
13
13
  import { config } from "../shared/config";
14
14
  import { createLogger } from "../shared/logger";
15
15
  import { getOnboardedUsers, addOnboarded as dbAddOnboarded, isOnboarded as dbIsOnboarded } from "../shared/db";
16
+ import { isAgentCliInstalled } from "../shared/ai-cli";
16
17
 
17
18
  const log = createLogger("core");
18
19
 
@@ -46,8 +47,8 @@ export function checkDeps(): DepsStatus {
46
47
  : "Linux";
47
48
 
48
49
  return {
49
- claude: Bun.which("claude") !== null,
50
- codex: Bun.which("codex") !== null,
50
+ claude: isAgentCliInstalled("claude"),
51
+ codex: isAgentCliInstalled("codex"),
51
52
  openaiKey: !!config.openaiApiKey,
52
53
  os,
53
54
  };
@@ -16,6 +16,7 @@ import { getRecentHistory } from "./history";
16
16
  import { shouldContinue } from "./context";
17
17
  import { config } from "../shared/config";
18
18
  import { createLogger } from "../shared/logger";
19
+ import { buildBunWrappedAgentCliCommand } from "../shared/ai-cli";
19
20
  import { existsSync, mkdirSync, readFileSync, appendFileSync } from "fs";
20
21
  import { join } from "path";
21
22
 
@@ -25,10 +26,7 @@ const PROMPT_PREVIEW_MAX = 220;
25
26
  export const CLAUDE_RATE_LIMIT_MESSAGE = "Claude is out of credits right now. Please try again in a few minutes.";
26
27
  export const CODEX_AUTH_REQUIRED_MESSAGE = [
27
28
  "Codex login is required.",
28
- "Check the Arisa daemon logs now and complete the device-auth steps shown there.",
29
- "If the login flow is not running, execute:",
30
- "<code>codex login --device-auth</code>",
31
- "Then send your message again.",
29
+ "Check the Arisa daemon logs now and complete the device-auth steps shown there."
32
30
  ].join("\n");
33
31
 
34
32
  function logActivity(backend: string, model: string | null, durationMs: number, status: string) {
@@ -147,7 +145,7 @@ async function runClaude(message: string, chatId: string): Promise<string> {
147
145
  log.info(`Claude spawn | cmd: claude --dangerously-skip-permissions --model ${model.model} -p <prompt>`);
148
146
  log.debug(`Claude prompt >>>>\n${prompt}\n<<<<`);
149
147
 
150
- const proc = Bun.spawn(["claude", ...args], {
148
+ const proc = Bun.spawn(buildBunWrappedAgentCliCommand("claude", args), {
151
149
  cwd: config.projectDir,
152
150
  stdout: "pipe",
153
151
  stderr: "pipe",
@@ -218,7 +216,7 @@ export async function processWithCodex(message: string): Promise<string> {
218
216
  );
219
217
  log.debug(`Codex prompt >>>>\n${message}\n<<<<`);
220
218
 
221
- const proc = Bun.spawn(["codex", ...args], {
219
+ const proc = Bun.spawn(buildBunWrappedAgentCliCommand("codex", args), {
222
220
  cwd: config.projectDir,
223
221
  stdout: "pipe",
224
222
  stderr: "pipe",
@@ -11,10 +11,15 @@
11
11
 
12
12
  import { config } from "../shared/config";
13
13
  import { createLogger } from "../shared/logger";
14
+ import {
15
+ buildBunWrappedAgentCliCommand,
16
+ resolveAgentCliPath,
17
+ type AgentCliName,
18
+ } from "../shared/ai-cli";
14
19
 
15
20
  const log = createLogger("daemon");
16
21
 
17
- export type AgentCli = "claude" | "codex";
22
+ export type AgentCli = AgentCliName;
18
23
 
19
24
  export interface CliExecutionResult {
20
25
  cli: AgentCli;
@@ -32,8 +37,8 @@ export interface CliFallbackOutcome {
32
37
 
33
38
  export function getAvailableAgentCli(): AgentCli[] {
34
39
  const order: AgentCli[] = [];
35
- if (Bun.which("claude") !== null) order.push("claude");
36
- if (Bun.which("codex") !== null) order.push("codex");
40
+ if (resolveAgentCliPath("claude") !== null) order.push("claude");
41
+ if (resolveAgentCliPath("codex") !== null) order.push("codex");
37
42
  return order;
38
43
  }
39
44
 
@@ -43,9 +48,15 @@ export function getAgentCliLabel(cli: AgentCli): string {
43
48
 
44
49
  function buildCommand(cli: AgentCli, prompt: string): string[] {
45
50
  if (cli === "claude") {
46
- return ["claude", "--dangerously-skip-permissions", "--model", "sonnet", "-p", prompt];
51
+ return buildBunWrappedAgentCliCommand(
52
+ "claude",
53
+ ["--dangerously-skip-permissions", "--model", "sonnet", "-p", prompt],
54
+ );
47
55
  }
48
- return ["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "-C", config.projectDir, prompt];
56
+ return buildBunWrappedAgentCliCommand(
57
+ "codex",
58
+ ["exec", "--dangerously-bypass-approvals-and-sandbox", "-C", config.projectDir, prompt],
59
+ );
49
60
  }
50
61
 
51
62
  async function runSingleCli(
@@ -116,4 +127,3 @@ function summarizeError(raw: string): string {
116
127
  if (!clean) return "no details";
117
128
  return clean.length > 200 ? `${clean.slice(0, 200)}...` : clean;
118
129
  }
119
-
@@ -3,18 +3,19 @@
3
3
  * @role Trigger Codex device auth flow from Daemon when auth errors are detected.
4
4
  * @responsibilities
5
5
  * - Detect codex auth-required signals in Core responses
6
- * - Run `codex login --device-auth` in background from daemon process
6
+ * - Run `codex login --device-auth` (wrapped via Bun) in background from daemon process
7
7
  * - Avoid duplicate runs with in-progress lock + cooldown
8
8
  * @effects Spawns codex CLI process, writes to daemon logs/terminal
9
9
  */
10
10
 
11
11
  import { config } from "../shared/config";
12
12
  import { createLogger } from "../shared/logger";
13
+ import { buildBunWrappedAgentCliCommand } from "../shared/ai-cli";
13
14
 
14
15
  const log = createLogger("daemon");
15
16
 
16
17
  const AUTH_HINT_PATTERNS = [
17
- /codex login --device-auth/i,
18
+ /codex.*login --device-auth/i,
18
19
  /codex is not authenticated on this server/i,
19
20
  /missing bearer authentication in header/i,
20
21
  ];
@@ -59,12 +60,12 @@ export function maybeStartCodexDeviceAuth(rawCoreText: string, chatId?: string):
59
60
  }
60
61
 
61
62
  async function runCodexDeviceAuth(): Promise<void> {
62
- log.warn("Codex auth required. Starting `codex login --device-auth` now.");
63
+ log.warn("Codex auth required. Starting `bun --bun <path-to-codex> login --device-auth` now.");
63
64
  log.warn("Complete device auth using the URL/code printed below in this Arisa terminal.");
64
65
 
65
66
  let proc: ReturnType<typeof Bun.spawn>;
66
67
  try {
67
- proc = Bun.spawn(["codex", "login", "--device-auth"], {
68
+ proc = Bun.spawn(buildBunWrappedAgentCliCommand("codex", ["login", "--device-auth"]), {
68
69
  cwd: config.projectDir,
69
70
  stdin: "inherit",
70
71
  stdout: "inherit",
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @module shared/ai-cli
3
+ * @role Resolve agent CLI binaries and execute them via Bun runtime.
4
+ */
5
+
6
+ export type AgentCliName = "claude" | "codex";
7
+
8
+ export function resolveAgentCliPath(cli: AgentCliName): string | null {
9
+ return Bun.which(cli);
10
+ }
11
+
12
+ export function isAgentCliInstalled(cli: AgentCliName): boolean {
13
+ return resolveAgentCliPath(cli) !== null;
14
+ }
15
+
16
+ export function buildBunWrappedAgentCliCommand(cli: AgentCliName, args: string[]): string[] {
17
+ const cliPath = resolveAgentCliPath(cli);
18
+ if (!cliPath) {
19
+ throw new Error(`${cli} CLI not found in PATH`);
20
+ }
21
+ return ["bun", "--bun", cliPath, ...args];
22
+ }