@smithers-orchestrator/engine 0.17.0 → 0.18.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/package.json +15 -15
- package/src/effect/workflow-bridge.js +5 -1
- package/src/engine.js +74 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Concrete Smithers workflow execution engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,20 +33,20 @@
|
|
|
33
33
|
"react": "^19.2.5",
|
|
34
34
|
"react-dom": "^19.2.5",
|
|
35
35
|
"zod": "^4.3.6",
|
|
36
|
-
"@smithers-orchestrator/agents": "0.
|
|
37
|
-
"@smithers-orchestrator/
|
|
38
|
-
"@smithers-orchestrator/driver": "0.
|
|
39
|
-
"@smithers-orchestrator/
|
|
40
|
-
"@smithers-orchestrator/
|
|
41
|
-
"@smithers-orchestrator/
|
|
42
|
-
"@smithers-orchestrator/
|
|
43
|
-
"@smithers-orchestrator/
|
|
44
|
-
"@smithers-orchestrator/
|
|
45
|
-
"@smithers-orchestrator/
|
|
46
|
-
"@smithers-orchestrator/
|
|
47
|
-
"@smithers-orchestrator/
|
|
48
|
-
"@smithers-orchestrator/
|
|
49
|
-
"@smithers-orchestrator/vcs": "0.
|
|
36
|
+
"@smithers-orchestrator/agents": "0.18.0",
|
|
37
|
+
"@smithers-orchestrator/db": "0.18.0",
|
|
38
|
+
"@smithers-orchestrator/driver": "0.18.0",
|
|
39
|
+
"@smithers-orchestrator/errors": "0.18.0",
|
|
40
|
+
"@smithers-orchestrator/graph": "0.18.0",
|
|
41
|
+
"@smithers-orchestrator/memory": "0.18.0",
|
|
42
|
+
"@smithers-orchestrator/observability": "0.18.0",
|
|
43
|
+
"@smithers-orchestrator/scheduler": "0.18.0",
|
|
44
|
+
"@smithers-orchestrator/scorers": "0.18.0",
|
|
45
|
+
"@smithers-orchestrator/time-travel": "0.18.0",
|
|
46
|
+
"@smithers-orchestrator/components": "0.18.0",
|
|
47
|
+
"@smithers-orchestrator/react-reconciler": "0.18.0",
|
|
48
|
+
"@smithers-orchestrator/sandbox": "0.18.0",
|
|
49
|
+
"@smithers-orchestrator/vcs": "0.18.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/bun": "latest",
|
|
@@ -72,8 +72,12 @@ function isRetryableBridgeTaskFailure(attempt) {
|
|
|
72
72
|
if (meta?.failureRetryable === false) {
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
|
+
const errorCode = parseAttemptErrorCode(attempt?.errorJson);
|
|
76
|
+
if (errorCode === "AGENT_CONFIG_INVALID") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
75
79
|
const kind = typeof meta?.kind === "string" ? meta.kind : null;
|
|
76
|
-
return !(kind !== "agent" &&
|
|
80
|
+
return !(kind !== "agent" && errorCode === "INVALID_OUTPUT");
|
|
77
81
|
}
|
|
78
82
|
/**
|
|
79
83
|
* @param {SmithersDb} adapter
|
package/src/engine.js
CHANGED
|
@@ -25,7 +25,7 @@ import { findVcsRoot } from "@smithers-orchestrator/vcs/find-root";
|
|
|
25
25
|
import * as BunContext from "@effect/platform-bun/BunContext";
|
|
26
26
|
import { eq, getTableName } from "drizzle-orm";
|
|
27
27
|
import { getTableColumns } from "drizzle-orm/utils";
|
|
28
|
-
import { Chunk, Duration, Effect, Fiber, Metric, Queue, Schedule } from "effect";
|
|
28
|
+
import { Cause, Chunk, Duration, Effect, Exit, Fiber, Metric, Queue, Schedule } from "effect";
|
|
29
29
|
import { attemptDuration, cacheHits, cacheMisses, nodeDuration, promptSizeBytes, responseSizeBytes, runDuration, runsResumedTotal, schedulerConcurrencyUtilization, schedulerQueueDepth, schedulerWaitDuration, trackEvent, } from "@smithers-orchestrator/observability/metrics";
|
|
30
30
|
import { runScorersAsync } from "@smithers-orchestrator/scorers/run-scorers";
|
|
31
31
|
import { dirname, resolve } from "node:path";
|
|
@@ -287,6 +287,25 @@ function isHeartbeatPayloadValidationError(err) {
|
|
|
287
287
|
return (code === "HEARTBEAT_PAYLOAD_NOT_JSON_SERIALIZABLE" ||
|
|
288
288
|
code === "HEARTBEAT_PAYLOAD_TOO_LARGE");
|
|
289
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Effect.runPromise rejects with a FiberFailure wrapper. For task execution we
|
|
292
|
+
* need the original failure so retry metadata can read SmithersError fields.
|
|
293
|
+
*
|
|
294
|
+
* @template A
|
|
295
|
+
* @param {Effect.Effect<A, unknown>} effect
|
|
296
|
+
* @returns {Promise<A>}
|
|
297
|
+
*/
|
|
298
|
+
async function runPromisePreservingFailure(effect) {
|
|
299
|
+
const exit = await Effect.runPromiseExit(effect);
|
|
300
|
+
if (Exit.isSuccess(exit)) {
|
|
301
|
+
return exit.value;
|
|
302
|
+
}
|
|
303
|
+
const failure = Cause.failureOption(exit.cause);
|
|
304
|
+
if (failure._tag === "Some") {
|
|
305
|
+
throw failure.value;
|
|
306
|
+
}
|
|
307
|
+
throw Cause.squash(exit.cause);
|
|
308
|
+
}
|
|
290
309
|
/**
|
|
291
310
|
* @param {Record<string, unknown>} meta
|
|
292
311
|
* @param {string} engine
|
|
@@ -1784,6 +1803,7 @@ function resolveTaskOutputs(tasks, workflow) {
|
|
|
1784
1803
|
if (isTimerTask(task)) {
|
|
1785
1804
|
continue;
|
|
1786
1805
|
}
|
|
1806
|
+
const hasAmbiguousOutputRef = Boolean(task.outputRef && workflow.ambiguousZodSchemas?.has(task.outputRef));
|
|
1787
1807
|
// Already resolved (has a table)
|
|
1788
1808
|
if (task.outputTable) {
|
|
1789
1809
|
if (!task.outputSchema && task.outputTableName && workflow.schemaRegistry) {
|
|
@@ -1807,10 +1827,14 @@ function resolveTaskOutputs(tasks, workflow) {
|
|
|
1807
1827
|
}
|
|
1808
1828
|
}
|
|
1809
1829
|
if (!task.outputTable) {
|
|
1830
|
+
if (hasAmbiguousOutputRef) {
|
|
1831
|
+
throw new SmithersError("UNKNOWN_OUTPUT_SCHEMA", `Task "${task.nodeId}" uses an output schema that is registered under multiple keys. Use createSmithers(...).outputs.<key> or a string output key instead of the shared raw Zod object.`);
|
|
1832
|
+
}
|
|
1810
1833
|
throw new SmithersError("UNKNOWN_OUTPUT_SCHEMA", `Task "${task.nodeId}" uses an output ZodObject that is not registered in createSmithers()`);
|
|
1811
1834
|
}
|
|
1812
1835
|
}
|
|
1813
1836
|
const raw = task.outputSchema;
|
|
1837
|
+
const hasAmbiguousOutputSchema = Boolean(raw && typeof raw === "object" && workflow.ambiguousZodSchemas?.has(raw));
|
|
1814
1838
|
// Resolve ZodObject via outputSchema when no outputRef resolved.
|
|
1815
1839
|
if (!task.outputTable && raw && typeof raw === "object" && workflow.zodToKeyName) {
|
|
1816
1840
|
const keyName = workflow.zodToKeyName.get(raw);
|
|
@@ -1824,6 +1848,9 @@ function resolveTaskOutputs(tasks, workflow) {
|
|
|
1824
1848
|
}
|
|
1825
1849
|
}
|
|
1826
1850
|
if (!task.outputTable) {
|
|
1851
|
+
if (hasAmbiguousOutputSchema) {
|
|
1852
|
+
throw new SmithersError("UNKNOWN_OUTPUT_SCHEMA", `Task "${task.nodeId}" uses an output schema that is registered under multiple keys. Use createSmithers(...).outputs.<key> or a string output key instead of the shared raw Zod object.`);
|
|
1853
|
+
}
|
|
1827
1854
|
throw new SmithersError("UNKNOWN_OUTPUT_SCHEMA", `Task "${task.nodeId}" uses an output ZodObject that is not registered in createSmithers()`);
|
|
1828
1855
|
}
|
|
1829
1856
|
}
|
|
@@ -2554,6 +2581,7 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
2554
2581
|
let cacheJjBase = null;
|
|
2555
2582
|
let responseText = null;
|
|
2556
2583
|
let effectiveAgent = null;
|
|
2584
|
+
let supportsNativeStructuredOutput = false;
|
|
2557
2585
|
// Resolve effective root once so both caching and execution share it.
|
|
2558
2586
|
const taskRoot = desc.worktreePath ?? toolConfig.rootDir;
|
|
2559
2587
|
const stepCacheEnabled = cacheEnabled || Boolean(desc.cachePolicy);
|
|
@@ -2874,7 +2902,8 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
2874
2902
|
maybeCompleteHijack();
|
|
2875
2903
|
};
|
|
2876
2904
|
let effectivePrompt = desc.prompt ?? "";
|
|
2877
|
-
|
|
2905
|
+
supportsNativeStructuredOutput = effectiveAgent.supportsNativeStructuredOutput === true;
|
|
2906
|
+
if (desc.outputTable && !supportsNativeStructuredOutput) {
|
|
2878
2907
|
const schemaDesc = describeSchemaShape(desc.outputTable, desc.outputSchema);
|
|
2879
2908
|
const jsonInstructions = [
|
|
2880
2909
|
"**REQUIRED OUTPUT** — You MUST end your response with a JSON object in a code fence matching this schema:",
|
|
@@ -3026,7 +3055,7 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
3026
3055
|
// Use fallback agent on retry attempts when available
|
|
3027
3056
|
let result;
|
|
3028
3057
|
try {
|
|
3029
|
-
result = await
|
|
3058
|
+
result = await runPromisePreservingFailure(withSmithersSpan(smithersSpanNames.agent, Effect.tryPromise({
|
|
3030
3059
|
try: () => {
|
|
3031
3060
|
const agentCall = guidedResumeMessages?.length
|
|
3032
3061
|
? {
|
|
@@ -3486,17 +3515,25 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
3486
3515
|
const zodIssues = validation.error?.issues
|
|
3487
3516
|
?.map((iss) => ` - ${(iss.path ?? []).join(".")}: ${iss.message}`)
|
|
3488
3517
|
.join("\n") ?? "Unknown validation error";
|
|
3489
|
-
const schemaRetryPrompt =
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3518
|
+
const schemaRetryPrompt = supportsNativeStructuredOutput
|
|
3519
|
+
? [
|
|
3520
|
+
`Your structured output didn't match the required schema. Validation errors:`,
|
|
3521
|
+
zodIssues,
|
|
3522
|
+
``,
|
|
3523
|
+
`Return corrected structured data matching this schema:`,
|
|
3524
|
+
schemaDesc,
|
|
3525
|
+
].join("\n")
|
|
3526
|
+
: [
|
|
3527
|
+
`Your output didn't match the required schema. Validation errors:`,
|
|
3528
|
+
zodIssues,
|
|
3529
|
+
``,
|
|
3530
|
+
`Please return valid JSON matching the schema exactly.`,
|
|
3531
|
+
``,
|
|
3532
|
+
`You MUST output ONLY a valid JSON object with exactly these fields and types:`,
|
|
3533
|
+
schemaDesc,
|
|
3534
|
+
``,
|
|
3535
|
+
`Output ONLY the JSON object, no other text.`,
|
|
3536
|
+
].join("\n");
|
|
3500
3537
|
logInfo("schema validation retry", {
|
|
3501
3538
|
runId,
|
|
3502
3539
|
nodeId: desc.nodeId,
|
|
@@ -3526,6 +3563,9 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
3526
3563
|
recordInternalHeartbeat();
|
|
3527
3564
|
emitOutput(text, "stderr");
|
|
3528
3565
|
},
|
|
3566
|
+
...(supportsNativeStructuredOutput
|
|
3567
|
+
? { outputSchema: desc.outputSchema }
|
|
3568
|
+
: {}),
|
|
3529
3569
|
});
|
|
3530
3570
|
const retryText = (schemaRetryResult.text ?? "").trim();
|
|
3531
3571
|
responseText = retryText || responseText;
|
|
@@ -3547,8 +3587,24 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
3547
3587
|
cloneJsonValue(schemaRetryMessages) ?? schemaRetryMessages;
|
|
3548
3588
|
// Try to parse the retry response
|
|
3549
3589
|
let retryOutput;
|
|
3590
|
+
if (supportsNativeStructuredOutput) {
|
|
3591
|
+
try {
|
|
3592
|
+
if (schemaRetryResult._output !== undefined &&
|
|
3593
|
+
schemaRetryResult._output !== null) {
|
|
3594
|
+
retryOutput = schemaRetryResult._output;
|
|
3595
|
+
}
|
|
3596
|
+
else if (schemaRetryResult.output !== undefined &&
|
|
3597
|
+
schemaRetryResult.output !== null) {
|
|
3598
|
+
retryOutput = schemaRetryResult.output;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
catch {
|
|
3602
|
+
// Structured output access threw; fall back to text parsing.
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3550
3605
|
try {
|
|
3551
|
-
if (
|
|
3606
|
+
if (retryOutput === undefined &&
|
|
3607
|
+
(retryText.startsWith("{") || retryText.startsWith("["))) {
|
|
3552
3608
|
retryOutput = JSON.parse(retryText);
|
|
3553
3609
|
}
|
|
3554
3610
|
}
|
|
@@ -3743,9 +3799,9 @@ async function legacyExecuteTask(adapter, db, runId, desc, descriptorMap, inputT
|
|
|
3743
3799
|
if (effectiveError &&
|
|
3744
3800
|
typeof effectiveError === "object" &&
|
|
3745
3801
|
// @ts-ignore — duck-type on SmithersError shape
|
|
3746
|
-
effectiveError.details
|
|
3747
|
-
|
|
3748
|
-
|
|
3802
|
+
(effectiveError.details?.failureRetryable === false ||
|
|
3803
|
+
// @ts-ignore
|
|
3804
|
+
effectiveError.code === "AGENT_CONFIG_INVALID")) {
|
|
3749
3805
|
attemptMeta.failureRetryable = false;
|
|
3750
3806
|
}
|
|
3751
3807
|
// Honour `discardResumeSession: true` from agent-side errors (e.g. kimi
|