@smithers-orchestrator/engine 0.20.3 → 0.20.4

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.
@@ -21,13 +21,13 @@ import * as BunContext from "@effect/platform-bun/BunContext";
21
21
  function isAbortError(err) {
22
22
  if (!err)
23
23
  return false;
24
- if (err.name === "AbortError")
25
- return true;
26
24
  if (typeof DOMException !== "undefined" &&
27
25
  err instanceof DOMException &&
28
26
  err.name === "AbortError") {
29
27
  return true;
30
28
  }
29
+ if (err.name === "AbortError")
30
+ return true;
31
31
  if (err instanceof Error) {
32
32
  return /aborted|abort/i.test(err.message);
33
33
  }
@@ -304,3 +304,7 @@ export const executeStaticTaskBridge = async (adapter, runId, desc, eventBus, to
304
304
  removeAbortForwarder();
305
305
  }
306
306
  };
307
+
308
+ export const __staticTaskBridgeInternals = {
309
+ isAbortError,
310
+ };
@@ -113,6 +113,12 @@ const getNextTaskActivityAttempt = async (adapter, runId, desc) => {
113
113
  const latestAttempt = attempts[0]?.attempt ?? 0;
114
114
  return latestAttempt + 1;
115
115
  };
116
+ function taskBridgeResultForError(error) {
117
+ if (error instanceof RetriableTaskFailure) {
118
+ return { terminal: false };
119
+ }
120
+ throw error;
121
+ }
116
122
  /**
117
123
  * @param {SmithersDb} adapter
118
124
  * @param {_BunSQLiteDatabase} db
@@ -174,10 +180,7 @@ const runTaskBridgeExecution = async (adapter, db, runId, desc, descriptorMap, i
174
180
  return { terminal: true };
175
181
  }
176
182
  catch (error) {
177
- if (error instanceof RetriableTaskFailure) {
178
- return { terminal: false };
179
- }
180
- throw error;
183
+ return taskBridgeResultForError(error);
181
184
  }
182
185
  });
183
186
  };
@@ -260,3 +263,11 @@ export const executeTaskBridgeEffect = (adapter, db, runId, desc, descriptorMap,
260
263
  try: () => executeTaskBridge(adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn),
261
264
  catch: (cause) => toSmithersError(cause, "execute task bridge"),
262
265
  });
266
+ export const __workflowBridgeInternals = {
267
+ classifyTaskAttempt,
268
+ getNextTaskActivityAttempt,
269
+ isRetryableBridgeTaskFailure,
270
+ parseAttemptErrorCode,
271
+ runEffectOrPromise,
272
+ taskBridgeResultForError,
273
+ };
@@ -175,9 +175,6 @@ export function createSchedulerWakeQueue() {
175
175
  }
176
176
  return new Promise((resolve) => {
177
177
  resolver = () => {
178
- if (pending > 0) {
179
- pending -= 1;
180
- }
181
178
  resolve();
182
179
  };
183
180
  });
@@ -231,3 +228,13 @@ export async function runWorkflowWithMakeBridge(workflow, opts, executeBody) {
231
228
  }
232
229
  }
233
230
  }
231
+
232
+ export const __workflowMakeBridgeInternals = {
233
+ createWorkflowExecutionEffect,
234
+ createWorkflowMakeBridgeRuntime,
235
+ executeRegisteredChildWorkflow,
236
+ getWorkflowNamespace,
237
+ isSuspendingStatus,
238
+ makeBridgeWorkflow,
239
+ registerBridgeWorkflow,
240
+ };
package/src/engine.js CHANGED
@@ -2980,14 +2980,14 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
2980
2980
  if (desc.outputTable && !supportsNativeStructuredOutput) {
2981
2981
  const schemaDesc = describeSchemaShape(desc.outputTable, desc.outputSchema);
2982
2982
  const jsonInstructions = [
2983
- "**REQUIRED OUTPUT** — You MUST end your response with a JSON object in a code fence matching this schema:",
2984
- "```json",
2983
+ "**REQUIRED OUTPUT** — You MUST return ONLY a raw JSON object matching this schema:",
2985
2984
  schemaDesc,
2986
- "```",
2987
- "Output the JSON at the END of your response. The workflow will fail without it.",
2985
+ "Do not include prose, markdown, headings, commentary, or code fences.",
2986
+ "The first character of your response must be `{` and the last character must be `}`.",
2987
+ "The workflow will fail unless the entire response is the JSON object.",
2988
2988
  ].join("\n");
2989
2989
  effectivePrompt = [
2990
- "IMPORTANT: After completing the task below, you MUST output a JSON object in a ```json code fence at the very end of your response. Do NOT forget this — the workflow fails without it.",
2990
+ "IMPORTANT: After completing the task below, you MUST output ONLY a raw JSON object. Do NOT wrap it in markdown or add any prose — the workflow fails without it.",
2991
2991
  "",
2992
2992
  effectivePrompt,
2993
2993
  "",
@@ -3448,7 +3448,7 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
3448
3448
  `Now you MUST output ONLY a valid JSON object (no other text) summarizing your work above, with exactly these fields and types:`,
3449
3449
  schemaDesc,
3450
3450
  ``,
3451
- `Output ONLY the JSON object, nothing else.`,
3451
+ `Output ONLY the raw JSON object, with no markdown fences or prose.`,
3452
3452
  ].join("\n");
3453
3453
  const retryResult = await effectiveAgent.generate({
3454
3454
  options: undefined,
@@ -3679,7 +3679,7 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
3679
3679
  `You MUST output ONLY a valid JSON object with exactly these fields and types:`,
3680
3680
  schemaDesc,
3681
3681
  ``,
3682
- `Output ONLY the JSON object, no other text.`,
3682
+ `Output ONLY the raw JSON object, with no markdown fences or other text.`,
3683
3683
  ].join("\n");
3684
3684
  logInfo("schema validation retry", {
3685
3685
  runId,
@@ -4318,7 +4318,8 @@ async function runWorkflowAsync(workflow, opts) {
4318
4318
  * @returns {Promise<RunBodyResult>}
4319
4319
  */
