niahere 0.4.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -44,9 +44,9 @@
44
44
  "license": "MIT",
45
45
  "private": false,
46
46
  "dependencies": {
47
- "@anthropic-ai/claude-agent-sdk": "^0.2.126",
48
- "@anthropic-ai/sdk": "^0.88.0",
49
- "@modelcontextprotocol/sdk": "^1.27.1",
47
+ "@anthropic-ai/claude-agent-sdk": "^0.3.190",
48
+ "@anthropic-ai/sdk": "^0.105.0",
49
+ "@modelcontextprotocol/sdk": "^1.29.0",
50
50
  "@slack/bolt": "^4.6.0",
51
51
  "cron-parser": "^5.5.0",
52
52
  "grammy": "^1.41.1",
@@ -1,5 +1,5 @@
1
1
  import type { AgentEvent, Normalizer } from "../types";
2
- import { truncate, formatToolUse } from "../../utils/format-activity";
2
+ import { truncate } from "../../utils/format-activity";
3
3
  import { isRetryableApiError, isProviderDownError } from "../../utils/retry";
4
4
 
5
5
  /**
@@ -30,22 +30,14 @@ export class SdkNormalizer implements Normalizer {
30
30
  }
31
31
 
32
32
  if (msg.type === "tool_use_summary") {
33
- return [
34
- {
35
- type: "tool",
36
- name: msg.tool_name || "tool",
37
- summary: formatToolUse(msg.tool_name || "tool", msg.tool_input),
38
- },
39
- ];
33
+ // The SDK provides a ready-made human-readable summary (e.g. "Read foo.ts").
34
+ // (Older code read tool_name/tool_input, which this event does not carry.)
35
+ return msg.summary ? [{ type: "tool", name: "tool", summary: truncate(msg.summary, 70) }] : [];
40
36
  }
41
37
 
42
38
  if (msg.type === "tool_progress") {
43
- if (msg.tool_name === "Bash" && msg.content) {
44
- return [{ type: "tool", name: "Bash", summary: `$ ${truncate(msg.content, 60)}` }];
45
- }
46
- if (msg.content) {
47
- return [{ type: "tool", name: msg.tool_name || "tool", summary: truncate(msg.content, 70) }];
48
- }
39
+ // Carries only tool_use_id/tool_name/elapsed_time no displayable content,
40
+ // and fires repeatedly. tool_use_summary already covers tool activity.
49
41
  return [];
50
42
  }
51
43
 
@@ -151,8 +151,14 @@ class ClaudeSession implements AgentSession {
151
151
  retry = true;
152
152
  break;
153
153
  }
154
- yield ev;
155
- if (ev.type === "result" || ev.type === "error") {
154
+ // A retryable error that survived all our retries means the provider is
155
+ // effectively down for us flag it so the consumer can fail over.
156
+ const out =
157
+ ev.type === "error" && ev.retryable && this.retryCount >= MAX_SEND_RETRIES
158
+ ? { ...ev, providerDown: true }
159
+ : ev;
160
+ yield out;
161
+ if (out.type === "result" || out.type === "error") {
156
162
  this.retryCount = 0;
157
163
  return;
158
164
  }
@@ -1,9 +1,37 @@
1
+ import { existsSync, readdirSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join, dirname } from "path";
1
4
  import type { AgentBackend, AgentSession, AgentSessionContext, AgentEvent } from "../types";
2
5
  import type { Attachment } from "../../types/attachment";
3
6
  import type { McpSourceContext } from "../../mcp";
4
7
  import { CodexNormalizer } from "./codex-normalize";
5
8
  import { mintRun, revokeRun } from "../mcp-endpoint";
6
9
 
10
+ /**
11
+ * Resolve the codex binary's absolute path. The daemon runs under launchd with a
12
+ * minimal PATH (`/usr/bin:/bin:...`) that excludes nvm/homebrew bins, so a bare
13
+ * `codex` spawn would fail. Search the likely install locations (env override,
14
+ * the runtime's own bin, homebrew, every nvm node bin, bun) and fall back to
15
+ * PATH only as a last resort. Cached after first resolution.
16
+ */
17
+ let cachedCodexBin: string | null = null;
18
+ export function resolveCodexBin(): string {
19
+ if (cachedCodexBin) return cachedCodexBin;
20
+ const candidates: string[] = [];
21
+ if (process.env.CODEX_PATH) candidates.push(process.env.CODEX_PATH);
22
+ candidates.push(join(dirname(process.execPath), "codex")); // sibling of bun/node
23
+ candidates.push("/opt/homebrew/bin/codex", "/usr/local/bin/codex");
24
+ try {
25
+ const nvm = join(homedir(), ".nvm", "versions", "node");
26
+ for (const v of readdirSync(nvm)) candidates.push(join(nvm, v, "bin", "codex"));
27
+ } catch {
28
+ /* no nvm */
29
+ }
30
+ candidates.push(join(homedir(), ".bun", "bin", "codex"));
31
+ cachedCodexBin = candidates.find((p) => existsSync(p)) ?? "codex";
32
+ return cachedCodexBin;
33
+ }
34
+
7
35
  /** Minimal spawned-process surface, injectable so the session is unit-testable. */
8
36
  export interface CliProc {
9
37
  stdout: ReadableStream<Uint8Array>;
@@ -33,7 +61,12 @@ function scrubbedEnv(extra: Record<string, string>): Record<string, string> {
33
61
  }
34
62
 
35
63
  function defaultSpawn(args: string[], opts: { cwd: string; env: Record<string, string> }): CliProc {
36
- const proc = Bun.spawn(["codex", ...args], { cwd: opts.cwd, env: opts.env, stdout: "pipe", stderr: "pipe" });
64
+ const proc = Bun.spawn([resolveCodexBin(), ...args], {
65
+ cwd: opts.cwd,
66
+ env: opts.env,
67
+ stdout: "pipe",
68
+ stderr: "pipe",
69
+ });
37
70
  return {
38
71
  stdout: proc.stdout as ReadableStream<Uint8Array>,
39
72
  stderr: proc.stderr as ReadableStream<Uint8Array>,