@signalridge/pi-workflows 1.7.5 → 1.9.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.
@@ -252,6 +252,12 @@ function emit(hooks: OrchestrationHooks, event: OrchestrationTaskEvent): void {
252
252
  }
253
253
  }
254
254
 
255
+ function detachmentError(task: OrchestrationTask, error: unknown): TypeError {
256
+ return new TypeError(
257
+ `orchestrate() task "${task.id}" results cannot be detached: ${error instanceof Error ? error.message : String(error)}`,
258
+ );
259
+ }
260
+
255
261
  function snapshotContext(
256
262
  task: OrchestrationTask,
257
263
  attempt: number,
@@ -262,9 +268,7 @@ function snapshotContext(
262
268
  try {
263
269
  detachedResults = structuredClone(values);
264
270
  } catch (error: unknown) {
265
- throw new TypeError(
266
- `orchestrate() task "${task.id}" results cannot be detached: ${error instanceof Error ? error.message : String(error)}`,
267
- );
271
+ throw detachmentError(task, error);
268
272
  }
269
273
  return Object.freeze({
270
274
  id: task.id,
@@ -361,6 +365,19 @@ export async function executeOrchestration(
361
365
  });
362
366
  }
363
367
 
368
+ // Every attempt in this layer sees the same barrier state, even after a
369
+ // sibling settles. Detach once here, then again for each callback so nested
370
+ // mutations cannot alter the baseline or another attempt's input.
371
+ const layerStatuses = { ...statuses };
372
+ let layerValues: Record<string, unknown> = {};
373
+ let layerCloneError: { error: unknown } | undefined;
374
+ try {
375
+ layerValues = structuredClone(values);
376
+ } catch (error: unknown) {
377
+ // Keep detachment failures inside the existing per-task retry/error path.
378
+ layerCloneError = { error };
379
+ }
380
+
364
381
  const outcomes = await Promise.all(
365
382
  ready.map(async (task): Promise<{ task: OrchestrationTask; fatal?: { error: unknown } }> => {
366
383
  const record = records.get(task.id);
@@ -369,7 +386,8 @@ export async function executeOrchestration(
369
386
  record.attempts = attempt;
370
387
  try {
371
388
  hooks.throwIfAborted?.();
372
- const value = await task.run(snapshotContext(task, attempt, values, statuses));
389
+ if (layerCloneError) throw detachmentError(task, layerCloneError.error);
390
+ const value = await task.run(snapshotContext(task, attempt, layerValues, layerStatuses));
373
391
  hooks.throwIfAborted?.();
374
392
  pending.delete(task.id);
375
393
  record.status = "completed";