4320
4320
  async function runWorkflowBody(workflow, opts) {
4321
- if (process.env.SMITHERS_LEGACY_ENGINE === "1") {
4321
+ if (opts.__smithersEngineMode === "legacy" ||
4322
+ process.env.SMITHERS_LEGACY_ENGINE === "1") {
4322
4323
  return runWorkflowBodyLegacy(workflow, opts);
4323
4324
  }
4324
4325
  return runWorkflowBodyDriver(workflow, opts);
@@ -7229,3 +7230,79 @@ export function runWorkflow(workflow, opts) {
7229
7230
  root: true,
7230
7231
  });
7231
7232
  }
7233
+
7234
+ export const __engineInternals = {
7235
+ sha256Hex,
7236
+ isBlockingAgentActionKind,
7237
+ makeAbortError,
7238
+ isAbortError,
7239
+ collectErrorMessages,
7240
+ isStructuredOutputParseFailure,
7241
+ depsTextAccessHint,
7242
+ makeStructuredOutputCompatibilityError,
7243
+ makePlainTextOutputError,
7244
+ abortPromise,
7245
+ parseAttemptMetaJson,
7246
+ asConversationMessages,
7247
+ cloneJsonValue,
7248
+ parseAttemptHeartbeatData,
7249
+ validateHeartbeatValue,
7250
+ serializeHeartbeatPayload,
7251
+ heartbeatTimeoutReasonFromAbort,
7252
+ isHeartbeatPayloadValidationError,
7253
+ runPromisePreservingFailure,
7254
+ extractHijackContinuation,
7255
+ findHijackContinuation,
7256
+ collectDefinedToolMetadata,
7257
+ collectToolResumeWarnings,
7258
+ buildToolResumeWarningMessage,
7259
+ hasToolResumeWarningMessage,
7260
+ appendToolResumeWarningMessage,
7261
+ prependToolResumeWarningMessage,
7262
+ workflowSessionTaskId,
7263
+ workflowSessionTaskIds,
7264
+ summarizeWorkflowSessionDecision,
7265
+ summarizeLegacySchedulerDecision,
7266
+ workflowSessionSummaryKey,
7267
+ coercePositiveInt,
7268
+ buildInputRow,
7269
+ normalizeInputRow,
7270
+ normalizeOutputRow,
7271
+ quoteSqlIdent,
7272
+ toSqlValue,
7273
+ getTableColumnEntries,
7274
+ insertRowWithClient,
7275
+ copyRunScopedRowsWithClient,
7276
+ ralphStateToObject,
7277
+ cloneRalphStateMap,
7278
+ buildCarriedInputRow,
7279
+ continueRunAsNew,
7280
+ resolveRootDir,
7281
+ resolveLogDir,
7282
+ getWorkflowImportScanLoader,
7283
+ extractWorkflowImportSpecifiers,
7284
+ resolveWorkflowImport,
7285
+ buildDurabilityConfig,
7286
+ getStoredDurabilityConfig,
7287
+ compareNullableString,
7288
+ assertResumeDurabilityMetadata,
7289
+ wireAbortSignal,
7290
+ parseRunConfigJson,
7291
+ parseRunAuthContext,
7292
+ isResumableRunStatus,
7293
+ normalizeHotOptions,
7294
+ assertInputObject,
7295
+ validateRunOptions,
7296
+ iterationsToMap,
7297
+ ralphStateFromDriverTransition,
7298
+ resolveTaskOutputs,
7299
+ buildDescriptorMap,
7300
+ buildRalphStateMap,
7301
+ ralphIterationsFromState,
7302
+ ralphIterationsObject,
7303
+ buildRalphDoneMap,
7304
+ parseAttemptErrorCode,
7305
+ isRetryableTaskFailure,
7306
+ cancelInProgress,
7307
+ cancelStaleAttempts,
7308
+ };
@@ -17,6 +17,37 @@ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
17
17
 
