@crewhaus/errors 0.2.4 → 0.3.0

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/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export declare class CompilerError extends CrewhausError {
31
31
  constructor(message: string, cause?: unknown);
32
32
  }
33
33
  export declare class RuntimeError extends CrewhausError {
34
- readonly name = "RuntimeError";
34
+ readonly name: string;
35
35
  constructor(message: string, cause?: unknown);
36
36
  }
37
37
  export declare class ConfigError extends CrewhausError {
@@ -69,3 +69,95 @@ export declare class ProviderAuthError extends AdapterError {
69
69
  readonly name = "ProviderAuthError";
70
70
  constructor(providerId: string, message: string, cause?: unknown);
71
71
  }
72
+ /**
73
+ * Terminal failure classes a run can die with. Broader than `ErrorCode`
74
+ * (which tags where an error was raised): a `FailureClass` tags *why the run
75
+ * stopped*, in end-user vocabulary, and maps 1:1 onto an exit code.
76
+ */
77
+ export type FailureClass = "billing" | "auth" | "rate_limit" | "crewhaus_budget" | "mcp_boot" | "context_overflow" | "spec" | "config" | "tool" | "unknown";
78
+ /**
79
+ * The single structured description of a terminal run failure. `detail`
80
+ * carries the raw provider text (with attribution, e.g. `Anthropic said:
81
+ * "…"`); `remediation` is the one actionable next step; `exitCode` comes
82
+ * from `EXIT_CODES` so fleet / the UI host can dispatch without parsing.
83
+ */
84
+ export type FailureReport = {
85
+ readonly class: FailureClass;
86
+ readonly title: string;
87
+ readonly detail: string;
88
+ readonly remediation?: string;
89
+ readonly exitCode: number;
90
+ readonly docsUrl?: string;
91
+ };
92
+ /**
93
+ * Process exit-code table (documented in CLI-REFERENCE). Everything fatal
94
+ * used to be exit 1; these codes let `fleet run` and the UI host tell
95
+ * "account out of funding" from "bad spec" from "tool crash".
96
+ */
97
+ export declare const EXIT_CODES: {
98
+ /** Clean run. */
99
+ readonly ok: 0;
100
+ /** Unclassified fatal error (the pre-0.3.0 catch-all). */
101
+ readonly generic: 1;
102
+ /** Spec parse / validation failure. */
103
+ readonly spec: 20;
104
+ /** Config / missing-env failure. */
105
+ readonly config: 21;
106
+ /** Provider rejected the credentials at runtime (401/403). */
107
+ readonly auth: 30;
108
+ /** Provider account out of funding (credit balance, insufficient_quota, 402). */
109
+ readonly billing: 31;
110
+ /** Provider quota / rate limit exhausted after retries. */
111
+ readonly rate_limit: 32;
112
+ /** CrewHaus's own configured budget cap ended the run. */
113
+ readonly crewhaus_budget: 33;
114
+ /** Tool or MCP failure (including MCP boot). */
115
+ readonly tool: 40;
116
+ };
117
+ /**
118
+ * Terminal error carrying a `FailureReport`. Thrown by runtime-core when the
119
+ * recovery engine returns a `halt` action. Extends `RuntimeError` so every
120
+ * existing `instanceof CrewhausError` catch (e.g. `crewhaus run`'s die()
121
+ * routing) keeps working unchanged; PR 3 upgrades those catch sites to
122
+ * render the report and exit with `report.exitCode`.
123
+ */
124
+ export declare class RunFailedError extends RuntimeError {
125
+ readonly name = "RunFailedError";
126
+ readonly report: FailureReport;
127
+ constructor(report: FailureReport, cause?: unknown);
128
+ }
129
+ /**
130
+ * Render a `FailureReport` as the canonical multi-line failure message:
131
+ *
132
+ * ✗ run stopped — provider account out of funding
133
+ * Anthropic said: "Your credit balance is too low to access the Anthropic API."
134
+ * Fix: add credits at https://console.anthropic.com/settings/billing, then rerun.
135
+ * (exit 31)
136
+ *
137
+ * `opts.prefix` replaces the leading `✗` (e.g. `crewhaus:` for the CLI).
138
+ * `opts.notes` are surface-specific trailing lines rendered after Fix/Docs
139
+ * and before the exit line (e.g. `crewhaus run`'s "Your session is saved —
140
+ * …" resume hint). Dependency-free by design — emitted bundles inline-import
141
+ * this.
142
+ */
143
+ export declare function formatRunFailure(report: FailureReport, opts?: {
144
+ prefix?: string;
145
+ notes?: readonly string[];
146
+ }): string;
147
+ /**
148
+ * Is `err` a RunFailedError (or a structurally identical wire twin)? The
149
+ * duck-typed fallback matters for compiled bundles: a bundle and the runtime
150
+ * it embeds can end up with two copies of this package (duplicated install,
151
+ * bundler realm split), in which case `instanceof` lies but the shape —
152
+ * `name === "RunFailedError"` plus a report with a numeric `exitCode` —
153
+ * does not.
154
+ */
155
+ export declare function isRunFailedError(err: unknown): err is RunFailedError;
156
+ /**
157
+ * Coerce any thrown value into a `FailureReport`: a RunFailedError yields
158
+ * its own classified report; everything else synthesizes a best-effort
159
+ * generic report (class `"unknown"`, exit {@link EXIT_CODES.generic}) so
160
+ * every terminal surface — compiled bundles' catch wrappers, daemon mains,
161
+ * `crewhaus run` — renders ONE shape regardless of what escaped.
162
+ */
163
+ export declare function toFailureReport(err: unknown): FailureReport;
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ export class CompilerError extends CrewhausError {
44
44
  }
45
45
  }
