@arcforge/err 2.0.120 → 2.0.121

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": "@arcforge/err",
3
- "version": "2.0.120",
3
+ "version": "2.0.121",
4
4
  "description": "Structured Axon errors — the code map every user-facing failure is rendered from.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -15,7 +15,7 @@
15
15
  "deploy": "echo \"This package is published ONLY by apps/tui/scripts/release.ts, which pins workspace:* deps to concrete versions first. Publishing it directly ships an unresolvable dependency.\" && exit 1"
16
16
  },
17
17
  "dependencies": {
18
- "@arcforge/types": "2.0.120"
18
+ "@arcforge/types": "2.0.121"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/bun": "latest",
package/src/err.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { errorMap, type AxonErrorCode, type AxonErrorMap } from "./map"
1
+ import { errorMap, type AxonErrorCode, type AxonErrorMap, type AxonErrorMapEntry } from "./map"
2
2
  import { captureStack, firstRealFrame } from "./stack"
3
3
  import { renderError } from "./render"
4
4
  import { emitError } from "./sink"
@@ -56,7 +56,10 @@ export function err(codeOrCause: AxonErrorCode | unknown, opts?: AxonErrorOpts):
56
56
  }
57
57
 
58
58
  const code = codeOrCause as AxonErrorCode
59
- const def = (errorMap as AxonErrorMap)[code]
59
+ // Read through the ENTRY type, not the literal map type: `as const` keeps
60
+ // each entry's exact shape, so optional fields are absent from the type of
61
+ // entries that omit them and unreadable off the union.
62
+ const def: AxonErrorMapEntry = (errorMap as AxonErrorMap)[code]
60
63
 
61
64
  const e = new Error(opts?.detail ?? def.title, { cause: opts?.cause }) as AxonError
62
65
 
@@ -65,6 +68,9 @@ export function err(codeOrCause: AxonErrorCode | unknown, opts?: AxonErrorOpts):
65
68
  e.description = def.description
66
69
  e.source = def.source
67
70
  e.severity = opts?.severity ?? def.severity
71
+ // Carried onto the instance so renderers (and the CLI's own catch) can ask
72
+ // whose fault this is without reaching back into the map.
73
+ if (def.expected) e.expected = true
68
74
  e.context = opts?.context
69
75
  e.frames = captureStack(2) // drop captureStack's own frame + err()'s
70
76
  e.isAxonError = true
package/src/map.ts CHANGED
@@ -33,6 +33,25 @@ export type AxonErrorMapEntry = {
33
33
  description: string
34
34
  source: AxonErrorSource
35
35
  severity: AxonErrorSeverity
36
+ /**
37
+ * Is this a failure the USER caused and can fix, rather than a fault in
38
+ * our code?
39
+ *
40
+ * `axon publish` outside a project directory is a typo, not a bug — the
41
+ * title and description already say everything actionable, so a renderer
42
+ * showing eighty lines of our internals underneath is telling the user to
43
+ * debug software they did not write. Marking it `expected` renders the
44
+ * headline alone.
45
+ *
46
+ * Deliberately NOT `severity`, which answers a different question:
47
+ * PROJECT_NOT_FOUND is genuinely `fatal` (the command cannot continue) AND
48
+ * genuinely expected. Recoverability and blame are independent axes.
49
+ *
50
+ * Absent means unexpected — the safe default. An unclassified failure gets
51
+ * the full trace, which is what makes it debuggable; the only cost of
52
+ * forgetting this flag is a noisier message, never a hidden bug.
53
+ */
54
+ expected?: true
36
55
  }
37
56
 