18
18
  const DEFAULT_MAX_GENERATIONS = 3;
19
19
  const DEFAULT_DEBOUNCE_MS = 100;
20
+
21
+ function makeHotReloadFailureEvent(err, ctx) {
22
+ const { entryPath, generation, changedFiles } = ctx;
23
+ if (err instanceof Error && err.message?.includes("Schema change detected")) {
24
+ logWarning("hot workflow reload marked unsafe", {
25
+ entryPath,
26
+ generation,
27
+ changedFileCount: changedFiles.length,
28
+ reason: err.message,
29
+ }, "hot:reload");
30
+ return {
31
+ type: "unsafe",
32
+ generation,
33
+ changedFiles,
34
+ reason: err.message,
35
+ };
36
+ }
37
+ logWarning("hot workflow reload failed", {
38
+ entryPath,
39
+ generation,
40
+ changedFileCount: changedFiles.length,
41
+ error: err instanceof Error ? err.message : String(err),
42
+ }, "hot:reload");
43
+ return {
44
+ type: "failed",
45
+ generation,
46
+ changedFiles,
47
+ error: err,
48
+ };
49
+ }
50
+
20
51
  export class HotWorkflowController {
21
52
  entryPath;
22
53
  hotRoot;
@@ -160,32 +191,11 @@ export class HotWorkflowController {
160
191
  };
161
192
  }).pipe(Effect.catchAll((err) => Effect.gen(function* () {
162
193
  yield* Metric.increment(hotReloadFailures);
163
- if (err instanceof Error && err.message?.includes("Schema change detected")) {
164
- logWarning("hot workflow reload marked unsafe", {
165
- entryPath,
166
- generation: gen,
167
- changedFileCount: changedFiles.length,
168
- reason: err.message,
169
- }, "hot:reload");
170
- return {
171
- type: "unsafe",
172
- generation: gen,
173
- changedFiles,
174
- reason: err.message,
175
- };
176
- }
177
- logWarning("hot workflow reload failed", {
194
+ return makeHotReloadFailureEvent(err, {
178
195
  entryPath,
179
196
  generation: gen,
180
- changedFileCount: changedFiles.length,
181
- error: err instanceof Error ? err.message : String(err),
182
- }, "hot:reload");
183
- return {
184
- type: "failed",
185
- generation: gen,
186
197
  changedFiles,
187
- error: err,
188
- };
198
+ });
189
199
  })), Effect.annotateLogs({
190
200
  entryPath,
191
201
  hotRoot,
@@ -218,3 +228,7 @@ export class HotWorkflowController {
218
228
  }), Effect.withLogSpan("hot:close"));
219
229
  }
220
230
  }
231
+
232
+ export const __hotWorkflowControllerInternals = {
233
+ makeHotReloadFailureEvent,
234
+ };
@@ -13,6 +13,17 @@ const DEFAULT_EXCLUDE = [
13
13
  ".smithers",
14
14
  ".DS_Store",
15
15
  ];