46
46
  export class RuntimeError extends CrewhausError {
47
+ // `: string` (not the literal) so subclasses (RunFailedError) can narrow.
47
48
  name = "RuntimeError";
48
49
  constructor(message, cause) {
49
50
  super("runtime", message, cause);
@@ -100,3 +101,128 @@ export class ProviderAuthError extends AdapterError {
100
101
  super(providerId, message, cause);
101
102
  }
102
103
  }
104
+ /**
105
+ * Process exit-code table (documented in CLI-REFERENCE). Everything fatal
106
+ * used to be exit 1; these codes let `fleet run` and the UI host tell
107
+ * "account out of funding" from "bad spec" from "tool crash".
108
+ */
109
+ export const EXIT_CODES = {
110
+ /** Clean run. */
111
+ ok: 0,
112
+ /** Unclassified fatal error (the pre-0.3.0 catch-all). */
113
+ generic: 1,
114
+ /** Spec parse / validation failure. */
115
+ spec: 20,
116
+ /** Config / missing-env failure. */
117
+ config: 21,
118
+ /** Provider rejected the credentials at runtime (401/403). */
119
+ auth: 30,
120
+ /** Provider account out of funding (credit balance, insufficient_quota, 402). */
121
+ billing: 31,
122
+ /** Provider quota / rate limit exhausted after retries. */
123
+ rate_limit: 32,
124
+ /** CrewHaus's own configured budget cap ended the run. */
125
+ crewhaus_budget: 33,
126
+ /** Tool or MCP failure (including MCP boot). */
127
+ tool: 40,
128
+ };
129
+ /**
130
+ * Terminal error carrying a `FailureReport`. Thrown by runtime-core when the
131
+ * recovery engine returns a `halt` action. Extends `RuntimeError` so every
132
+ * existing `instanceof CrewhausError` catch (e.g. `crewhaus run`'s die()
133
+ * routing) keeps working unchanged; PR 3 upgrades those catch sites to
134
+ * render the report and exit with `report.exitCode`.
135
+ */
136
+ export class RunFailedError extends RuntimeError {
137
+ name = "RunFailedError";
138
+ report;
139
+ constructor(report, cause) {
140
+ super(`run stopped — ${report.title}: ${report.detail}`, cause);
141
+ this.report = report;
142
+ }
143
+ }
144
+ /**
145
+ * Render a `FailureReport` as the canonical multi-line failure message:
146
+ *
147
+ * ✗ run stopped — provider account out of funding
148
+ * Anthropic said: "Your credit balance is too low to access the Anthropic API."
149
+ * Fix: add credits at https://console.anthropic.com/settings/billing, then rerun.
150
+ * (exit 31)
151
+ *
152
+ * `opts.prefix` replaces the leading `✗` (e.g. `crewhaus:` for the CLI).
153
+ * `opts.notes` are surface-specific trailing lines rendered after Fix/Docs
154
+ * and before the exit line (e.g. `crewhaus run`'s "Your session is saved —
155
+ * …" resume hint). Dependency-free by design — emitted bundles inline-import
156
+ * this.
157
+ */
158
+ export function formatRunFailure(report, opts) {
159
+ const prefix = opts?.prefix ?? "✗";
160
+ const lines = [`${prefix} run stopped — ${report.title}`];
161
+ if (report.detail.length > 0) {
162
+ for (const detailLine of report.detail.split("\n")) {
163
+ lines.push(` ${detailLine}`);
164
+ }
165
+ }
166
+ if (report.remediation !== undefined && report.remediation.length > 0) {
167
+ lines.push(` Fix: ${report.remediation}`);
168
+ }
169
+ if (report.docsUrl !== undefined && report.docsUrl.length > 0) {
170
+ lines.push(` Docs: ${report.docsUrl}`);
171
+ }
172
+ for (const note of opts?.notes ?? []) {
173
+ lines.push(` ${note}`);
174
+ }
175
+ lines.push(` (exit ${report.exitCode})`);
176
+ return lines.join("\n");
177
+ }
178
+ /**
179
+ * Is `err` a RunFailedError (or a structurally identical wire twin)? The
180
+ * duck-typed fallback matters for compiled bundles: a bundle and the runtime
181
+ * it embeds can end up with two copies of this package (duplicated install,
182
+ * bundler realm split), in which case `instanceof` lies but the shape —
183
+ * `name === "RunFailedError"` plus a report with a numeric `exitCode` —
184
+ * does not.
185
+ */
186
+ export function isRunFailedError(err) {
187
+ if (err instanceof RunFailedError)
188
+ return true;
189
+ const duck = err;
190
+ return (duck !== null &&
191
+ typeof duck === "object" &&
192
+ duck.name === "RunFailedError" &&
193
+ typeof duck.report === "object" &&
194
+ duck.report !== null &&
195
+ typeof duck.report.exitCode === "number");
196
+ }
197
+ /**
198
+ * Coerce any thrown value into a `FailureReport`: a RunFailedError yields
199
+ * its own classified report; everything else synthesizes a best-effort
200
+ * generic report (class `"unknown"`, exit {@link EXIT_CODES.generic}) so
201
+ * every terminal surface — compiled bundles' catch wrappers, daemon mains,
202
+ * `crewhaus run` — renders ONE shape regardless of what escaped.
203
+ */
204
+ export function toFailureReport(err) {
205
+ if (isRunFailedError(err))
206
+ return err.report;
207
+ const detail = err instanceof Error ? err.message : String(err);
208
+ // A `ConfigError` (missing env var, unresolvable secret ref — e.g. the
209
+ // thredz boot's THREDZ_API_KEY resolution, §4.4) is a classified failure
210
+ // with its own exit code (21), not the generic catch-all. Duck-typed on
211
+ // the stable `code` field so a realm-split copy of this package still
212
+ // classifies.
213
+ const code = err?.code;
214
+ if (err instanceof Error && code === "config") {
215
+ return {
216
+ class: "config",
217
+ title: "configuration error",
218
+ detail,
219
+ exitCode: EXIT_CODES.config,
220
+ };
221
+ }
222
+ return {
223
+ class: "unknown",
224
+ title: "unexpected error",
225
+ detail,
226
+ exitCode: EXIT_CODES.generic,
227
+ };
228
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/errors",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Typed error hierarchy shared across factory layers",
6
6
  "main": "dist/index.js",