38
57
  /**
@@ -52,6 +71,7 @@ export const errorMap = {
52
71
  description: "The requested prompt could not be resolved — it is not declared by the agent, or the published package it names ships no prompt by that name.",
53
72
  source: "runtime",
54
73
  severity: "fatal",
74
+ expected: true,
55
75
  },
56
76
  PROMPT_FILE_NOT_FOUND: {
57
77
  code: "AX-PROMPT-002",
@@ -59,6 +79,7 @@ export const errorMap = {
59
79
  description: "The prompt is declared but its source file no longer exists on disk.",
60
80
  source: "runtime",
61
81
  severity: "fatal",
82
+ expected: true,
62
83
  },
63
84
  PROMPT_RENDER_FAILED: {
64
85
  code: "AX-PROMPT-003",
@@ -87,6 +108,7 @@ export const errorMap = {
87
108
  description: "The requested script was not found in the agent's blueprint. It may not be declared, or the agent needs to be re-prepared.",
88
109
  source: "runtime",
89
110
  severity: "fatal",
111
+ expected: true,
90
112
  },
91
113
  SCRIPT_FILE_NOT_FOUND: {
92
114
  code: "AX-SCRIPT-002",
@@ -94,6 +116,7 @@ export const errorMap = {
94
116
  description: "The script is declared but its source file no longer exists on disk.",
95
117
  source: "runtime",
96
118
  severity: "fatal",
119
+ expected: true,
97
120
  },
98
121
  ENGINE_MISSING: {
99
122
  code: "AX-ENGINE-001",
@@ -236,6 +259,7 @@ export const errorMap = {
236
259
  description: "The cognet named by `cognet:` in axon.config.ts could not be located. The detail says where it was looked for — a registry that answered 404, or a node_modules tree it was missing from. Check the specifier for a typo, and check which registry the CLI is pointed at (AXON_API_BASE).",
237
260
  source: "cognet",
238
261
  severity: "fatal",
262
+ expected: true,
239
263
  },
240
264
 
241
265
  // ── Project (build/project/) ────────────────────────────────────────────
@@ -303,6 +327,7 @@ export const errorMap = {
303
327
  description: "The selected model routes through a provider (OpenRouter, Codex) that isn't connected yet — connect it before picking a model on that route.",
304
328
  source: "tui",
305
329
  severity: "fatal",
330
+ expected: true,
306
331
  },
307
332
  PALETTE_INPUT_REQUIRED: {
308
333
  code: "AX-TUI-012",
@@ -310,6 +335,7 @@ export const errorMap = {
310
335
  description: "A palette command needs a value for this field before it can run.",
311
336
  source: "tui",
312
337
  severity: "fatal",
338
+ expected: true,
313
339
  },
314
340
  BENCH_NO_CASES: {
315
341
  code: "AX-TUI-013",
@@ -326,6 +352,7 @@ export const errorMap = {
326
352
  description: "No axon.config.ts, module.config.ts, cognet.config.ts, bench.config.ts, or prompt.config.ts was found at this path — it isn't a recognized project directory.",
327
353
  source: "manifest",
328
354
  severity: "fatal",
355
+ expected: true,
329
356
  },
330
357
  PROJECT_WRONG_KIND: {
331
358
  code: "AX-PROJECT-011",
@@ -333,6 +360,7 @@ export const errorMap = {
333
360
  description: "This command targets one project kind, and the directory holds another — run the command that matches what is actually here.",
334
361
  source: "manifest",
335
362
  severity: "fatal",
363
+ expected: true,
336
364
  },
337
365
  PROJECT_EXISTS: {
338
366
  code: "AX-PROJECT-018",
@@ -468,6 +496,7 @@ export const errorMap = {
468
496
  description: "No recorded bench run exists with this id.",
469
497
  source: "bench",
470
498
  severity: "fatal",
499
+ expected: true,
471
500
  },
472
501
  BENCH_NOT_FOUND: {
473
502
  code: "AX-BENCH-002",
@@ -475,6 +504,7 @@ export const errorMap = {
475
504
  description: "No bench.config.ts was found at this path — it isn't a recognized bench project.",
476
505
  source: "bench",
477
506
  severity: "fatal",
507
+ expected: true,
478
508
  },
479
509
  BENCH_TESTS_NOT_FOUND: {
480
510
  code: "AX-BENCH-003",
@@ -482,6 +512,7 @@ export const errorMap = {
482
512
  description: "The bench config declares test files that don't exist on disk.",
483
513
  source: "bench",
484
514
  severity: "fatal",
515
+ expected: true,
485
516
  },
486
517
  BENCH_LOCAL_REF_NOT_FOUND: {
487
518
  code: "AX-BENCH-004",
@@ -489,6 +520,7 @@ export const errorMap = {
489
520
  description: "A factor variable references a local file/directory that doesn't exist.",
490
521
  source: "bench",
491
522
  severity: "fatal",
523
+ expected: true,
492
524
  },
493
525
  BENCH_CONFIG_INVALID: {
494
526
  code: "AX-BENCH-005",
@@ -503,6 +535,7 @@ export const errorMap = {
503
535
  description: "No package.json exists at the bench root — every bench project needs one for identity.",
504
536
  source: "bench",
505
537
  severity: "fatal",
538
+ expected: true,
506
539
  },
507
540
  BENCH_PACKAGE_NAME_REQUIRED: {
508
541
  code: "AX-BENCH-007",
@@ -510,6 +543,7 @@ export const errorMap = {
510
543
  description: "The bench's package.json has no name field.",
511
544
  source: "bench",
512
545
  severity: "fatal",
546
+ expected: true,
513
547
  },
514
548
  BENCH_PACKAGE_VERSION_REQUIRED: {
515
549
  code: "AX-BENCH-008",
@@ -517,6 +551,7 @@ export const errorMap = {
517
551
  description: "The bench's package.json has no version field.",
518
552
  source: "bench",
519
553
  severity: "fatal",
554
+ expected: true,
520
555
  },
521
556
  BENCH_CONTEXT_MISSING: {
522
557
  code: "AX-BENCH-009",
@@ -636,6 +671,7 @@ export const errorMap = {
636
671
  description: "The bench config's matrix has no axis with this key.",
637
672
  source: "bench",
638
673
  severity: "fatal",
674
+ expected: true,
639
675
  },
640
676
  BENCH_AXIS_VALUE_NOT_FOUND: {
641
677
  code: "AX-BENCH-022",
@@ -643,6 +679,7 @@ export const errorMap = {
643
679
  description: "The bench config's matrix axis has no declared value with this id.",
644
680
  source: "bench",
645
681
  severity: "fatal",
682
+ expected: true,
646
683
  },
647
684
  BENCH_WORKSPACE_ESCAPE: {
648
685
  code: "AX-BENCH-023",
@@ -671,6 +708,7 @@ export const errorMap = {
671
708
  description: "A workspace template's declared source path isn't a real directory.",
672
709
  source: "bench",
673
710
  severity: "fatal",
711
+ expected: true,
674
712
  },
675
713
  BENCH_LOG_INVALID: {
676
714
  code: "AX-BENCH-027",
@@ -736,6 +774,7 @@ export const errorMap = {
736
774
  description: "No axon.config.ts exists at this path.",
737
775
  source: "manifest",
738
776
  severity: "fatal",
777
+ expected: true,
739
778
  },
740
779
  CONFIG_LOAD_FAILED: {
741
780
  code: "AX-BLUEPRINT-004",
@@ -773,6 +812,7 @@ export const errorMap = {
773
812
  description: "This agent uses a Codex model, but your Axon account is not connected to ChatGPT — run :provider codex connect and try again.",
774
813
  source: "kernel",
775
814
  severity: "fatal",
815
+ expected: true,
776
816
  },
777
817
  RUN_IN_PROGRESS: {
778
818
  code: "AX-KERNEL-002",
@@ -838,6 +878,7 @@ export const errorMap = {
838
878
  description: "The requested thread id isn't registered in this session.",
839
879
  source: "thread",
840
880
  severity: "fatal",
881
+ expected: true,
841
882
  },
842
883
  THREAD_BRANCH_UNKNOWN: {
843
884
  code: "AX-SESSION-003",
@@ -873,6 +914,7 @@ export const errorMap = {
873
914
  description: "A control-channel call named a method the peer does not expose. The two ends disagree about the surface — usually a TUI and an extension built from different versions.",
874
915
  source: "server",
875
916
  severity: "fatal",
917
+ expected: true,
876
918
  },
877
919
  CONTROL_PATH_NOT_CALLABLE: {
878
920
  code: "AX-CONTROL-002",
@@ -1096,6 +1138,7 @@ export const errorMap = {
1096
1138
  description: "The action needs an active profile, but none is logged in yet.",
1097
1139
  source: "tui",
1098
1140
  severity: "fatal",
1141
+ expected: true,
1099
1142
  },
1100
1143
  PROFILE_NOT_AUTHENTICATED: {
1101
1144
  code: "AX-TUI-003",
@@ -1103,6 +1146,15 @@ export const errorMap = {
1103
1146
  description: "The target profile exists but has no stored session — it has never logged in, or its session was cleared.",
1104
1147
  source: "tui",
1105
1148
  severity: "fatal",
1149
+ expected: true,
1150
+ },
1151
+ BACKEND_UNREACHABLE: {
1152
+ code: "AX-TUI-044",
1153
+ title: "Backend Unreachable",
1154
+ description: "A stored credential could not be verified because the backend could not be reached. The credential is NOT discarded — it may be perfectly valid — but it cannot be trusted until it is checked, so the action is refused rather than proceeding unverified.",
1155
+ source: "tui",
1156
+ severity: "fatal",
1157
+ expected: true,
1106
1158
  },
1107
1159
  PROFILE_UNKNOWN: {
1108
1160
  code: "AX-TUI-004",
@@ -1110,6 +1162,7 @@ export const errorMap = {
1110
1162
  description: "The requested profile id isn't one of the profiles stored on this machine.",
1111
1163
  source: "tui",
1112
1164
  severity: "fatal",
1165
+ expected: true,
1113
1166
  },
1114
1167
  SESSION_ALREADY_RUNNING: {
1115
1168
  code: "AX-TUI-005",
@@ -1117,6 +1170,7 @@ export const errorMap = {
1117
1170
  description: "spawn() was asked to resume a session that already has a live instance. Focus the running instance instead of booting a second runtime over the same log.",
1118
1171
  source: "tui",
1119
1172
  severity: "fatal",
1173
+ expected: true,
1120
1174
  },
1121
1175
  SESSION_NOT_RUNNING: {
1122
1176
  code: "AX-TUI-016",
@@ -1124,6 +1178,15 @@ export const errorMap = {
1124
1178
  description: "focus() was pointed at a sessionId with no live instance behind it. Spawn (or resume) it first — focus is pure selection over running instances.",
1125
1179
  source: "tui",
1126
1180
  severity: "fatal",
1181
+ expected: true,
1182
+ },
1183
+ NO_ACTIVE_AGENT: {
1184
+ code: "AX-TUI-045",
1185
+ title: "No Active Agent",
1186
+ description: "The action needs a running agent, but none is active — start one first.",
1187
+ source: "tui",
1188
+ severity: "fatal",
1189
+ expected: true,
1127
1190
  },
1128
1191
  NO_FOCUSED_INSTANCE: {
1129
1192
  code: "AX-TUI-017",
@@ -1131,6 +1194,7 @@ export const errorMap = {
1131
1194
  description: "A module install/uninstall was requested with no running agent instance focused — spawn or focus one first.",
1132
1195
  source: "tui",
1133
1196
  severity: "fatal",
1197
+ expected: true,
1134
1198
  },
1135
1199
  MODEL_IMMUTABLE_DEPLOYED: {
1136
1200
  code: "AX-TUI-006",
@@ -1138,6 +1202,7 @@ export const errorMap = {
1138
1202
  description: "A deployed agent's config lives in the cloud, not on this machine — its model is fixed at deploy time. Change it in the local project and deploy again.",
1139
1203
  source: "tui",
1140
1204
  severity: "fatal",
1205
+ expected: true,
1141
1206
  },
1142
1207
  MODULE_INSTALL_FAILED: {
1143
1208
  code: "AX-TUI-018",
@@ -1215,6 +1280,7 @@ export const errorMap = {
1215
1280
  description: "`axon watch`/`axon unwatch` need a directory argument.",
1216
1281
  source: "tui",
1217
1282
  severity: "fatal",
1283
+ expected: true,
1218
1284
  },
1219
1285
  WATCH_PATH_NOT_FOUND: {
1220
1286
  code: "AX-TUI-022",
@@ -1222,6 +1288,7 @@ export const errorMap = {
1222
1288
  description: "`axon watch` was given a directory that doesn't exist on disk.",
1223
1289
  source: "tui",
1224
1290
  severity: "fatal",
1291
+ expected: true,
1225
1292
  },
1226
1293
  EDITOR_NOT_SET: {
1227
1294
  code: "AX-TUI-023",
@@ -1243,6 +1310,7 @@ export const errorMap = {
1243
1310
  description: "The init palette's \"watch a new directory\" entry needs both a directory path and an agent name, space-separated.",
1244
1311
  source: "tui",
1245
1312
  severity: "fatal",
1313
+ expected: true,
1246
1314
  },
1247
1315
 
1248
1316
  // ── Module boot-time execution (core runs defineModule setup) ────────────
@@ -1287,6 +1355,7 @@ export const errorMap = {
1287
1355
  description: "A module's setup() called ctx.env.require() for a variable the agent's resolved environment does not provide. The module declares required env in module.config.ts; the agent must supply it (e.g. in .env).",
1288
1356
  source: "runtime",
1289
1357
  severity: "fatal",
1358
+ expected: true,
1290
1359
  },
1291
1360
  MODULE_POLICY_IMMUTABLE: {
1292
1361
  code: "AX-MODULE-006",
@@ -1294,6 +1363,7 @@ export const errorMap = {
1294
1363
  description: "A module's setup() called ctx.policy.update(). The resolved agent policy is authoritative and cannot be mutated at boot — declare policy needs statically in module.config.ts so the CLI reconciles them at install.",
1295
1364
  source: "runtime",
1296
1365
  severity: "fatal",
1366
+ expected: true,
1297
1367
  },
1298
1368
 
1299
1369
  // ── Registry retrieval (build/registry.ts) ──────────────────────────────
@@ -1303,6 +1373,7 @@ export const errorMap = {
1303
1373
  description: "`axon clone` was run without an artifact to clone. Name one, for example: axon clone @axon/arxiv",
1304
1374
  source: "cli",
1305
1375
  severity: "fatal",
1376
+ expected: true,
1306
1377
  },
1307
1378
  FORK_REF_REQUIRED: {
1308
1379
  code: "AX-PROJECT-023",
package/src/render.ts CHANGED
@@ -20,6 +20,8 @@ export type AxonErrorLike = {
20
20
  context: Record<string, unknown> | undefined
21
21
  frames: AxonStackFrame[]
22
22
  cause?: unknown
23
+ /** The user caused this and can fix it — renders without our internals. See map.ts. */
24
+ expected?: boolean
23
25
  }