16
+
17
+ function hotOverlayError(cause, operation, details) {
18
+ return toSmithersError(cause, operation, {
19
+ code: "HOT_OVERLAY_FAILED",
20
+ details,
21
+ });
22
+ }
23
+
24
+ function hotOverlayErrorMapper(operation, details) {
25
+ return (cause) => hotOverlayError(cause, operation, details);
26
+ }
16
27
  /**
17
28
  * Build a generation overlay by hardlinking (or copying) the hot root
18
29
  * tree into a new generation directory.
@@ -31,9 +42,10 @@ export function buildOverlayEffect(hotRoot, outDir, generation, opts) {
31
42
  return Effect.gen(function* () {
32
43
  yield* Effect.tryPromise({
33
44
  try: () => mkdir(genDir, { recursive: true }),
34
- catch: (cause) => toSmithersError(cause, "create hot overlay generation dir", {
35
- code: "HOT_OVERLAY_FAILED",
36
- details: { hotRoot, outDir, generation },
45
+ catch: hotOverlayErrorMapper("create hot overlay generation dir", {
46
+ hotRoot,
47
+ outDir,
48
+ generation,
37
49
  }),
38
50
  });
39
51
  yield* mirrorTreeEffect(hotRoot, genDir, exclude);
@@ -63,9 +75,9 @@ function mirrorTreeEffect(src, dest, exclude) {
63
75
  return Effect.gen(function* () {
64
76
  const entries = yield* Effect.tryPromise({
65
77
  try: () => readdir(src, { withFileTypes: true }),
66
- catch: (cause) => toSmithersError(cause, "read hot overlay source dir", {
67
- code: "HOT_OVERLAY_FAILED",
68
- details: { src, dest },
78
+ catch: hotOverlayErrorMapper("read hot overlay source dir", {
79
+ src,
80
+ dest,
69
81
  }),
70
82
  });
71
83
  for (const entry of entries) {
@@ -78,9 +90,9 @@ function mirrorTreeEffect(src, dest, exclude) {
78
90
  if (entry.isDirectory()) {
79
91
  yield* Effect.tryPromise({
80
92
  try: () => mkdir(destPath, { recursive: true }),
81
- catch: (cause) => toSmithersError(cause, "create mirrored hot overlay dir", {
82
- code: "HOT_OVERLAY_FAILED",
83
- details: { srcPath, destPath },
93
+ catch: hotOverlayErrorMapper("create mirrored hot overlay dir", {
94
+ srcPath,
95
+ destPath,
84
96
  }),
85
97
  });
86
98
  yield* mirrorTreeEffect(srcPath, destPath, exclude);
@@ -88,24 +100,24 @@ function mirrorTreeEffect(src, dest, exclude) {
88
100
  else if (entry.isFile()) {
89
101
  const linked = yield* Effect.either(Effect.tryPromise({
90
102
  try: () => link(srcPath, destPath),
91
- catch: (cause) => toSmithersError(cause, "hardlink overlay file", {
92
- code: "HOT_OVERLAY_FAILED",
93
- details: { srcPath, destPath },
103
+ catch: hotOverlayErrorMapper("hardlink overlay file", {
104
+ srcPath,
105
+ destPath,
94
106
  }),
95
107
  }));
96
108
  if (linked._tag === "Left") {
97
109
  yield* Effect.tryPromise({
98
110
  try: () => mkdir(dirname(destPath), { recursive: true }),
99
- catch: (cause) => toSmithersError(cause, "create overlay file parent dir", {
100
- code: "HOT_OVERLAY_FAILED",
101
- details: { srcPath, destPath },
111
+ catch: hotOverlayErrorMapper("create overlay file parent dir", {
112
+ srcPath,
113
+ destPath,
102
114
  }),
103
115
  });
104
116
  yield* Effect.tryPromise({
105
117
  try: () => copyFile(srcPath, destPath),
106
- catch: (cause) => toSmithersError(cause, "copy overlay file", {
107
- code: "HOT_OVERLAY_FAILED",
108
- details: { srcPath, destPath },
118
+ catch: hotOverlayErrorMapper("copy overlay file", {
119
+ srcPath,
120
+ destPath,
109
121
  }),
110
122
  });
111
123
  }
@@ -126,9 +138,9 @@ export function cleanupGenerationsEffect(outDir, keepLast) {
126
138
  return;
127
139
  const entries = yield* Effect.tryPromise({
128
140
  try: () => readdir(outDir, { withFileTypes: true }),
129
- catch: (cause) => toSmithersError(cause, "read hot overlay generations", {
130
- code: "HOT_OVERLAY_FAILED",
131
- details: { outDir, keepLast },
141
+ catch: hotOverlayErrorMapper("read hot overlay generations", {
142
+ outDir,
143
+ keepLast,
132
144
  }),
133
145
  });
134
146
  const genDirs = entries
@@ -143,9 +155,9 @@ export function cleanupGenerationsEffect(outDir, keepLast) {
143
155
  for (const dir of toRemove) {
144
156
  yield* Effect.either(Effect.tryPromise({
145
157
  try: () => rm(join(outDir, dir.name), { recursive: true, force: true }),
146
- catch: (cause) => toSmithersError(cause, "remove stale hot overlay generation", {
147
- code: "HOT_OVERLAY_FAILED",
148
- details: { outDir, generationDir: dir.name },
158
+ catch: hotOverlayErrorMapper("remove stale hot overlay generation", {
159
+ outDir,
160
+ generationDir: dir.name,
149
161
  }),
150
162
  }));
151
163
  }
@@ -175,3 +187,9 @@ export function resolveOverlayEntry(entryPath, hotRoot, genDir) {
175
187
  const rel = relative(hotRoot, entryPath);
176
188
  return resolve(genDir, rel);
177
189
  }
190
+
191
+ export const __overlayInternals = {
192
+ hotOverlayError,
193
+ hotOverlayErrorMapper,
194
+ mirrorTreeEffect,
195
+ };
@@ -118,3 +118,6 @@ export function validateHumanRequestValue(request, value) {
118
118
  }
119
119
  return { ok: true };
120
120
  }
121
+ export const __humanRequestInternals = {
122
+ formatValidationIssues,
123
+ };