@kortyx/hooks 0.34.1 → 0.34.3
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/CHANGELOG.md +30 -0
- package/README.md +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +182 -68
- package/dist/index.js.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Return recoverable, bounded model feedback for invalid named interrupt requests. Preserve strict human responses, request quotas, approval gates, and tool-loop limits.
|
|
6
|
+
- Replace child execution snapshots natively while reading legacy object and single-element array snapshots.
|
|
7
|
+
|
|
8
|
+
## [0.34.3](https://github.com/kortyx-io/kortyx/compare/hooks-v0.34.2...hooks-v0.34.3) (2026-10-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **runtime:** preserve interrupt state across pauses and resume ([#348](https://github.com/kortyx-io/kortyx/issues/348)) ([03b49f2](https://github.com/kortyx-io/kortyx/commit/03b49f2b8b6b44a2852d16fd4fe1b3e199316072))
|
|
14
|
+
|
|
15
|
+
## [0.34.2](https://github.com/kortyx-io/kortyx/compare/hooks-v0.34.1...hooks-v0.34.2) (2026-10-13)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **evals:** preserve diagnostics and separate postcondition failures ([#337](https://github.com/kortyx-io/kortyx/issues/337)) ([d4fce4e](https://github.com/kortyx-io/kortyx/commit/d4fce4e670971d3b755835fc681c8125f67a9c70))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Dependencies
|
|
24
|
+
|
|
25
|
+
* The following workspace dependencies were updated
|
|
26
|
+
* dependencies
|
|
27
|
+
* @kortyx/core bumped to 0.14.0
|
|
28
|
+
* @kortyx/providers bumped to 0.9.3
|
|
29
|
+
* @kortyx/telemetry-contracts bumped to 0.17.2
|
|
30
|
+
* @kortyx/prompts bumped to 0.2.1
|
|
31
|
+
* @kortyx/usage-telemetry bumped to 0.3.0
|
|
32
|
+
|
|
3
33
|
## [0.34.1](https://github.com/kortyx-io/kortyx/compare/hooks-v0.34.0...hooks-v0.34.1) (2026-10-13)
|
|
4
34
|
|
|
5
35
|
|
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ tool loop, so tools can run before and after a pause. Use `mode: "optional"`
|
|
|
35
35
|
when the model may finish without asking, and `maxRequests` to bound sequential
|
|
36
36
|
human turns.
|
|
37
37
|
|
|
38
|
+
Malformed model-authored requests return an `isError` tool result so the model can correct its arguments. They consume the existing model/loop allowance, but do not issue a human request or consume `maxRequests`. Required mode still requires a valid request. Human response validation and approval denial remain strict.
|
|
39
|
+
|
|
38
40
|
The singular `useReason({ interrupt })` option and `result.interruptResponse`
|
|
39
41
|
are deprecated and will be removed in the next major release. Migrate to
|
|
40
42
|
`interrupts.contracts` and `result.interruptHistory`.
|
|
@@ -137,3 +139,9 @@ Await groups sequentially within one node; children may contain nested groups.
|
|
|
137
139
|
Native `Promise.all` calls and parallel graph edges remain unsupported.
|
|
138
140
|
|
|
139
141
|
See [the execution and replay contract](../../docs/design-specs/child-workflows.md).
|
|
142
|
+
|
|
143
|
+
When a required `outputs.return` is missing, `useReason` raises the typed
|
|
144
|
+
`REASON_TERMINAL_OUTPUT_MISSING` validation error. Optional
|
|
145
|
+
`toolExecution: { maxSteps: 4, missingReturnCorrections: 1 }` allows an output-only
|
|
146
|
+
correction within the existing step budget, without replaying business tools.
|
|
147
|
+
The correction budget defaults to zero.
|
package/dist/index.d.ts
CHANGED
|
@@ -262,6 +262,8 @@ type UseReasonInterruptConfig<TRequest extends InterruptInput = InterruptInput,
|
|
|
262
262
|
schemaVersion?: string | undefined;
|
|
263
263
|
};
|
|
264
264
|
type UseReasonToolExecution = {
|
|
265
|
+
/** Extra model corrections for a missing terminal return; defaults to zero and shares maxSteps. Completed calls are not replayed. */
|
|
266
|
+
missingReturnCorrections?: number | undefined;
|
|
265
267
|
/** Model-pass budget, including output generation and correction across resume. */
|
|
266
268
|
maxSteps?: number | undefined;
|
|
267
269
|
approval?: boolean | Record<string, boolean> | undefined;
|
package/dist/index.js
CHANGED
|
@@ -245,7 +245,7 @@ var import_node = require("@kortyx/usage-telemetry/node");
|
|
|
245
245
|
// package.json
|
|
246
246
|
var package_default = {
|
|
247
247
|
name: "@kortyx/hooks",
|
|
248
|
-
version: "0.34.
|
|
248
|
+
version: "0.34.3",
|
|
249
249
|
private: false,
|
|
250
250
|
description: "Lifecycle hooks and extension points for Kortyx.",
|
|
251
251
|
keywords: [
|
|
@@ -2965,6 +2965,40 @@ var emitReasonStructuredOutput = (args) => {
|
|
|
2965
2965
|
var import_core5 = require("@kortyx/core");
|
|
2966
2966
|
var import_errors6 = require("@kortyx/core/errors");
|
|
2967
2967
|
var import_zod5 = require("zod");
|
|
2968
|
+
|
|
2969
|
+
// src/reason/input-feedback.ts
|
|
2970
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2971
|
+
function schemaPath(schema, path) {
|
|
2972
|
+
let current = schema;
|
|
2973
|
+
let label = "";
|
|
2974
|
+
for (const part of path.slice(0, 8)) {
|
|
2975
|
+
if (!record(current)) break;
|
|
2976
|
+
if (typeof part === "number" && Number.isSafeInteger(part) && part >= 0) {
|
|
2977
|
+
if (!record(current.items)) break;
|
|
2978
|
+
current = current.items;
|
|
2979
|
+
label += `[${part}]`;
|
|
2980
|
+
} else if (typeof part === "string" && record(current.properties) && Object.hasOwn(current.properties, part)) {
|
|
2981
|
+
current = current.properties[part];
|
|
2982
|
+
label += `${label ? "." : ""}${part.slice(0, 64)}`;
|
|
2983
|
+
} else break;
|
|
2984
|
+
}
|
|
2985
|
+
return label || "input";
|
|
2986
|
+
}
|
|
2987
|
+
function interruptInputFeedback(error, schema, name, required) {
|
|
2988
|
+
const issues = Array.isArray(error.issues) ? error.issues : [];
|
|
2989
|
+
const problems = issues.slice(0, 5).map((issue) => {
|
|
2990
|
+
const path = record(issue) && Array.isArray(issue.path) ? issue.path : [];
|
|
2991
|
+
return `- ${schemaPath(schema, path)} must satisfy its declared input schema.`;
|
|
2992
|
+
});
|
|
2993
|
+
return [
|
|
2994
|
+
`Invalid input for ${name}:`,
|
|
2995
|
+
...problems,
|
|
2996
|
+
...issues.length > 5 ? ["- Additional input fields are invalid."] : [],
|
|
2997
|
+
required ? "Correct the input and call the tool again. Human input is required before finishing." : "Correct the input and call the tool again, or finish without requesting human input when none is needed."
|
|
2998
|
+
].join("\n");
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
// src/reason/tools.ts
|
|
2968
3002
|
var toToolDefinitions = (tools2) => tools2.map((tool) => ({
|
|
2969
3003
|
name: tool.name,
|
|
2970
3004
|
...tool.title ? { title: tool.title } : {},
|
|
@@ -3167,6 +3201,18 @@ var runReasonToolLoop = async (args) => {
|
|
|
3167
3201
|
let finalizing = checkpoint?.finalizing ?? false;
|
|
3168
3202
|
let completed = false;
|
|
3169
3203
|
const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
|
|
3204
|
+
const missingReturnCorrections = useReasonArgs.toolExecution?.missingReturnCorrections ?? 0;
|
|
3205
|
+
if (!Number.isSafeInteger(missingReturnCorrections) || missingReturnCorrections < 0)
|
|
3206
|
+
throw new import_errors6.KortyxError(
|
|
3207
|
+
"INVALID_REASON_CORRECTION_POLICY",
|
|
3208
|
+
"Invalid missing return correction budget.",
|
|
3209
|
+
{
|
|
3210
|
+
category: "configuration",
|
|
3211
|
+
retryable: false,
|
|
3212
|
+
safeMessage: "toolExecution.missingReturnCorrections must be a nonnegative safe integer."
|
|
3213
|
+
}
|
|
3214
|
+
);
|
|
3215
|
+
let missingReturnAttempts = checkpoint?.missingReturnAttempts ?? 0;
|
|
3170
3216
|
const messages = checkpoint?.messages ?? createInitialMessages({
|
|
3171
3217
|
messages: useReasonArgs.messages,
|
|
3172
3218
|
inputOverride: Boolean(useReasonArgs.outputSchema),
|
|
@@ -3218,13 +3264,17 @@ var runReasonToolLoop = async (args) => {
|
|
|
3218
3264
|
let outputContractCorrections = checkpoint?.outputContractCorrections ?? 0;
|
|
3219
3265
|
const correctionExhausted = () => {
|
|
3220
3266
|
const message = `useReason could not complete output-contract correction within toolExecution.maxSteps (${maxSteps}). Select exactly one terminal return contract; multiple emit contracts are allowed. Increase maxSteps to allow correction and output generation.`;
|
|
3221
|
-
return new import_errors6.
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3267
|
+
return new import_errors6.KortyxError(
|
|
3268
|
+
"REASON_OUTPUT_CONTRACT_CORRECTION_EXHAUSTED",
|
|
3269
|
+
message,
|
|
3270
|
+
{
|
|
3271
|
+
category: "validation",
|
|
3272
|
+
operation: "useReason",
|
|
3273
|
+
safeMessage: message,
|
|
3274
|
+
retryable: false,
|
|
3275
|
+
details: { maxSteps, outputContractCorrections }
|
|
3276
|
+
}
|
|
3277
|
+
);
|
|
3228
3278
|
};
|
|
3229
3279
|
const resolvedInterrupts = checkpoint?.resolvedInterrupts ?? [];
|
|
3230
3280
|
for (const request of resolvedInterrupts) ctx.node.replayInterrupt?.(request);
|
|
@@ -3243,6 +3293,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
3243
3293
|
interruptHistory,
|
|
3244
3294
|
emissions,
|
|
3245
3295
|
outputContractCorrections,
|
|
3296
|
+
missingReturnAttempts,
|
|
3246
3297
|
...returned ? { returned } : {},
|
|
3247
3298
|
finalizing,
|
|
3248
3299
|
...pending ? { pending } : {},
|
|
@@ -3278,7 +3329,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
3278
3329
|
const step = pending ?? await reasonEngine(
|
|
3279
3330
|
{
|
|
3280
3331
|
...useReasonArgs,
|
|
3281
|
-
tools: finalizing ? [] : toolDefinitions,
|
|
3332
|
+
tools: finalizing ? [] : missingReturnAttempts > 0 ? outputDefinitions : toolDefinitions,
|
|
3282
3333
|
...separateOutput && !finalizing ? { responseFormat: { type: "text" } } : {},
|
|
3283
3334
|
messages,
|
|
3284
3335
|
emit: false,
|
|
@@ -3309,6 +3360,16 @@ var runReasonToolLoop = async (args) => {
|
|
|
3309
3360
|
);
|
|
3310
3361
|
aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
|
|
3311
3362
|
const toolCalls = step.toolCalls ?? [];
|
|
3363
|
+
if (missingReturnAttempts > 0 && toolCalls.some((call) => !outputByToolName.has(call.name)))
|
|
3364
|
+
throw new import_errors6.KortyxError(
|
|
3365
|
+
"REASON_OUTPUT_CORRECTION_TOOL_DISALLOWED",
|
|
3366
|
+
"A return correction requested a non-output tool.",
|
|
3367
|
+
{
|
|
3368
|
+
category: "validation",
|
|
3369
|
+
retryable: false,
|
|
3370
|
+
safeMessage: "A terminal output correction cannot rerun tools or human-input requests."
|
|
3371
|
+
}
|
|
3372
|
+
);
|
|
3312
3373
|
const ids = /* @__PURE__ */ new Set();
|
|
3313
3374
|
for (const call of toolCalls) {
|
|
3314
3375
|
if (!call.id || ids.has(call.id) || !reused && knownToolCallIds.has(call.id))
|
|
@@ -3329,10 +3390,32 @@ var runReasonToolLoop = async (args) => {
|
|
|
3329
3390
|
});
|
|
3330
3391
|
if (!reused) for (const call of toolCalls) knownToolCallIds.add(call.id);
|
|
3331
3392
|
if (toolCalls.length === 0) {
|
|
3332
|
-
if (Object.keys(useReasonArgs.outputs?.return ?? {}).length > 0 && !returned)
|
|
3333
|
-
|
|
3334
|
-
|
|
3393
|
+
if (Object.keys(useReasonArgs.outputs?.return ?? {}).length > 0 && !returned) {
|
|
3394
|
+
if (missingReturnAttempts < missingReturnCorrections) {
|
|
3395
|
+
messages.push({ role: "assistant", content: step.text });
|
|
3396
|
+
messages.push({
|
|
3397
|
+
role: "user",
|
|
3398
|
+
content: "MISSING_TERMINAL_OUTPUT: No terminal output was published. Select exactly one configured return contract using the completed tool and human-input results. Do not repeat completed calls."
|
|
3399
|
+
});
|
|
3400
|
+
missingReturnAttempts += 1;
|
|
3401
|
+
outputContractCorrections += 1;
|
|
3402
|
+
pending = void 0;
|
|
3403
|
+
pendingStepIndex = void 0;
|
|
3404
|
+
save();
|
|
3405
|
+
continue;
|
|
3406
|
+
}
|
|
3407
|
+
throw new import_errors6.KortyxError(
|
|
3408
|
+
"REASON_TERMINAL_OUTPUT_MISSING",
|
|
3409
|
+
"useReason outputs.return requires one terminal output contract call.",
|
|
3410
|
+
{
|
|
3411
|
+
category: "validation",
|
|
3412
|
+
operation: "useReason",
|
|
3413
|
+
retryable: false,
|
|
3414
|
+
safeMessage: "useReason outputs.return requires one terminal output contract call.",
|
|
3415
|
+
details: { missingReturnAttempts }
|
|
3416
|
+
}
|
|
3335
3417
|
);
|
|
3418
|
+
}
|
|
3336
3419
|
if (interruptConfig?.mode === "required" && interruptHistory.length === 0)
|
|
3337
3420
|
throw new Error(
|
|
3338
3421
|
"useReason interrupts.mode is required, but the model returned a final answer without requesting human input."
|
|
@@ -3642,11 +3725,35 @@ var runReasonToolLoop = async (args) => {
|
|
|
3642
3725
|
throw new Error(
|
|
3643
3726
|
`useReason reached interrupts.maxRequests (${interruptConfig?.maxRequests ?? 1}).`
|
|
3644
3727
|
);
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3728
|
+
let request;
|
|
3729
|
+
try {
|
|
3730
|
+
request = parseWithSchema(
|
|
3731
|
+
resolved.contract.requestSchema,
|
|
3732
|
+
toolCall.input,
|
|
3733
|
+
`useReason interrupt contract "${resolved.name}" request`
|
|
3734
|
+
);
|
|
3735
|
+
} catch (error) {
|
|
3736
|
+
if (!(error instanceof import_errors6.ValidationError)) throw error;
|
|
3737
|
+
const result3 = {
|
|
3738
|
+
toolCallId: toolCall.id,
|
|
3739
|
+
name: toolCall.name,
|
|
3740
|
+
isError: true,
|
|
3741
|
+
content: interruptInputFeedback(
|
|
3742
|
+
error,
|
|
3743
|
+
interruptDefinitions.find((tool2) => tool2.name === toolCall.name)?.inputSchema,
|
|
3744
|
+
toolCall.name,
|
|
3745
|
+
interruptConfig?.mode === "required"
|
|
3746
|
+
)
|
|
3747
|
+
};
|
|
3748
|
+
toolResults.push(result3);
|
|
3749
|
+
messages.push({ role: "tool", ...result3 });
|
|
3750
|
+
traceSpan?.addEvent?.("useReason.interrupt.invalid", {
|
|
3751
|
+
checkpointKey,
|
|
3752
|
+
contract: resolved.name
|
|
3753
|
+
});
|
|
3754
|
+
save();
|
|
3755
|
+
continue;
|
|
3756
|
+
}
|
|
3650
3757
|
traceSpan?.addEvent?.("useReason.interrupt.requested", {
|
|
3651
3758
|
checkpointKey,
|
|
3652
3759
|
contract: resolved.name,
|
|
@@ -3847,11 +3954,15 @@ var runReasonToolLoop = async (args) => {
|
|
|
3847
3954
|
toolResults.push(result2);
|
|
3848
3955
|
allToolResults.push(result2);
|
|
3849
3956
|
if (shouldEmitTool) {
|
|
3957
|
+
const diagnosticId = steps[stepIndex]?.toolObservations?.slice().reverse().find(
|
|
3958
|
+
(observation) => observation.toolCallId === `${opId}:${toolCall.id}`
|
|
3959
|
+
)?.diagnosticId;
|
|
3850
3960
|
emitToolEvent("tool-call-error", {
|
|
3851
3961
|
tool: tool.name,
|
|
3852
3962
|
toolCallId: toolCall.id,
|
|
3853
3963
|
message: result2.content,
|
|
3854
|
-
failure: result2.failure
|
|
3964
|
+
failure: result2.failure,
|
|
3965
|
+
...diagnosticId ? { diagnosticId } : {}
|
|
3855
3966
|
});
|
|
3856
3967
|
}
|
|
3857
3968
|
}
|
|
@@ -4582,10 +4693,10 @@ function parallel(values) {
|
|
|
4582
4693
|
throw new Error("Parallel call membership changed during replay.");
|
|
4583
4694
|
}
|
|
4584
4695
|
for (const callId of group.record.calls) {
|
|
4585
|
-
const
|
|
4586
|
-
if (
|
|
4587
|
-
const index =
|
|
4588
|
-
if (
|
|
4696
|
+
const record2 = callRecord(callId);
|
|
4697
|
+
if (record2?.status !== "interrupted") continue;
|
|
4698
|
+
const index = record2.interrupts.length - 1;
|
|
4699
|
+
if (record2.interrupts[index]?.response !== void 0) continue;
|
|
4589
4700
|
group.record.interrupts.push({ callId, index });
|
|
4590
4701
|
await bridge(callId, index);
|
|
4591
4702
|
throw new Error(
|
|
@@ -4756,26 +4867,26 @@ async function runWorkflowCall(args, task) {
|
|
|
4756
4867
|
version: definition?.version ?? null
|
|
4757
4868
|
});
|
|
4758
4869
|
const key = `__useWorkflow:${args.id}`;
|
|
4759
|
-
let
|
|
4760
|
-
if (
|
|
4870
|
+
let record2 = ctx.currentNodeState.byKey[key];
|
|
4871
|
+
if (record2 && record2.parallelKey !== task.group?.key)
|
|
4761
4872
|
throw new WorkflowCallError(
|
|
4762
4873
|
workflow,
|
|
4763
4874
|
"parallel call membership changed during replay"
|
|
4764
4875
|
);
|
|
4765
|
-
if (
|
|
4876
|
+
if (record2 && record2.fingerprint !== fingerprint)
|
|
4766
4877
|
throw new WorkflowCallError(
|
|
4767
4878
|
workflow,
|
|
4768
4879
|
"call input or target changed during replay"
|
|
4769
4880
|
);
|
|
4770
|
-
if (!
|
|
4771
|
-
|
|
4881
|
+
if (!record2) ctx.node.consumeExecution?.("maxChildInvocations");
|
|
4882
|
+
record2 ??= {
|
|
4772
4883
|
fingerprint,
|
|
4773
4884
|
invocationId: (0, import_node_crypto6.randomUUID)(),
|
|
4774
4885
|
status: "running",
|
|
4775
4886
|
interrupts: [],
|
|
4776
4887
|
...task.group ? { parallelKey: task.group.key } : {}
|
|
4777
4888
|
};
|
|
4778
|
-
ctx.currentNodeState.byKey[key] =
|
|
4889
|
+
ctx.currentNodeState.byKey[key] = record2;
|
|
4779
4890
|
ctx.stateDirty = true;
|
|
4780
4891
|
if (!task.group) ctx.workflowCallActive = true;
|
|
4781
4892
|
const telemetry = ctx.node.workflowCallTelemetry;
|
|
@@ -4783,7 +4894,7 @@ async function runWorkflowCall(args, task) {
|
|
|
4783
4894
|
ctx.currentNodeState.byKey[activationKey] ??= (0, import_node_crypto6.randomUUID)();
|
|
4784
4895
|
const callerNodeExecutionId = ctx.currentNodeState.byKey[activationKey];
|
|
4785
4896
|
const metadata = {
|
|
4786
|
-
invocationId:
|
|
4897
|
+
invocationId: record2.invocationId,
|
|
4787
4898
|
parentInvocationId: telemetry?.correlation?.invocationId ?? null,
|
|
4788
4899
|
branchId: telemetry?.correlation?.branchId ?? telemetry?.correlation?.runId ?? "unknown",
|
|
4789
4900
|
callId: args.id,
|
|
@@ -4793,46 +4904,49 @@ async function runWorkflowCall(args, task) {
|
|
|
4793
4904
|
targetWorkflowId: workflow,
|
|
4794
4905
|
targetVersion: definition?.version ?? null
|
|
4795
4906
|
};
|
|
4796
|
-
|
|
4907
|
+
record2.telemetry = metadata;
|
|
4797
4908
|
const report = (status, content = {}) => {
|
|
4798
|
-
|
|
4909
|
+
record2.sequence = (record2.sequence ?? 0) + 1;
|
|
4799
4910
|
emitWorkflowCall(telemetry, `workflow.call.${status}`, {
|
|
4800
4911
|
...metadata,
|
|
4801
|
-
sequence:
|
|
4912
|
+
sequence: record2.sequence,
|
|
4802
4913
|
...content
|
|
4803
4914
|
});
|
|
4804
4915
|
};
|
|
4805
4916
|
const recordUsage = (usage2) => {
|
|
4806
4917
|
if (!usage2) return;
|
|
4807
4918
|
accumulateTokenUsage({
|
|
4808
|
-
input: usage2.input - (
|
|
4809
|
-
output: usage2.output - (
|
|
4810
|
-
total: usage2.total - (
|
|
4919
|
+
input: usage2.input - (record2.usage?.input ?? 0),
|
|
4920
|
+
output: usage2.output - (record2.usage?.output ?? 0),
|
|
4921
|
+
total: usage2.total - (record2.usage?.total ?? 0)
|
|
4811
4922
|
});
|
|
4812
|
-
|
|
4923
|
+
record2.usage = usage2;
|
|
4813
4924
|
};
|
|
4814
|
-
const cached =
|
|
4925
|
+
const cached = record2.status === "completed";
|
|
4815
4926
|
try {
|
|
4816
|
-
for (const interruption of task.group ? [] :
|
|
4927
|
+
for (const interruption of task.group ? [] : record2.interrupts) {
|
|
4817
4928
|
interruption.response = await awaitInterruptInternal({
|
|
4818
4929
|
request: interruption.request,
|
|
4819
4930
|
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
4820
4931
|
});
|
|
4821
4932
|
}
|
|
4822
|
-
if (
|
|
4933
|
+
if (record2.status === "failed")
|
|
4823
4934
|
throw new WorkflowCallError(
|
|
4824
4935
|
workflow,
|
|
4825
|
-
|
|
4936
|
+
record2.error ?? "child failed",
|
|
4826
4937
|
void 0,
|
|
4827
|
-
|
|
4938
|
+
record2.failure ?? (0, import_errors8.serializeFailure)(void 0)
|
|
4828
4939
|
);
|
|
4829
|
-
if (task.group &&
|
|
4940
|
+
if (task.group && record2.status === "interrupted" && record2.interrupts.at(-1)?.response === void 0)
|
|
4830
4941
|
throw new ParallelChildWaiting();
|
|
4831
|
-
while (
|
|
4942
|
+
while (record2.status !== "completed") {
|
|
4832
4943
|
let outcome;
|
|
4833
4944
|
try {
|
|
4945
|
+
if (Array.isArray(record2.snapshot) && record2.snapshot.length !== 1)
|
|
4946
|
+
throw new import_errors8.WorkflowContractError("Invalid child workflow snapshot.");
|
|
4947
|
+
const snapshot = Array.isArray(record2.snapshot) ? record2.snapshot[0] : record2.snapshot;
|
|
4834
4948
|
report(
|
|
4835
|
-
|
|
4949
|
+
snapshot ? "resumed" : "started",
|
|
4836
4950
|
workflowCallContent(telemetry, "input", input)
|
|
4837
4951
|
);
|
|
4838
4952
|
outcome = await service({
|
|
@@ -4841,9 +4955,9 @@ async function runWorkflowCall(args, task) {
|
|
|
4841
4955
|
workflow,
|
|
4842
4956
|
input,
|
|
4843
4957
|
definition,
|
|
4844
|
-
invocationId:
|
|
4845
|
-
...
|
|
4846
|
-
...
|
|
4958
|
+
invocationId: record2.invocationId,
|
|
4959
|
+
...snapshot ? { snapshot } : {},
|
|
4960
|
+
...record2.interrupts.length ? { response: record2.interrupts.at(-1)?.response } : {}
|
|
4847
4961
|
});
|
|
4848
4962
|
} catch (error) {
|
|
4849
4963
|
const child = error;
|
|
@@ -4852,48 +4966,48 @@ async function runWorkflowCall(args, task) {
|
|
|
4852
4966
|
if ((0, import_core8.isExecutionCancelled)(error)) throw error;
|
|
4853
4967
|
if ((0, import_core8.isExecutionLimitReached)(error)) {
|
|
4854
4968
|
if (child?.__kortyxChildSnapshot)
|
|
4855
|
-
|
|
4969
|
+
record2.snapshot = child.__kortyxChildSnapshot;
|
|
4856
4970
|
report("suspended", {
|
|
4857
4971
|
reason: "limit_reached",
|
|
4858
4972
|
limit: error.limitReached
|
|
4859
4973
|
});
|
|
4860
4974
|
throw error;
|
|
4861
4975
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4976
|
+
record2.status = "failed";
|
|
4977
|
+
record2.failure = (0, import_errors8.serializeFailure)(error);
|
|
4978
|
+
record2.error = record2.failure.message;
|
|
4865
4979
|
report("failed", { error: "Child workflow failed" });
|
|
4866
4980
|
throw new WorkflowCallError(
|
|
4867
4981
|
workflow,
|
|
4868
|
-
|
|
4982
|
+
record2.error,
|
|
4869
4983
|
error,
|
|
4870
|
-
|
|
4984
|
+
record2.failure
|
|
4871
4985
|
);
|
|
4872
4986
|
}
|
|
4873
4987
|
(0, import_core8.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
4874
4988
|
recordUsage(outcome.usage);
|
|
4875
4989
|
if (outcome.status === "completed") {
|
|
4876
|
-
|
|
4877
|
-
workflowCallFingerprint(
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
for (const observation of
|
|
4990
|
+
record2.data = outcome.data;
|
|
4991
|
+
workflowCallFingerprint(record2.data);
|
|
4992
|
+
record2.status = "completed";
|
|
4993
|
+
record2.toolObservations = outcome.toolObservations ?? [];
|
|
4994
|
+
for (const observation of record2.toolObservations)
|
|
4881
4995
|
rememberToolObservation(observation);
|
|
4882
|
-
delete
|
|
4996
|
+
delete record2.snapshot;
|
|
4883
4997
|
report(
|
|
4884
4998
|
"completed",
|
|
4885
|
-
workflowCallContent(telemetry, "output",
|
|
4999
|
+
workflowCallContent(telemetry, "output", record2.data)
|
|
4886
5000
|
);
|
|
4887
5001
|
} else {
|
|
4888
|
-
|
|
4889
|
-
|
|
5002
|
+
record2.status = "interrupted";
|
|
5003
|
+
record2.snapshot = outcome.snapshot;
|
|
4890
5004
|
report("suspended", {
|
|
4891
5005
|
leaf: outcome.request.meta?.workflowCall ?? null
|
|
4892
5006
|
});
|
|
4893
5007
|
const interruption = {
|
|
4894
5008
|
request: outcome.request
|
|
4895
5009
|
};
|
|
4896
|
-
|
|
5010
|
+
record2.interrupts.push(interruption);
|
|
4897
5011
|
if (task.group) throw new ParallelChildWaiting();
|
|
4898
5012
|
interruption.response = await awaitInterruptInternal({
|
|
4899
5013
|
request: interruption.request,
|
|
@@ -4902,12 +5016,12 @@ async function runWorkflowCall(args, task) {
|
|
|
4902
5016
|
}
|
|
4903
5017
|
}
|
|
4904
5018
|
if (cached) {
|
|
4905
|
-
report("reused", workflowCallContent(telemetry, "output",
|
|
4906
|
-
if (
|
|
4907
|
-
await replayToolObservations(
|
|
4908
|
-
delete
|
|
5019
|
+
report("reused", workflowCallContent(telemetry, "output", record2.data));
|
|
5020
|
+
if (record2.restoredToolBranch !== telemetry?.correlation?.branchId)
|
|
5021
|
+
await replayToolObservations(record2.toolObservations ?? [], true);
|
|
5022
|
+
delete record2.restoredToolBranch;
|
|
4909
5023
|
}
|
|
4910
|
-
const data =
|
|
5024
|
+
const data = record2.data ?? {};
|
|
4911
5025
|
return {
|
|
4912
5026
|
data: JSON.parse(JSON.stringify(data))
|
|
4913
5027
|
};
|