24
26
 
25
27
  /** One frame, Rust-style: file:line:col, then its captured source context with a caret. */
@@ -37,7 +39,23 @@ export function renderFrame(frame: AxonStackFrame): string {
37
39
 
38
40
  const RULE_WIDTH = 80
39
41
 
40
- /** The full renderable report: headline + description, a rule, then every frame and the cause chain. Blank line before and after the whole block. */
42
+ /**
43
+ * The renderable report.
44
+ *
45
+ * Two shapes, chosen by whose fault the failure is:
46
+ *
47
+ * EXPECTED headline + description + context. `axon publish` outside a
48
+ * project is a typo; the description already says exactly what
49
+ * to do, and eighty lines of our call stack underneath tells the
50
+ * user to debug software they did not write.
51
+ *
52
+ * otherwise the full report — frames, source snippets, cause chain. An
53
+ * unclassified failure IS ours, and this is what makes it
54
+ * debuggable from a pasted terminal log.
55
+ *
56
+ * The default is the full report (see map.ts's `expected`), so forgetting to
57
+ * classify a code costs a noisy message and never a hidden bug.
58
+ */
41
59
  export function renderError(error: AxonErrorLike): string {
42
60
  const lines = [`Axon Error: ${error.title}`, error.description]
43
61
 
@@ -49,8 +67,17 @@ export function renderError(error: AxonErrorLike): string {
49
67
  lines.push("", "Context:", indent(renderContext(error.context)))
50
68
  }
51
69
 
52
- lines.push("─".repeat(RULE_WIDTH), ...error.frames.map(renderFrame))
70
+ // OUR frames are what an expected failure omits — they describe code the
71
+ // user did not write and cannot act on.
72
+ if (!error.expected) {
73
+ lines.push("─".repeat(RULE_WIDTH), ...error.frames.map(renderFrame))
74
+ }
53
75
 
76
+ // A cause survives either way: something deliberately attached it, and it
77
+ // names the underlying fault ("ECONNREFUSED" under a missing file), which
78
+ // is the most actionable line in the whole report. Suppressing it would
79
+ // leave the user reading "the file is missing" with no hint that the real
80
+ // problem was the network.
54
81
  if (error.cause !== undefined) {
55
82
  lines.push("", "Caused by:", indent(causeMessage(error.cause)))
56
83
  }