@bastani/atomic 0.9.44-alpha.1 → 0.9.44-alpha.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +17 -0
  3. package/dist/builtin/intercom/broker/client-message-validation.ts +6 -0
  4. package/dist/builtin/intercom/broker/client.ts +4 -0
  5. package/dist/builtin/intercom/broker/framing.ts +18 -1
  6. package/dist/builtin/intercom/broker/send-signature.ts +4 -1
  7. package/dist/builtin/intercom/index.bundle.mjs +101 -18
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/skills/intercom/SKILL.md +1 -0
  10. package/dist/builtin/intercom/types.ts +8 -0
  11. package/dist/builtin/mcp/package.json +1 -1
  12. package/dist/builtin/subagents/package.json +1 -1
  13. package/dist/builtin/web-access/index.bundle.mjs +2510 -1308
  14. package/dist/builtin/web-access/package.json +1 -1
  15. package/dist/builtin/workflows/CHANGELOG.md +13 -0
  16. package/dist/builtin/workflows/package.json +1 -1
  17. package/dist/builtin/workflows/src/extension/index.bundle.mjs +363 -37
  18. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +363 -37
  19. package/dist/builtin/workflows/src/index.js +363 -37
  20. package/dist/builtin/workflows/src/local-worker-entry.mjs +363 -37
  21. package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
  22. package/dist/core/agent-session-custom-message-commit.js +59 -0
  23. package/dist/core/agent-session-custom-message-commit.js.map +1 -1
  24. package/dist/core/agent-session-methods.d.ts +1 -0
  25. package/dist/core/agent-session-methods.d.ts.map +1 -1
  26. package/dist/core/agent-session-methods.js.map +1 -1
  27. package/dist/core/agent-session-prompt.d.ts +4 -0
  28. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  29. package/dist/core/agent-session-prompt.js +10 -2
  30. package/dist/core/agent-session-prompt.js.map +1 -1
  31. package/dist/core/agent-session.d.ts +2 -0
  32. package/dist/core/agent-session.d.ts.map +1 -1
  33. package/dist/core/agent-session.js +9 -0
  34. package/dist/core/agent-session.js.map +1 -1
  35. package/dist/core/builtin-packages.d.ts.map +1 -1
  36. package/dist/core/builtin-packages.js +13 -13
  37. package/dist/core/builtin-packages.js.map +1 -1
  38. package/docs/intercom/operations.md +1 -1
  39. package/docs/intercom.md +11 -1
  40. package/docs/subagents.md +9 -1
  41. package/examples/extensions/gondolin/package-lock.json +3 -3
  42. package/npm-shrinkwrap.json +67 -51
  43. package/package.json +4 -4
@@ -1661,6 +1661,17 @@ function removeProcessExitCleanup(cleanup) {
1661
1661
  if (processExitCleanups.delete(cleanup) && processExitCleanups.size === 0)
1662
1662
  process.off("exit", runProcessExitCleanups);
1663
1663
  }
