niahere 0.4.5 → 0.4.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": "niahere",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -6,6 +6,7 @@ import type { Attachment } from "../../types/attachment";
6
6
  import type { McpSourceContext } from "../../mcp";
7
7
  import { CodexNormalizer } from "./codex-normalize";
8
8
  import { mintRun, revokeRun } from "../mcp-endpoint";
9
+ import { isCliProviderDownError } from "../../utils/retry";
9
10
 
10
11
  /**
11
12
  * Resolve the codex binary's absolute path. The daemon runs under launchd with a
@@ -188,7 +189,7 @@ class CodexSession implements AgentSession {
188
189
  type: "error",
189
190
  message: stderr.trim() || `codex exited ${exit}`,
190
191
  retryable: false,
191
- providerDown: false,
192
+ providerDown: isCliProviderDownError(stderr),
192
193
  };
193
194
  }
194
195
  } finally {
@@ -3,9 +3,25 @@ import { runJob } from "./runner";
3
3
  import { getConfig } from "../utils/config";
4
4
  import { log } from "../utils/log";
5
5
  import { computeInitialNextRun, computeNextRun } from "../utils/schedule";
6
+ import type { JobResult } from "../types";
6
7
 
7
8
  export { computeInitialNextRun, computeNextRun };
8
9
 
10
+ /**
11
+ * Log a finished job at a level matching its outcome. `runJob` resolves with
12
+ * `status: "error"` instead of throwing, so the caller's `.catch` only ever sees
13
+ * an infrastructure fault — a job that ran and failed must be branched on here
14
+ * or it is indistinguishable from success in the log.
15
+ */
16
+ export function logJobOutcome(result: JobResult): void {
17
+ const fields = { job: result.job, status: result.status, duration: result.duration_ms };
18
+ if (result.status === "error") {
19
+ log.error({ ...fields, error: result.error, terminal_reason: result.terminal_reason }, "scheduler: job failed");
20
+ return;
21
+ }
22
+ log.info(fields, "scheduler: job completed");
23
+ }
24
+
9
25
  function isWithinActiveHours(): boolean {
10
26
  const config = getConfig();
11
27
  const { start, end } = config.activeHours;
@@ -57,11 +73,9 @@ async function tick(): Promise<void> {
57
73
  runningJobs.add(job.name);
58
74
 
59
75
  runJob(job)
60
- .then((result) => {
61
- log.info({ job: job.name, status: result.status, duration: result.duration_ms }, "scheduler: job completed");
62
- })
76
+ .then(logJobOutcome)
63
77
  .catch((err) => {
64
- log.error({ err, job: job.name }, "scheduler: job failed");
78
+ log.error({ err, job: job.name }, "scheduler: job crashed");
65
79
  })
66
80
  .finally(() => {
67
81
  runningJobs.delete(job.name);
@@ -31,6 +31,34 @@ export function isProviderDownError(error: string | null | undefined): boolean {
31
31
  return !trimmed || trimmed.toLowerCase() === "unknown error";
32
32
  }
33
33
 
34
+ const CLI_PROVIDER_DOWN_PATTERNS = [
35
+ /authenticat/i,
36
+ /unauthoriz/i,
37
+ /\b(401|403)\b/,
38
+ /not logged in/i,
39
+ /\blogin\b/i,
40
+ /\bcredentials?\b/i,
41
+ /\bapi key\b/i,
42
+ /(session|token) expired/i,
43
+ /\b(econnrefused|enotfound|etimedout|econnreset)\b/i,
44
+ /connection (refused|reset|timed out)/i,
45
+ /network (error|unreachable)/i,
46
+ /failed to refresh available models/i,
47
+ ];
48
+
49
+ /**
50
+ * Provider-down classification for CLI-subprocess backends (codex), whose
51
+ * failures surface as free-form stderr rather than the Claude SDK's structured
52
+ * error. Matches the ways a CLI reports "I could not reach or authenticate with
53
+ * the provider" — as opposed to a task that ran and genuinely failed, which must
54
+ * stay a real error so the chain does not burn a second backend replaying it.
55
+ */
56
+ export function isCliProviderDownError(stderr: string | null | undefined): boolean {
57
+ const trimmed = stderr?.trim();
58
+ if (!trimmed) return true;
59
+ return CLI_PROVIDER_DOWN_PATTERNS.some((p) => p.test(trimmed));
60
+ }
61
+
34
62
  /** Sleep for ms milliseconds. */
35
63
  export function sleep(ms: number): Promise<void> {
36
64
  return new Promise((r) => setTimeout(r, ms));