@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 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.1",
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.ProviderRequestError(message, {
3222
- code: "REASON_OUTPUT_CONTRACT_CORRECTION_EXHAUSTED",
3223
- operation: "useReason",
3224
- safeMessage: message,
3225
- retryable: false,
3226
- details: { maxSteps, outputContractCorrections }
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
- throw new Error(
3334
- "useReason outputs.return requires one terminal output contract call."
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
- const request = parseWithSchema(
3646
- resolved.contract.requestSchema,
3647
- toolCall.input,
3648
- `useReason interrupt contract "${resolved.name}" request`
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 record = callRecord(callId);
4586
- if (record?.status !== "interrupted") continue;
4587
- const index = record.interrupts.length - 1;
4588
- if (record.interrupts[index]?.response !== void 0) continue;
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 record = ctx.currentNodeState.byKey[key];
4760
- if (record && record.parallelKey !== task.group?.key)
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 (record && record.fingerprint !== fingerprint)
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 (!record) ctx.node.consumeExecution?.("maxChildInvocations");
4771
- record ??= {
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] = record;
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: record.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
- record.telemetry = metadata;
4907
+ record2.telemetry = metadata;
4797
4908
  const report = (status, content = {}) => {
4798
- record.sequence = (record.sequence ?? 0) + 1;
4909
+ record2.sequence = (record2.sequence ?? 0) + 1;
4799
4910
  emitWorkflowCall(telemetry, `workflow.call.${status}`, {
4800
4911
  ...metadata,
4801
- sequence: record.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 - (record.usage?.input ?? 0),
4809
- output: usage2.output - (record.usage?.output ?? 0),
4810
- total: usage2.total - (record.usage?.total ?? 0)
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
- record.usage = usage2;
4923
+ record2.usage = usage2;
4813
4924
  };
4814
- const cached = record.status === "completed";
4925
+ const cached = record2.status === "completed";
4815
4926
  try {
4816
- for (const interruption of task.group ? [] : record.interrupts) {
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 (record.status === "failed")
4933
+ if (record2.status === "failed")
4823
4934
  throw new WorkflowCallError(
4824
4935
  workflow,
4825
- record.error ?? "child failed",
4936
+ record2.error ?? "child failed",
4826
4937
  void 0,
4827
- record.failure ?? (0, import_errors8.serializeFailure)(void 0)
4938
+ record2.failure ?? (0, import_errors8.serializeFailure)(void 0)
4828
4939
  );
4829
- if (task.group && record.status === "interrupted" && record.interrupts.at(-1)?.response === void 0)
4940
+ if (task.group && record2.status === "interrupted" && record2.interrupts.at(-1)?.response === void 0)
4830
4941
  throw new ParallelChildWaiting();
4831
- while (record.status !== "completed") {
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
- record.snapshot ? "resumed" : "started",
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: record.invocationId,
4845
- ...record.snapshot ? { snapshot: record.snapshot } : {},
4846
- ...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
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
- record.snapshot = child.__kortyxChildSnapshot;
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
- record.status = "failed";
4863
- record.failure = (0, import_errors8.serializeFailure)(error);
4864
- record.error = record.failure.message;
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
- record.error,
4982
+ record2.error,
4869
4983
  error,
4870
- record.failure
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
- record.data = outcome.data;
4877
- workflowCallFingerprint(record.data);
4878
- record.status = "completed";
4879
- record.toolObservations = outcome.toolObservations ?? [];
4880
- for (const observation of record.toolObservations)
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 record.snapshot;
4996
+ delete record2.snapshot;
4883
4997
  report(
4884
4998
  "completed",
4885
- workflowCallContent(telemetry, "output", record.data)
4999
+ workflowCallContent(telemetry, "output", record2.data)
4886
5000
  );
4887
5001
  } else {
4888
- record.status = "interrupted";
4889
- record.snapshot = outcome.snapshot;
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
- record.interrupts.push(interruption);
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", record.data));
4906
- if (record.restoredToolBranch !== telemetry?.correlation?.branchId)
4907
- await replayToolObservations(record.toolObservations ?? [], true);
4908
- delete record.restoredToolBranch;
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 = record.data ?? {};
5024
+ const data = record2.data ?? {};
4911
5025
  return {
4912
5026
  data: JSON.parse(JSON.stringify(data))
4913
5027
  };