1664
+ function writtenHandle(event) {
1665
+ if (event.type === RUN_EVENT_TYPES.promptAsked || event.type === RUN_EVENT_TYPES.promptReleased)
1666
+ return event.payload.handle;
1667
+ if (event.type === RUN_EVENT_TYPES.checkpointRecorded || event.type === RUN_EVENT_TYPES.messageSettled || event.type === RUN_EVENT_TYPES.messageDelivered)
1668
+ return;
1669
+ return event.payload;
1670
+ }
1671
+ function snapshotNeedsHistory(tail) {
1672
+ const handle = writtenHandle(tail.snapshot.event);
1673
+ return handle !== undefined && (handle.pendingStageMessageChange !== undefined || hasPendingStageDeliveryDelta(handle.pendingStageMessages));
1674
+ }
1664
1675
  function* invoke(action) {
1665
1676
  try {
1666
1677
  const result = action();
@@ -1773,11 +1784,11 @@ class JournalDurableBackend {
1773
1784
  const payload = event.payload;
1774
1785
  this.projection.archivePendingStageDeliveryReceipt(entry.runId, payload.messageId, payload.delivery, payload.accounting);
1775
1786
  } else if (event.type === RUN_EVENT_TYPES.promptAsked || event.type === RUN_EVENT_TYPES.promptReleased) {
1776
- this.projection.applyWorkflowSnapshot(event.payload.handle);
1787
+ this.projection.applyWorkflowSnapshot(decodeHandle(event.payload.handle, this.projection.getWorkflow(entry.runId)?.pendingStageMessages));
1777
1788
  this.prompts.set(entry.runId, new PromptReservationState(event.payload.handle.pendingPrompts, event.payload.prompts));
1778
1789
  } else {
1779
- const handle = event.payload;
1780
1790
  const previous = this.projection.getWorkflow(entry.runId);
1791
+ const handle = decodeHandle(event.payload, previous?.pendingStageMessages);
1781
1792
  this.projection.applyWorkflowSnapshot(handle);
1782
1793
  if (event.type === RUN_EVENT_TYPES.runStarted || previous?.pendingPrompts !== handle.pendingPrompts)
1783
1794
  this.prompts.delete(entry.runId);
@@ -1897,6 +1908,20 @@ class JournalDurableBackend {
1897
1908
  }
1898
1909
  *append(runId, event, options) {
1899
1910
  const lease = yield* this.ownership(runId);
1911
+ const base = this.sequences.get(runId);
1912
+ const pinned = options?.expectSeq;
1913
+ const encoded = base !== undefined && (pinned === undefined || pinned === base) && event.type !== RUN_EVENT_TYPES.runStarted ? encodeHandleEvent(event, this.projection.getWorkflow(runId)?.pendingStageMessages) : undefined;
1914
+ if (encoded !== undefined) {
1915
+ try {
1916
+ return yield* this.write(runId, lease, encoded, { ...options, expectSeq: base });
1917
+ } catch (error) {
1918
+ if (!(error instanceof JournalCasConflictError) || pinned !== undefined)
1919
+ throw error;
1920
+ }
1921
+ }
1922
+ yield* this.write(runId, lease, event, options);
1923
+ }
1924
+ *write(runId, lease, event, options) {
1900
1925
  try {
1901
1926
  const outcome = yield* invoke(() => this.journal.append(lease, event, options));
1902
1927
  const nextSeq = (this.sequences.get(runId) ?? 0) + 1;
@@ -2073,11 +2098,12 @@ class JournalDurableBackend {
2073
2098
  for (const message of messages)
2074
2099
  if (message.runId !== logicalRunId)
2075
2100
  throw new JournalMessageScopeError(logicalRunId, message.runId);
2076
- yield* this.snapshot({
2101
+ const next = {
2077
2102
  ...handle,
2078
2103
  pendingStageMessages: replacePendingStageMessagesForRun(handle.pendingStageMessages ?? [], logicalRunId, messages),
2079
2104
  updatedAt: Math.max(Date.now(), handle.updatedAt + 1)
2080
- }, RUN_EVENT_TYPES.messageQueued);
2105
+ };
2106
+ yield* this.snapshot(next, RUN_EVENT_TYPES.messageQueued);
2081
2107
  return true;
2082
2108
  }
2083
2109
  async archivePendingStageMessage(runId, message) {
@@ -2171,7 +2197,7 @@ class JournalDurableBackend {
2171
2197
  if (summarized !== undefined && summarized.seq === version.seq && summarized.at === version.at)
2172
2198
  return;
2173
2199
  const tail = yield* invoke(() => journal.snapshotTail(runId));
2174
- if (tail !== undefined)
2200
+ if (tail !== undefined && !snapshotNeedsHistory(tail))
2175
2201
  return this.summarize(runId, tail);
2176
2202
  if (summarized === undefined && version.seq === 0)
2177
2203
  return;
@@ -2397,7 +2423,182 @@ class JournalDurableBackend {
2397
2423
  }
2398
2424
  }
2399
2425
  }
2400
- var PROCESS_HOLDER_ID, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError;
2426
+ function identity(message) {
2427
+ return JSON.stringify([message.runId, message.id]);
2428
+ }
2429
+ function uniqueByIdentity(messages) {
2430
+ const result = new Map;
2431
+ const repeated = new Set;
2432
+ for (const message of messages) {
2433
+ const key = identity(message);
2434
+ if (result.has(key))
2435
+ repeated.add(key);
2436
+ result.set(key, message);
2437
+ }
2438
+ for (const key of repeated)
2439
+ result.delete(key);
2440
+ return result;
2441
+ }
2442
+ function isDeltaEncoded(message) {
2443
+ return message.deliveryDelta !== undefined;
2444
+ }
2445
+ function deliveryDelta(base, deliveries) {
2446
+ const drop = [];
2447
+ let kept = 0;
2448
+ base.forEach((delivery, index) => {
2449
+ if (kept < deliveries.length && JSON.stringify(delivery) === JSON.stringify(deliveries[kept]))
2450
+ kept++;
2451
+ else
2452
+ drop.push(index);
2453
+ });
2454
+ const add = deliveries.slice(kept);
2455
+ return drop.length === 0 ? { add } : { drop, add };
2456
+ }
2457
+ function pendingStageFields(message) {
2458
+ return Object.keys(message);
2459
+ }
2460
+ function copyField(target, source, key) {
2461
+ target[key] = source[key];
2462
+ }
2463
+ function isRemovedField(message, key) {
2464
+ return message[key] === undefined;
2465
+ }
2466
+ function json(message) {
2467
+ let text = entryJson.get(message);
2468
+ if (text === undefined) {
2469
+ text = JSON.stringify(message);
2470
+ entryJson.set(message, text);
2471
+ }
2472
+ return text;
2473
+ }
2474
+ function patchEntry(index, base, message) {
2475
+ const set = {};
2476
+ const unset = pendingStageFields(base).filter((key) => base[key] !== undefined && isRemovedField(message, key));
2477
+ for (const key of pendingStageFields(message)) {
2478
+ if (message[key] === undefined || key === "deliveries")
2479
+ continue;
2480
+ if (JSON.stringify(message[key]) !== JSON.stringify(base[key]))
2481
+ copyField(set, message, key);
2482
+ }
2483
+ let delta;
2484
+ if (message.deliveries !== undefined) {
2485
+ if (base.deliveries === undefined)
2486
+ set.deliveries = message.deliveries;
2487
+ else if (JSON.stringify(base.deliveries) !== JSON.stringify(message.deliveries))
2488
+ delta = deliveryDelta(base.deliveries, message.deliveries);
2489
+ }
2490
+ return {
2491
+ patch: index,
2492
+ ...Object.keys(set).length === 0 ? {} : { set },
2493
+ ...unset.length === 0 ? {} : { unset },
2494
+ ...delta === undefined ? {} : { deliveryDelta: delta }
2495
+ };
2496
+ }
2497
+ function encodePendingStageList(previous, next) {
2498
+ const positions = new Map;
2499
+ const repeated = new Set;
2500
+ previous.forEach((message, index) => {
2501
+ const key = identity(message);
2502
+ if (positions.has(key))
2503
+ repeated.add(key);
2504
+ positions.set(key, index);
2505
+ });
2506
+ for (const key of repeated)
2507
+ positions.delete(key);
2508
+ const items = [];
2509
+ let reused = 0;
2510
+ for (const message of next) {
2511
+ const index = positions.get(identity(message));
2512
+ const base = index === undefined ? undefined : previous[index];
2513
+ if (base === undefined) {
2514
+ items.push(message);
2515
+ continue;
2516
+ }
2517
+ reused++;
2518
+ const patched = base === message || json(base) === json(message) ? undefined : patchEntry(index, base, message);
2519
+ if (patched === undefined || Object.keys(patched).length === 1) {
2520
+ const last = items.at(-1);
2521
+ if (Array.isArray(last) && last[0] + last[1] === index)
2522
+ last[1]++;
2523
+ else
2524
+ items.push([index, 1]);
2525
+ continue;
2526
+ }
2527
+ items.push(JSON.stringify(patched).length < json(message).length ? patched : message);
2528
+ }
2529
+ return reused > 0 ? items : undefined;
2530
+ }
2531
+ function decodePendingStageList(previous, items) {
2532
+ const base = (index) => {
2533
+ if (!Number.isInteger(index) || index < 0 || index >= previous.length)
2534
+ throw new Error(`atomic-workflows: journal pending-stage change names missing entry ${index}`);
2535
+ return previous[index];
2536
+ };
2537
+ const result = [];
2538
+ for (const item of items) {
2539
+ if (Array.isArray(item)) {
2540
+ const [start, count] = item;
2541
+ for (let index = start;index < start + count; index++)
2542
+ result.push(base(index));
2543
+ } else if (typeof item.patch === "number") {
2544
+ const { patch, set, unset, deliveryDelta: delta } = item;
2545
+ const entry = { ...base(patch), ...set };
2546
+ for (const key of unset ?? [])
2547
+ delete entry[key];
2548
+ if (delta !== undefined) {
2549
+ const dropped = new Set(delta.drop ?? []);
2550
+ const deliveries = base(patch).deliveries ?? [];
2551
+ entry.deliveries = [...deliveries.filter((_, index) => !dropped.has(index)), ...delta.add];
2552
+ }
2553
+ result.push(entry);
2554
+ } else
2555
+ result.push(item);
2556
+ }
2557
+ return result;
2558
+ }
2559
+ function encodeHandleEvent(event, previous) {
2560
+ const handle = writtenHandle(event);
2561
+ if (handle?.pendingStageMessages === undefined || previous === undefined)
2562
+ return;
2563
+ const change = encodePendingStageList(previous, handle.pendingStageMessages);
2564
+ if (change === undefined)
2565
+ return;
2566
+ const { pendingStageMessages: _full, ...rest } = handle;
2567
+ const encoded = { ...rest, pendingStageMessageChange: change };
2568
+ if (event.type === RUN_EVENT_TYPES.promptAsked || event.type === RUN_EVENT_TYPES.promptReleased)
2569
+ return { ...event, payload: { ...event.payload, handle: encoded } };
2570
+ return { ...event, payload: encoded };
2571
+ }
2572
+ function decodeHandle(handle, previous) {
2573
+ const { pendingStageMessageChange: change, ...rest } = handle;
2574
+ if (change !== undefined)
2575
+ return { ...rest, pendingStageMessages: decodePendingStageList(previous ?? [], change) };
2576
+ if (!hasPendingStageDeliveryDelta(handle.pendingStageMessages))
2577
+ return handle;
2578
+ return {
2579
+ ...handle,
2580
+ pendingStageMessages: decodePendingStageDeliveries(previous ?? [], handle.pendingStageMessages ?? [])
2581
+ };
2582
+ }
2583
+ function decodePendingStageDeliveries(previous, messages) {
2584
+ if (!messages.some(isDeltaEncoded))
2585
+ return messages;
2586
+ const bases = uniqueByIdentity(previous);
2587
+ return messages.map((message) => {
2588
+ if (!isDeltaEncoded(message))
2589
+ return message;
2590
+ const base = bases.get(identity(message))?.deliveries;
2591
+ if (base === undefined)
2592
+ throw new Error(`atomic-workflows: journal delivery change for ${identity(message)} has no base entry`);
2593
+ const { deliveryDelta, ...rest } = message;
2594
+ const dropped = new Set(deliveryDelta.drop ?? []);
2595
+ return { ...rest, deliveries: [...base.filter((_, index) => !dropped.has(index)), ...deliveryDelta.add] };
2596
+ });
2597
+ }
2598
+ function hasPendingStageDeliveryDelta(messages) {
2599
+ return messages?.some(isDeltaEncoded) === true;
2600
+ }
2601
+ var PROCESS_HOLDER_ID, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError, entryJson;
2401
2602
  var init_journal_backend = __esm(() => {
2402
2603
  init_workflow_instance_ownership_error();
2403
2604
  init_backend();
@@ -2455,6 +2656,7 @@ var init_journal_backend = __esm(() => {
2455
2656
  this.name = "JournalMessageScopeError";
2456
2657
  }
2457
2658
  };
2659
+ entryJson = new WeakMap;
2458
2660
  });
2459
2661
 
2460
2662
  // dist/builtin/workflows/src/durable/journal/legacy-journal-tables.ts
@@ -57447,7 +57649,20 @@ function createWorkflowBoundarySegmentsResolver(runs) {
57447
57649
  };
57448
57650
  }
57449
57651
  function workflowBoundaryHops(runs, runId) {
57652
+ return boundaryHopsFrom(new Map(runs.map((run) => [run.id, run])), runId);
57653
+ }
57654
+ function createWorkflowBoundaryHopsResolver(runs) {
57450
57655
  const runById = new Map(runs.map((run) => [run.id, run]));
57656
+ const cache = new Map;
57657
+ return (runId) => {
57658
+ if (cache.has(runId))
57659
+ return cache.get(runId);
57660
+ const hops = boundaryHopsFrom(runById, runId);
57661
+ cache.set(runId, hops);
57662
+ return hops;
57663
+ };
57664
+ }
57665
+ function boundaryHopsFrom(runById, runId) {
57451
57666
  const hops = [];
57452
57667
  let current = runById.get(runId);
57453
57668
  while (current !== undefined && current.parentRunId !== undefined) {
@@ -57473,6 +57688,35 @@ function workflowStagePathVariants(hops, stageSegments) {
57473
57688
  function stageMatchesPathPattern(patternSegments, hops, stageSegments) {
57474
57689
  return workflowStagePathVariants(hops, stageSegments).some((candidate) => matchStagePathSegments(patternSegments, candidate));
57475
57690
  }
57691
+ function createStagePathPatternIndex(patterns) {
57692
+ const byFirstSegment = new Map;
57693
+ const globFirst = [];
57694
+ patterns.forEach((pattern, index) => {
57695
+ const first = pattern[0];
57696
+ if (first === undefined || first.includes("*")) {
57697
+ globFirst.push(index);
57698
+ return;
57699
+ }
57700
+ const bucket = byFirstSegment.get(first);
57701
+ if (bucket === undefined)
57702
+ byFirstSegment.set(first, [index]);
57703
+ else
57704
+ bucket.push(index);
57705
+ });
57706
+ return {
57707
+ matching(hops, stageSegments) {
57708
+ if (patterns.length === 0)
57709
+ return [];
57710
+ const variants = workflowStagePathVariants(hops, stageSegments);
57711
+ const candidates = new Set(globFirst);
57712
+ for (const variant of variants) {
57713
+ for (const index of byFirstSegment.get(variant[0]) ?? [])
57714
+ candidates.add(index);
57715
+ }
57716
+ return [...candidates].sort((left, right) => left - right).filter((index) => variants.some((variant) => matchStagePathSegments(patterns[index], variant)));
57717
+ }
57718
+ };
57719
+ }
57476
57720
  var init_pending_stage_status = __esm(() => {
57477
57721
  init_store_internal();
57478
57722
  init_workflow_stage_target();
@@ -57483,24 +57727,63 @@ function stickyStageDeliveryStatuses(runs, rootRunId, availableBackend) {
57483
57727
  const root = runs.find((run) => run.id === rootRunId);
57484
57728
  const entries = (root?.pendingStageMessages ?? []).filter((entry) => entry.sticky === true);
57485
57729
  const result = [];
57730
+ const patternByTarget = new Map;
57731
+ const patterns = [];
57732
+ const entriesByPattern = [];
57733
+ const ownEntries = [];
57734
+ entries.forEach((entry, index) => {
57735
+ const target = entry.targetPath ?? entry.stageKey;
57736
+ let pattern = patternByTarget.get(target);
57737
+ if (pattern === undefined) {
57738
+ const parsed = parseWorkflowStageTarget(target);
57739
+ pattern = parsed?.rootRunId === rootRunId ? patterns.push(parsed.segments) - 1 : -1;
57740
+ if (pattern >= 0)
57741
+ entriesByPattern.push([]);
57742
+ patternByTarget.set(target, pattern);
57743
+ }
57744
+ if (pattern < 0)
57745
+ return;
57746
+ entriesByPattern[pattern].push(index);
57747
+ ownEntries.push(index);
57748
+ });
57749
+ if (ownEntries.length === 0)
57750
+ return result;
57751
+ const patternIndex = createStagePathPatternIndex(patterns);
57752
+ const deliveriesByEntry = new Map;
57753
+ const entryReceipts = (index, runId, stageId) => {
57754
+ let byStage = deliveriesByEntry.get(index);
57755
+ if (byStage === undefined) {
57756
+ byStage = new Map;
57757
+ for (const delivery of entries[index].deliveries ?? []) {
57758
+ const key = `${delivery.runId}\x00${delivery.stageId}`;
57759
+ const list = byStage.get(key);
57760
+ if (list === undefined)
57761
+ byStage.set(key, [delivery]);
57762
+ else
57763
+ list.push(delivery);
57764
+ }
57765
+ deliveriesByEntry.set(index, byStage);
57766
+ }
57767
+ return byStage.get(`${runId}\x00${stageId}`) ?? [];
57768
+ };
57486
57769
  availableBackend = availableRunDurableBackend(runs, rootRunId) ?? availableBackend;
57487
57770
  const backend = availableBackend === undefined ? undefined : durableBackendForRun(availableBackend, runs, rootRunId);
57771
+ const runById = new Map(runs.map((run) => [run.id, run]));
57772
+ const hopsFor = createWorkflowBoundaryHopsResolver(runs);
57488
57773
  for (const run of runs) {
57489
- if (run.id !== rootRunId && run.rootRunId !== rootRunId && durableRootRunIdForRun(runs, run.id) !== rootRunId)
57774
+ const durableRootRunId = reciprocalWorkflowRootRunId(runById, run.id);
57775
+ if (run.id !== rootRunId && run.rootRunId !== rootRunId && durableRootRunId !== rootRunId)
57490
57776
  continue;
57491
- const resolved = durableRootRunIdForRun(runs, run.id) === rootRunId;
57492
- const hops = workflowBoundaryHops(runs, run.id);
57777
+ const resolved = durableRootRunId === rootRunId;
57778
+ const hops = hopsFor(run.id);
57493
57779
  for (const stage of run.stages) {
57494
57780
  if (stage.nodeKind === "tool" || stage.replayKey?.startsWith("prompt:") === true || stage.replayKey?.startsWith("workflow:") === true || stage.workflowChildRun !== undefined || stage.workflowChild !== undefined)
57495
57781
  continue;
57496
- for (const entry of entries) {
57782
+ const matchedEntries = resolved && hops ? patternIndex.matching(hops, [stage.id, stage.name]).flatMap((pattern) => entriesByPattern[pattern]).sort((left, right) => left - right) : ownEntries;
57783
+ for (const index of matchedEntries) {
57784
+ const entry = entries[index];
57497
57785
  const target = entry.targetPath ?? entry.stageKey;
57498
- const parsed = parseWorkflowStageTarget(target);
57499
- if (!parsed || parsed.rootRunId !== rootRunId)
57500
- continue;
57501
- if (resolved && hops && !stageMatchesPathPattern(parsed.segments, hops, [stage.id, stage.name]))
57502
- continue;
57503
- const receipts = (entry.deliveries ?? []).filter((delivery) => delivery.runId === run.id && delivery.stageId === stage.id);
57786
+ const receipts = entryReceipts(index, run.id, stage.id);
57504
57787
  const delivered = backend?.hasCachedPendingStageDeliveryReceipt?.(rootRunId, entry.id, {
57505
57788
  runId: run.id,
57506
57789
  stageId: stage.id,
@@ -57818,20 +58101,27 @@ function possibleStageRows(runs, rootRunId, rootRun, backend) {
57818
58101
  if (isTerminalRunStatus(rootRun.status))
57819
58102
  return [];
57820
58103
  const stickyQueued = (rootRun.pendingStageMessages ?? []).filter((entry) => entry.sticky === true && entry.status === "queued");
58104
+ const segmentsByTarget = new Map;
57821
58105
  const stickySegments = (entry) => {
57822
- const parsed = parseWorkflowStageTarget(entry.targetPath ?? entry.stageKey);
57823
- return parsed?.rootRunId === rootRunId ? parsed.segments : undefined;
58106
+ const target = entry.targetPath ?? entry.stageKey;
58107
+ if (segmentsByTarget.has(target))
58108
+ return segmentsByTarget.get(target);
58109
+ const parsed = parseWorkflowStageTarget(target);
58110
+ const segments = parsed?.rootRunId === rootRunId ? parsed.segments : undefined;
58111
+ segmentsByTarget.set(target, segments);
58112
+ return segments;
57824
58113
  };
57825
58114
  const scanEntries = stickyQueued.filter((entry) => {
57826
58115
  const segments = stickySegments(entry);
57827
58116
  return !(segments !== undefined && segments.length === 1 && segments[0] === "**");
57828
58117
  });
58118
+ const liveStageMatches = materializedStageMatcher(runs, rootRunId);
57829
58119
  const rows = [];
57830
58120
  for (const scanEntry of rootRun.possibleStages ?? []) {
57831
58121
  const rowSegments = splitStagePathSegments(scanEntry);
57832
58122
  if (rowSegments.length === 0 || rowSegments.some((segment) => segment.length === 0))
57833
58123
  continue;
57834
- if (!rowSegments.some((segment) => segment.includes("*")) && materializedStageMatches(runs, rootRunId, rowSegments)) {
58124
+ if (!rowSegments.some((segment) => segment.includes("*")) && liveStageMatches(rowSegments)) {
57835
58125
  continue;
57836
58126
  }
57837
58127
  rows.push({
@@ -57852,28 +58142,43 @@ function possibleStageRows(runs, rootRunId, rootRun, backend) {
57852
58142
  }).length
57853
58143
  });
57854
58144
  const statuses = stickyStageDeliveryStatuses(runs, rootRunId, backend);
57855
- for (const target of new Set(statuses.map((status) => status.target))) {
57856
- if (rows.some((row) => row.target === target))
58145
+ const statusesByTarget = new Map;
58146
+ for (const status of statuses) {
58147
+ const forTarget = statusesByTarget.get(status.target);
58148
+ if (forTarget === undefined)
58149
+ statusesByTarget.set(status.target, [status]);
58150
+ else
58151
+ forTarget.push(status);
58152
+ }
58153
+ const queuedByTarget = new Map;
58154
+ for (const entry of stickyQueued) {
58155
+ const target = entry.targetPath ?? entry.stageKey;
58156
+ queuedByTarget.set(target, (queuedByTarget.get(target) ?? 0) + 1);
58157
+ }
58158
+ const rowTargets = new Set(rows.map((row) => row.target));
58159
+ for (const target of statusesByTarget.keys()) {
58160
+ if (rowTargets.has(target))
57857
58161
  continue;
57858
- rows.push({
57859
- target,
57860
- queuedCount: stickyQueued.filter((entry) => (entry.targetPath ?? entry.stageKey) === target).length,
57861
- deliveryOnly: true
57862
- });
58162
+ rowTargets.add(target);
58163
+ rows.push({ target, queuedCount: queuedByTarget.get(target) ?? 0, deliveryOnly: true });
57863
58164
  }
57864
58165
  for (let index = 0;index < rows.length; index++) {
57865
58166
  const row = rows[index];
57866
- const deliveryStates = statuses.filter((status) => status.target === row.target);
57867
- if (deliveryStates.length > 0)
58167
+ const deliveryStates = statusesByTarget.get(row.target);
58168
+ if (deliveryStates !== undefined)
57868
58169
  rows[index] = { ...row, deliveryStates };
57869
58170
  }
57870
58171
  return rows;
57871
58172
  }
57872
- function materializedStageMatches(runs, rootRunId, entrySegments) {
57873
- for (const run of runs) {
57874
- if (durableRootRunIdForRun(runs, run.id) !== rootRunId)
57875
- continue;
57876
- const hops = workflowBoundaryHops(runs, run.id);
58173
+ function materializedStageMatcher(runs, rootRunId) {
58174
+ const runById = new Map(runs.map((run) => [run.id, run]));
58175
+ const hopsFor = createWorkflowBoundaryHopsResolver(runs);
58176
+ const invocationRuns = runs.filter((run) => reciprocalWorkflowRootRunId(runById, run.id) === rootRunId);
58177
+ return (entrySegments) => materializedStageMatches(invocationRuns, hopsFor, entrySegments);
58178
+ }
58179
+ function materializedStageMatches(invocationRuns, hopsFor, entrySegments) {
58180
+ for (const run of invocationRuns) {
58181
+ const hops = hopsFor(run.id);
57877
58182
  if (hops === undefined)
57878
58183
  continue;
57879
58184
  for (const stage of run.stages) {
@@ -58148,10 +58453,12 @@ async function recordConfirmedStickyDeliveries(activeStore, event) {
58148
58453
  }
58149
58454
  function matchingLiveStages(runs, rootRunId, patternSegments) {
58150
58455
  const matches = [];
58456
+ const runById = new Map(runs.map((run) => [run.id, run]));
58457
+ const hopsFor = createWorkflowBoundaryHopsResolver(runs);
58151
58458
  for (const run of runs) {
58152
- if (durableRootRunIdForRun(runs, run.id) !== rootRunId)
58459
+ if (reciprocalWorkflowRootRunId(runById, run.id) !== rootRunId)
58153
58460
  continue;
58154
- const hops = workflowBoundaryHops(runs, run.id);
58461
+ const hops = hopsFor(run.id);
58155
58462
  if (hops === undefined)
58156
58463
  continue;
58157
58464
  for (const stage of run.stages) {
@@ -58287,11 +58594,26 @@ function writeMessage(socket, msg, callback) {
58287
58594
  }
58288
58595
  function createMessageReader(onMessage, onError) {
58289
58596
  let buffer = Buffer.alloc(0);
58597
+ let pending = [];
58598
+ let pendingLength = 0;
58599
+ let awaitedLength = 0;
58290
58600
  return (data) => {
58291
- buffer = Buffer.concat([buffer, data]);
58601
+ if (pendingLength + data.length < awaitedLength) {
58602
+ pending.push(data);
58603
+ pendingLength += data.length;
58604
+ return;
58605
+ }
58606
+ buffer = pending.length === 0 ? Buffer.concat([buffer, data]) : Buffer.concat([...pending, data]);
58607
+ pending = [];
58608
+ pendingLength = 0;
58609
+ awaitedLength = 0;
58292
58610
  while (buffer.length >= 4) {
58293
58611
  const length = buffer.readUInt32BE(0);
58294
58612
  if (buffer.length < 4 + length) {
58613
+ pending = [buffer];
58614
+ pendingLength = buffer.length;
58615
+ awaitedLength = 4 + length;
58616
+ buffer = Buffer.alloc(0);
58295
58617
  break;
58296
58618
  }
58297
58619
  const payload = buffer.subarray(4, 4 + length);
@@ -58403,7 +58725,8 @@ function buildSendSignature(to, options) {
58403
58725
  attachments: canonicalizeAttachmentsForSendSignature(options.attachments) ?? [],
58404
58726
  replyTo: options.replyTo ?? null,
58405
58727
  expectsReply: options.expectsReply ?? false,
58406
- replyError: options.replyError ?? null
58728
+ replyError: options.replyError ?? null,
58729
+ ...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery }
58407
58730
  });
58408
58731
  }
58409
58732
 
@@ -58589,6 +58912,8 @@ function isMessage(value2) {
58589
58912
  return false;
58590
58913
  if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean")
58591
58914
  return false;
58915
+ if (message.delivery !== undefined && message.delivery !== "steer" && message.delivery !== "followUp" && message.delivery !== "interrupt")
58916
+ return false;
58592
58917
  if (message.replyError !== undefined && typeof message.replyError !== "string")
58593
58918
  return false;
58594
58919
  if (message.source !== undefined && !isMessageSource(message.source))
@@ -59798,6 +60123,7 @@ var init_client = __esm(() => {
59798
60123
  timestamp: Date.now(),
59799
60124
  replyTo: options.replyTo,
59800
60125
  expectsReply: options.expectsReply,
60126
+ ...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery },
59801
60127
  replyError: options.replyError,
59802
60128
  source: this._messageSource,
59803
60129
  content: { text: options.text, attachments: options.attachments }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-custom-message-commit.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-custom-message-commit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAO9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA6BnD,6EAA6E;AAC7E,wBAAsB,2BAA2B,CAAC,CAAC,EAClD,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmGf;AAED,wBAAgB,4BAA4B,CAAC,CAAC,EAC7C,IAAI,EAAE,YAAY,EAClB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,+EAA+E;AAC/E,wBAAsB,4BAA4B,CAAC,CAAC,EACnD,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED,wBAAgB,6BAA6B,CAAC,CAAC,EAC9C,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,eAAO,MAAM,sCAAsC;;;CAGlD,CAAC"}
1
+ {"version":3,"file":"agent-session-custom-message-commit.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-custom-message-commit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAQ9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAmEnD,6EAA6E;AAC7E,wBAAsB,2BAA2B,CAAC,CAAC,EAClD,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA+Gf;AAED,wBAAgB,4BAA4B,CAAC,CAAC,EAC7C,IAAI,EAAE,YAAY,EAClB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,+EAA+E;AAC/E,wBAAsB,4BAA4B,CAAC,CAAC,EACnD,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA2Df;AAED,wBAAgB,6BAA6B,CAAC,CAAC,EAC9C,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,eAAO,MAAM,sCAAsC;;;CAGlD,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import { resolveWorkflowStageDeliveryTarget } from "./agent-session-delivery-forwarding.js";
2
2
  import { admitProtectedStreamingCustomMessage, queueProtectedStreamingCustomMessage, queueProtectedStreamingCustomMessages, triggerProtectedStreamingCustomMessages, } from "./agent-session-persistent-custom-messages.js";
3
+ import { deliverQueuedInputIfIdle } from "./agent-session-prompt.js";
4
+ import { TASK_COMPLETION_MESSAGE_TYPE } from "./tasks/completion.js";
3
5
  async function appendDurableStageAdmission(session, message) {
4
6
  await session._appendCustomMessage(message);
5
7
  }
@@ -23,15 +25,54 @@ async function commitPriorityMessage(session, message) {
23
25
  void turn.catch(() => { });
24
26
  }
25
27
  }
28
+ /**
29
+ * #3919: before a fresh stage's own prompt is submitted, turn-starting input is that prompt's startup context.
30
+ * Once the prompt is in preflight, a run has started, or queued input is paused, the ordinary admission paths
31
+ * own ordering. The stage's own task completions follow its own work, so they never precede its prompt.
32
+ */
33
+ function holdsForFirstStagePrompt(session, message, options) {
34
+ return (session._workflowStageAwaitingFirstPrompt &&
35
+ session._activePromptCount === 0 &&
36
+ !session._queuedMessagesPaused &&
37
+ message.customType !== TASK_COMPLETION_MESSAGE_TYPE &&
38
+ options?.triggerTurn === true &&
39
+ options.excludeFromContext !== true &&
40
+ options.deliverAs !== "nextTurn");
41
+ }
42
+ /**
43
+ * #3919: once the stage prompt is submitted but before it owns a run (its input hooks), a turn-starting
44
+ * message must not start a competing turn. It queues behind the pending prompt; interrupts keep their own path.
45
+ */
46
+ function queuesBehindFirstStagePrompt(session, options) {
47
+ return (session._workflowStageAwaitingFirstPrompt &&
48
+ session._activePromptCount > 0 &&
49
+ !session._queuedMessagesPaused &&
50
+ options?.triggerTurn === true &&
51
+ options.excludeFromContext !== true &&
52
+ options.deliverAs !== "nextTurn" &&
53
+ options.deliverAs !== "interrupt");
54
+ }
26
55
  /** Commit one delivery whose source generation already granted admission. */
27
56
  export async function commitAdmittedCustomMessage(session, appMessage, options) {
28
57
  const owner = resolveWorkflowStageDeliveryTarget(session);
29
58
  if (owner !== session)
30
59
  return commitAdmittedCustomMessage(owner, appMessage, options);
31
60
  const self = session;
61
+ if (holdsForFirstStagePrompt(self, appMessage, options)) {
62
+ await appendDurableStageAdmission(self, appMessage);
63
+ return;
64
+ }
32
65
  const useProtectedReconciliation = options?.persistWhenStreaming === true &&
33
66
  options.excludeFromContext !== true &&
34
67
  (options.triggerTurn === true || options.stageAdmissionKey !== undefined);
68
+ if (queuesBehindFirstStagePrompt(self, options)) {
69
+ const delivery = options?.deliverAs === "followUp" ? "followUp" : "steer";
70
+ if (useProtectedReconciliation)
71
+ await queueProtectedStreamingCustomMessage(self, appMessage, delivery);
72
+ else
73
+ self._queueAgentMessage(appMessage, delivery);
74
+ return;
75
+ }
35
76
  if (useProtectedReconciliation &&
36
77
  options?.triggerTurn &&
37
78
  options.deliverAs === "interrupt" &&
@@ -78,6 +119,9 @@ export async function commitAdmittedCustomMessage(session, appMessage, options)
78
119
  }
79
120
  else if ((self.isStreaming || self._isPreparingRun) && useProtectedReconciliation) {
80
121
  await queueProtectedStreamingCustomMessage(self, appMessage, options?.deliverAs === "followUp" ? "followUp" : "steer");
122
+ // The durable card append can yield past the run's last queue drain.
123
+ if (!self.isStreaming)
124
+ deliverQueuedInputIfIdle(self);
81
125
  }
82
126
  else if ((self.isStreaming || self._isPreparingRun) && options?.persistWhenStreaming === true) {
83
127
  await appendDurableStageAdmission(self, appMessage);
@@ -130,8 +174,21 @@ export async function commitAdmittedCustomMessages(session, appMessages, options
130
174
  if (owner !== session)
131
175
  return commitAdmittedCustomMessages(owner, appMessages, options);
132
176
  const self = session;
177
+ if (appMessages.every((item) => holdsForFirstStagePrompt(self, item, options))) {
178
+ for (const item of appMessages)
179
+ await appendDurableStageAdmission(self, item);
180
+ return;
181
+ }
133
182
  const useProtectedReconciliation = options?.persistWhenStreaming === true && options.triggerTurn === true && options.excludeFromContext !== true;
134
183
  const delivery = options?.deliverAs === "followUp" ? "followUp" : "steer";
184
+ if (queuesBehindFirstStagePrompt(self, options)) {
185
+ if (useProtectedReconciliation)
186
+ await queueProtectedStreamingCustomMessages(self, appMessages, delivery);
187
+ else
188
+ for (const item of appMessages)
189
+ self._queueAgentMessage(item, delivery);
190
+ return;
191
+ }
135
192
  if (options?.deliverAs === "nextTurn") {
136
193
  self._pendingNextTurnMessages.push(...appMessages);
137
194
  }
@@ -166,6 +223,8 @@ export async function commitAdmittedCustomMessages(session, appMessages, options
166
223
  }
167
224
  else if ((self.isStreaming || self._isPreparingRun) && useProtectedReconciliation) {
168
225
  await queueProtectedStreamingCustomMessages(self, appMessages, delivery);
226
+ if (!self.isStreaming)
227
+ deliverQueuedInputIfIdle(self);
169
228
  }
170
229
  else if ((self.isStreaming || self._isPreparingRun) && options?.persistWhenStreaming === true) {
171
230
  for (const item of appMessages)