@boardwalk-labs/runner 0.2.7 → 0.2.9

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.
@@ -91,6 +91,7 @@ export type ProgramResult = {
91
91
  error: {
92
92
  code: string;
93
93
  message: string;
94
+ hint?: string;
94
95
  };
95
96
  };
96
97
  /**
@@ -100,6 +100,36 @@ export function assertProgramRootOutsideWorkspace(programRoot, workspaceRoot) {
100
100
  throw new AppError(ErrorCode.VALIDATION_FAILED, `The program root "${programRoot}" is inside the workspace "${workspaceRoot}"; the extracted program must live outside it.`);
101
101
  }
102
102
  }
103
+ /** An engine `EngineError` carries a one-line actionable `hint` alongside its message. It reaches us
104
+ * as a thrown value across the SDK/engine package boundary, so DUCK-TYPE it rather than `instanceof`
105
+ * (a dual-package copy of the class would defeat the check) — any thrown error carrying a non-empty
106
+ * string `hint` is surfaced. Without this the hint is dropped here, and on a hosted run there is no
107
+ * other place it could survive: the runner reports a failure as `{ code, message, hint? }` and the
108
+ * broker persists exactly that. */
109
+ function errorHint(err) {
110
+ if (typeof err !== "object" || err === null)
111
+ return undefined;
112
+ const hint = err.hint;
113
+ return typeof hint === "string" && hint !== "" ? hint : undefined;
114
+ }
115
+ /** A machine-readable error code shaped like one: SCREAMING_SNAKE, as an engine `EngineError.code`
116
+ * (`VALIDATION`, `PROVIDER_ERROR`, …) and a Node syscall error (`ENOENT`) both are. */
117
+ const ERROR_CODE_RE = /^[A-Z][A-Z0-9_]{0,63}$/;
118
+ /**
119
+ * The SEMANTIC code of a thrown error, preferred over its class name. An engine `EngineError` carries
120
+ * `code: "VALIDATION"`; reporting `err.name` instead surfaced the useless `"EngineError"` on every
121
+ * hosted failure (and `"Error"` for anything else). Duck-typed for the same reason as {@link errorHint}.
122
+ *
123
+ * Two guards, because a thrown value is author-controlled: the SCREAMING_SNAKE shape keeps prose (or a
124
+ * stray object field) out of a field the UI renders as a code, and the CALLER redacts the result — an
125
+ * uppercase-alnum secret would satisfy the pattern, so the shape check is hygiene, not the boundary.
126
+ */
127
+ function errorCode(err) {
128
+ if (typeof err !== "object" || err === null)
129
+ return undefined;
130
+ const code = err.code;
131
+ return typeof code === "string" && ERROR_CODE_RE.test(code) ? code : undefined;
132
+ }
103
133
  /**
104
134
  * Run a workflow program to completion. Installs the host + input, extracts the VERIFIED artifact +
105
135
  * dynamic-imports its entry (which runs the body), and returns the terminal result. Always tears the
@@ -137,14 +167,26 @@ export async function runWorkflowProgram(args, deps) {
137
167
  const redactText = deps.redactText ?? ((s) => s);
138
168
  // Redact BEFORE both sinks: the message can carry a secret the program resolved then threw.
139
169
  const message = redactText(err instanceof Error ? err.message : String(err));
170
+ // The hint is author-facing guidance ("write `builtins: [...]`"); redact it too — it is built from
171
+ // the same untrusted inputs as the message and could echo a resolved secret.
172
+ const rawHint = errorHint(err);
173
+ const hint = rawHint === undefined ? undefined : redactText(rawHint);
174
+ // The error's own code when it has one ("VALIDATION"), else the class name ("Error"). Redacted
175
+ // like the rest: it is read off an author-controlled throw, not trusted to be a literal.
176
+ const rawCode = errorCode(err);
177
+ const code = rawCode !== undefined
178
+ ? redactText(rawCode)
179
+ : err instanceof Error
180
+ ? err.name
181
+ : "PROGRAM_ERROR";
140
182
  log.error("program_failed", { runId: args.runId, error: message });
141
183
  return {
142
184
  kind: "failed",
143
185
  output: null,
144
186
  error: {
145
- // `code` is the error class name (e.g. "Error", "AppError"), not user content — left as-is.
146
- code: err instanceof Error ? err.name : "PROGRAM_ERROR",
187
+ code,
147
188
  message,
189
+ ...(hint === undefined ? {} : { hint }),
148
190
  },
149
191
  };
150
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boardwalk-labs/runner",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Boardwalk self-hosted runner: execute cloud-scheduled runs on your own machines. Currently: the canonical runner contract types.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  "prebuild": "prebuildify --napi --strip -t 24.0.0"
54
54
  },
55
55
  "dependencies": {
56
- "@boardwalk-labs/engine": "0.2.6",
56
+ "@boardwalk-labs/engine": "0.2.7",
57
57
  "@boardwalk-labs/workflow": "0.2.2",
58
58
  "@modelcontextprotocol/sdk": "^1.29.0",
59
59
  "node-gyp-build": "^4.8.4",