@bastani/atomic 0.9.18-alpha.2 → 0.9.18-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 (28) hide show
  1. package/dist/builtin/intercom/CHANGELOG.md +8 -0
  2. package/dist/builtin/intercom/README.md +3 -3
  3. package/dist/builtin/intercom/broker/broker.ts +156 -12
  4. package/dist/builtin/intercom/broker/client.ts +89 -49
  5. package/dist/builtin/intercom/broker/pending-question-index.ts +10 -0
  6. package/dist/builtin/intercom/broker/send-handler.ts +32 -17
  7. package/dist/builtin/intercom/index.bundle.mjs +153 -50
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/skills/intercom/SKILL.md +1 -1
  10. package/dist/builtin/intercom/types.ts +30 -2
  11. package/dist/builtin/mcp/package.json +1 -1
  12. package/dist/builtin/subagents/package.json +1 -1
  13. package/dist/builtin/web-access/package.json +1 -1
  14. package/dist/builtin/workflows/CHANGELOG.md +18 -0
  15. package/dist/builtin/workflows/README.md +11 -2
  16. package/dist/builtin/workflows/builtin/{chunk-ngffz3y8.js → chunk-fghhy2a5.js} +1 -1
  17. package/dist/builtin/workflows/builtin/{chunk-6v0yv8tj.js → chunk-h3r2vkzc.js} +1 -1
  18. package/dist/builtin/workflows/builtin/{chunk-brerg33r.js → chunk-n58a7v26.js} +0 -1
  19. package/dist/builtin/workflows/builtin/goal.js +2 -2
  20. package/dist/builtin/workflows/builtin/index.js +3 -3
  21. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  22. package/dist/builtin/workflows/package.json +1 -1
  23. package/dist/builtin/workflows/src/extension/index.bundle.mjs +258 -49
  24. package/dist/builtin/workflows/src/index.js +35 -9
  25. package/docs/intercom.md +7 -5
  26. package/docs/workflows.md +18 -9
  27. package/npm-shrinkwrap.json +32 -32
  28. package/package.json +3 -3
@@ -54,7 +54,7 @@ var renderContactSupervisorResult = (result, { isPartial }, theme, context) => {
54
54
  return new Text(theme.fg("warning", "Intercom working..."), 0, 0);
55
55
  }
56
56
  const details = result.details;
57
- const failed = Boolean(context.isError || details?.error === true || details?.delivered === false);
57
+ const failed = Boolean(context.isError || details?.error === true || details?.delivered === false && details.queued !== true);
58
58
  let text = failed ? theme.fg("error", "✗ ") : theme.fg("success", "✓ ");
59
59
  text += theme.fg(failed ? "error" : "text", firstTextContent(result));
60
60
  if (details?.messageId && !context.expanded) {
@@ -416,6 +416,9 @@ import { randomUUID as randomUUID2 } from "crypto";
416
416
  function toError(error) {
417
417
  return error instanceof Error ? error : new Error(String(error));
418
418
  }
419
+ function isWorkflowStageRosterEntries(value) {
420
+ return Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null && entry.kind === "workflow-stage" && typeof entry.runId === "string" && typeof entry.stageId === "string" && typeof entry.stageName === "string" && typeof entry.target === "string" && (entry.lifecycle === "pending" || entry.lifecycle === "running") && typeof entry.group === "string" && (entry.sessionId === undefined || typeof entry.sessionId === "string"));
421
+ }
419
422
  var BROKER_SOCKET, GROUP_REQUEST_TIMEOUT_MS = 5000, PRESENCE_ACK_TIMEOUT_MS = 5000, IntercomClient;
420
423
  var init_client = __esm(() => {
421
424
  init_paths();
@@ -636,16 +639,15 @@ var init_client = __esm(() => {
636
639
  break;
637
640
  }
638
641
  case "sessions": {
639
- const { requestId, sessions } = brokerMessage;
640
- if (typeof requestId !== "string" || !Array.isArray(sessions) || !sessions.every(isSessionInfo)) {
642
+ const { requestId, sessions, workflowStages } = brokerMessage;
643
+ if (typeof requestId !== "string" || !Array.isArray(sessions) || !sessions.every(isSessionInfo) || workflowStages !== undefined && !isWorkflowStageRosterEntries(workflowStages)) {
641
644
  throw new Error("Invalid sessions message");
642
645
  }
643
646
  const pending = this.pendingLists.get(requestId);
644
- if (!pending) {
647
+ if (!pending)
645
648
  return;
646
- }
647
649
  this.pendingLists.delete(requestId);
648
- pending.resolve(sessions);
650
+ pending.resolve({ sessions, workflowStages: workflowStages ?? [] });
649
651
  break;
650
652
  }
651
653
  case "groups": {
@@ -885,7 +887,10 @@ var init_client = __esm(() => {
885
887
  }
886
888
  });
887
889
  }
888
- listSessions(group) {
890
+ async listSessions(group) {
891
+ return (await this.listDirectory(group)).sessions;
892
+ }
893
+ listDirectory(group) {
889
894
  let socket;
890
895
  try {
891
896
  socket = this.requireActiveSocket();
@@ -894,21 +899,21 @@ var init_client = __esm(() => {
894
899
  }
895
900
  return new Promise((resolve, reject) => {
896
901
  const requestId = randomUUID2();
897
- const wrappedResolve = (sessions) => {
898
- clearTimeout(timeout);
899
- resolve(sessions);
900
- };
901
- const wrappedReject = (error) => {
902
- clearTimeout(timeout);
903
- reject(error);
904
- };
905
902
  const timeout = setTimeout(() => {
906
- if (this.pendingLists.has(requestId)) {
907
- this.pendingLists.delete(requestId);
908
- wrappedReject(new Error("List sessions timeout"));
909
- }
903
+ if (!this.pendingLists.delete(requestId))
904
+ return;
905
+ reject(new Error("List sessions timeout"));
910
906
  }, 5000);
911
- this.pendingLists.set(requestId, { resolve: wrappedResolve, reject: wrappedReject });
907
+ this.pendingLists.set(requestId, {
908
+ resolve: (directory) => {
909
+ clearTimeout(timeout);
910
+ resolve(directory);
911
+ },
912
+ reject: (error) => {
913
+ clearTimeout(timeout);
914
+ reject(error);
915
+ }
916
+ });
912
917
  try {
913
918
  writeMessage(socket, group === undefined ? { type: "list", requestId } : { type: "list", requestId, group });
914
919
  } catch (error) {
@@ -1026,8 +1031,8 @@ var init_client = __esm(() => {
1026
1031
  }
1027
1032
  });
1028
1033
  }
1029
- registerPendingStageRoute(runId, group, capability) {
1030
- writeMessage(this.requireActiveSocket(), { type: "register_pending_stage_route", runId, group, capability });
1034
+ registerPendingStageRoute(runId, group, capability, stages) {
1035
+ writeMessage(this.requireActiveSocket(), { type: "register_pending_stage_route", runId, group, capability, stages });
1031
1036
  }
1032
1037
  registerLiveWorkflowStageRoute(runId, stageKeys, capability) {
1033
1038
  let socket;
@@ -2574,10 +2579,58 @@ var init_contact_supervisor_tool = __esm(() => {
2574
2579
  init_intercom_utils();
2575
2580
  });
2576
2581
 
2582
+ // dist/builtin/intercom/broker/supervisor-channel.ts
2583
+ var DEFAULT_TTL_MS;
2584
+ var init_supervisor_channel = __esm(() => {
2585
+ DEFAULT_TTL_MS = 10 * 60 * 1000;
2586
+ });
2587
+
2588
+ // dist/builtin/intercom/broker/group-membership.ts
2589
+ var init_group_membership = __esm(() => {
2590
+ init_group();
2591
+ });
2592
+
2593
+ // dist/builtin/intercom/broker/group-isolation.ts
2594
+ var init_group_isolation = __esm(() => {
2595
+ init_group_membership();
2596
+ });
2597
+ // dist/builtin/intercom/broker/send-handler.ts
2598
+ function parsePendingStageTarget(target) {
2599
+ const separator = target.indexOf(":");
2600
+ if (separator < 0)
2601
+ return;
2602
+ const runId = target.slice(0, separator);
2603
+ const stageKey = target.slice(separator + 1);
2604
+ return WORKFLOW_RUN_ID_PATTERN.test(runId) && stageKey.length > 0 ? { runId, stageKey } : undefined;
2605
+ }
2606
+ var WORKFLOW_RUN_ID_PATTERN;
2607
+ var init_send_handler = __esm(() => {
2608
+ init_supervisor_channel();
2609
+ init_group_isolation();
2610
+ init_group_membership();
2611
+ WORKFLOW_RUN_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2612
+ });
2613
+
2577
2614
  // dist/builtin/intercom/intercom-tool.ts
2578
2615
  import { randomUUID as randomUUID4 } from "crypto";
2579
2616
  import { Type as Type2 } from "typebox";
2580
2617
  import { Text as Text3 } from "@earendil-works/pi-tui";
2618
+ async function listDirectory(client, group) {
2619
+ if (typeof client.listDirectory === "function")
2620
+ return client.listDirectory(group);
2621
+ return { sessions: await client.listSessions(group), workflowStages: [] };
2622
+ }
2623
+ async function resolveReplySender(client, logicalTarget, sendTarget) {
2624
+ if (logicalTarget !== sendTarget)
2625
+ return sendTarget;
2626
+ if (parsePendingStageTarget(logicalTarget) === undefined)
2627
+ return sendTarget;
2628
+ const stage = (await listDirectory(client)).workflowStages.find((candidate) => candidate.sessionId !== undefined && (candidate.target === logicalTarget || `${candidate.runId}:${candidate.stageName}` === logicalTarget));
2629
+ return stage?.sessionId ?? sendTarget;
2630
+ }
2631
+ function formatWorkflowStageRow(stage) {
2632
+ return `- **${stage.stageName}** — workflow stage [${stage.lifecycle.toUpperCase()}] — target: \`${stage.target}\`${stage.sessionId === undefined ? "" : ` — intercom session: ${stage.sessionId}`}`;
2633
+ }
2581
2634
  function registerIntercomTool(pi, deps) {
2582
2635
  const { childOrchestratorMetadata, ensureConnected, syncPresenceIdentity, beginReplyWait } = deps;
2583
2636
  const resolveTarget = deps.resolveSessionTarget ?? resolveSessionTargetId;
@@ -2758,7 +2811,8 @@ ${lines.join(`
2758
2811
  case "list": {
2759
2812
  try {
2760
2813
  const mySessionId = connectedClient.sessionId;
2761
- const ownSessions = await connectedClient.listSessions();
2814
+ const ownDirectory = await listDirectory(connectedClient);
2815
+ const ownSessions = ownDirectory.sessions;
2762
2816
  const currentSession = ownSessions.find((session) => session.id === mySessionId);
2763
2817
  if (!currentSession) {
2764
2818
  return {
@@ -2769,32 +2823,49 @@ ${lines.join(`
2769
2823
  }
2770
2824
  const ownGroups = resolveOwnGroups(ownSessions);
2771
2825
  if (requestedGroup && !isOnlyOwnGroup(requestedGroup, ownGroups)) {
2772
- const peeked = await connectedClient.listSessions(requestedGroup);
2773
- const section = peeked.length === 0 ? `**Group [${requestedGroup}] (read-only peek):**
2774
- No sessions in this group.` : `**Group [${requestedGroup}] (read-only peek):**
2775
- ${peeked.map((session) => formatSessionListRow(session, currentSession.cwd, session.id === mySessionId)).join(`
2826
+ const peeked = await listDirectory(connectedClient, requestedGroup);
2827
+ const rows = [
2828
+ ...peeked.sessions.map((session) => formatSessionListRow(session, currentSession.cwd, session.id === mySessionId)),
2829
+ ...peeked.workflowStages.map(formatWorkflowStageRow)
2830
+ ];
2831
+ const section = rows.length === 0 ? `**Group [${requestedGroup}] (read-only peek):**
2832
+ No sessions or workflow stages in this group.` : `**Group [${requestedGroup}] (read-only peek):**
2833
+ ${rows.join(`
2776
2834
  `)}`;
2777
2835
  return {
2778
2836
  content: [{ type: "text", text: `Your groups: ${ownGroups.join(", ")}
2779
2837
 
2780
2838
  ${section}` }],
2781
2839
  isError: false,
2782
- details: { group: ownGroups.at(-1), groups: ownGroups, peekGroup: requestedGroup }
2840
+ details: {
2841
+ group: ownGroups.at(-1),
2842
+ groups: ownGroups,
2843
+ peekGroup: requestedGroup,
2844
+ workflowStages: peeked.workflowStages
2845
+ }
2783
2846
  };
2784
2847
  }
2785
2848
  const otherSessions = ownSessions.filter((session) => session.id !== mySessionId);
2786
2849
  const currentSection = `**Current session** (groups: ${ownGroups.join(", ")}):
2787
2850
  ${formatSessionListRow(currentSession, currentSession.cwd, true)}`;
2788
- const otherSection = otherSessions.length === 0 ? `**Other sessions:**
2789
- No other sessions share any of your groups.` : `**Other visible sessions:**
2790
- ${otherSessions.map((session) => formatSessionListRow(session, currentSession.cwd, false)).join(`
2851
+ const visibleRows = [
2852
+ ...otherSessions.map((session) => formatSessionListRow(session, currentSession.cwd, false)),
2853
+ ...ownDirectory.workflowStages.map(formatWorkflowStageRow)
2854
+ ];
2855
+ const otherSection = visibleRows.length === 0 ? `**Other sessions and workflow stages:**
2856
+ No other sessions or workflow stages share any of your groups.` : `**Other visible sessions and workflow stages:**
2857
+ ${visibleRows.join(`
2791
2858
  `)}`;
2792
2859
  return {
2793
2860
  content: [{ type: "text", text: `${currentSection}
2794
2861
 
2795
2862
  ${otherSection}` }],
2796
2863
  isError: false,
2797
- details: { group: ownGroups.at(-1), groups: ownGroups }
2864
+ details: {
2865
+ group: ownGroups.at(-1),
2866
+ groups: ownGroups,
2867
+ ...ownDirectory.workflowStages.length === 0 ? {} : { workflowStages: ownDirectory.workflowStages }
2868
+ }
2798
2869
  };
2799
2870
  } catch (error) {
2800
2871
  return {
@@ -2957,7 +3028,8 @@ ${message}${attachmentText}`);
2957
3028
  };
2958
3029
  }
2959
3030
  const questionId = randomUUID4();
2960
- const admission = beginReplyWait(sendTo, questionId, _signal);
3031
+ const replyFrom = await resolveReplySender(connectedClient, to, sendTo);
3032
+ const admission = beginReplyWait(replyFrom, questionId, _signal);
2961
3033
  if (!admission.ok) {
2962
3034
  const text = admission.reason === "busy" ? `Too many pending asks (${admission.limit}); reply-wait slots are full` : "Cancelled";
2963
3035
  return {
@@ -3166,6 +3238,7 @@ var init_intercom_tool = __esm(() => {
3166
3238
  init_result_renderers();
3167
3239
  init_intercom_utils();
3168
3240
  init_group();
3241
+ init_send_handler();
3169
3242
  });
3170
3243
 
3171
3244
  // dist/builtin/intercom/ui/compose.ts
@@ -3358,13 +3431,15 @@ class SessionListOverlay {
3358
3431
  done;
3359
3432
  sessions;
3360
3433
  selectedIndex = 0;
3434
+ workflowStages;
3361
3435
  maxVisible = 8;
3362
- constructor(theme, keybindings, currentSession, sessions, done) {
3436
+ constructor(theme, keybindings, currentSession, sessions, done, workflowStages = []) {
3363
3437
  this.theme = theme;
3364
3438
  this.keybindings = keybindings;
3365
3439
  this.currentSession = currentSession;
3366
3440
  this.sessions = sessions;
3367
3441
  this.done = done;
3442
+ this.workflowStages = workflowStages;
3368
3443
  }
3369
3444
  onSessionSelect(sessionId) {
3370
3445
  const session = this.sessions.find((s) => s.id === sessionId);
@@ -3448,6 +3523,18 @@ class SessionListOverlay {
3448
3523
  lines.push(row(this.theme.fg("dim", ` ${this.selectedIndex + 1}/${this.sessions.length}`)));
3449
3524
  }
3450
3525
  }
3526
+ if (this.workflowStages.length > 0) {
3527
+ const visibleStages = this.workflowStages.slice(0, this.maxVisible);
3528
+ lines.push(row());
3529
+ lines.push(row(this.theme.bold(" Workflow Stages")));
3530
+ for (const stage of visibleStages) {
3531
+ lines.push(row(` ${stage.stageName} [${stage.lifecycle.toUpperCase()}]`));
3532
+ lines.push(row(` ${this.theme.fg("dim", stage.target)}`));
3533
+ }
3534
+ if (visibleStages.length < this.workflowStages.length) {
3535
+ lines.push(row(this.theme.fg("dim", ` ${visibleStages.length}/${this.workflowStages.length}`)));
3536
+ }
3537
+ }
3451
3538
  lines.push(row());
3452
3539
  lines.push(border(`├${"─".repeat(contentWidth)}┤`));
3453
3540
  lines.push(row(this.theme.fg("dim", ` ${footer}`)));
@@ -3476,25 +3563,27 @@ function registerIntercomOverlay(pi, deps) {
3476
3563
  deps.syncPresenceIdentity(ctx.sessionManager.getSessionId());
3477
3564
  let currentSession;
3478
3565
  let sessions;
3566
+ let workflowStages;
3479
3567
  let duplicates;
3480
3568
  try {
3481
3569
  const mySessionId = overlayClient.sessionId;
3482
- const allSessions = await overlayClient.listSessions();
3570
+ const directory = await overlayClient.listDirectory();
3483
3571
  if (!deps.getLiveContext(ctx, overlayGeneration))
3484
3572
  return;
3485
- const foundCurrentSession = allSessions.find((s) => s.id === mySessionId);
3573
+ const foundCurrentSession = directory.sessions.find((session) => session.id === mySessionId);
3486
3574
  if (!foundCurrentSession) {
3487
3575
  deps.notifyIfLive(ctx, "Current session is missing from intercom session list", "error", overlayGeneration);
3488
3576
  return;
3489
3577
  }
3490
3578
  currentSession = foundCurrentSession;
3491
- duplicates = duplicateSessionNames(allSessions);
3492
- sessions = allSessions.filter((s) => s.id !== mySessionId);
3579
+ duplicates = duplicateSessionNames(directory.sessions);
3580
+ sessions = directory.sessions.filter((session) => session.id !== mySessionId);
3581
+ workflowStages = directory.workflowStages;
3493
3582
  } catch (error) {
3494
3583
  deps.notifyIfLive(ctx, `Failed to list sessions: ${getErrorMessage(error)}`, "error", overlayGeneration);
3495
3584
  return;
3496
3585
  }
3497
- const selectedSession = await ctx.ui.custom((_tui, theme, keybindings, done) => new SessionListOverlay(theme, keybindings, currentSession, sessions, done), { overlay: true }).catch(() => {
3586
+ const selectedSession = await ctx.ui.custom((_tui, theme, keybindings, done) => new SessionListOverlay(theme, keybindings, currentSession, sessions, done, workflowStages), { overlay: true }).catch(() => {
3498
3587
  return;
3499
3588
  });
3500
3589
  if (!selectedSession || !deps.getLiveContext(ctx, overlayGeneration))
@@ -3662,7 +3751,7 @@ class DeliveredMessageCache {
3662
3751
  ttlMs;
3663
3752
  maxEntries;
3664
3753
  delivered = new Map;
3665
- constructor(ttlMs = DEFAULT_TTL_MS, maxEntries = DEFAULT_MAX_ENTRIES) {
3754
+ constructor(ttlMs = DEFAULT_TTL_MS2, maxEntries = DEFAULT_MAX_ENTRIES) {
3666
3755
  this.ttlMs = ttlMs;
3667
3756
  this.maxEntries = maxEntries;
3668
3757
  }
@@ -3692,9 +3781,9 @@ class DeliveredMessageCache {
3692
3781
  }
3693
3782
  }
3694
3783
  }
3695
- var DEFAULT_TTL_MS, DEFAULT_MAX_ENTRIES = 1e4;
3784
+ var DEFAULT_TTL_MS2, DEFAULT_MAX_ENTRIES = 1e4;
3696
3785
  var init_delivered_message_cache = __esm(() => {
3697
- DEFAULT_TTL_MS = 10 * 60 * 1000;
3786
+ DEFAULT_TTL_MS2 = 10 * 60 * 1000;
3698
3787
  });
3699
3788
 
3700
3789
  // dist/builtin/intercom/terminal-ordering-barrier.ts
@@ -5407,10 +5496,15 @@ function piIntercomExtension(pi, testOverrides = {}) {
5407
5496
  };
5408
5497
  if (!existing)
5409
5498
  pendingStageRouteClients.set(runId, state);
5410
- if (state.client?.isConnected())
5499
+ if (state.client?.isConnected()) {
5500
+ state.route = route;
5501
+ state.client.registerPendingStageRoute(runId, normalizeGroup(route.group), route.capability, route.stages);
5411
5502
  return;
5412
- if (state.promise)
5413
- return state.promise;
5503
+ }
5504
+ if (state.promise) {
5505
+ await state.promise;
5506
+ return ensurePendingStageRouteClient(runId, route);
5507
+ }
5414
5508
  const promise = (async () => {
5415
5509
  await spawnBrokerIfNeeded(config.brokerCommand, config.brokerArgs);
5416
5510
  const nextClient = new IntercomClient(`${currentSessionId}:pending-stage-route:${runId}`);
@@ -5421,7 +5515,7 @@ function piIntercomExtension(pi, testOverrides = {}) {
5421
5515
  await nextClient.disconnect();
5422
5516
  throw new Error("Intercom runtime no longer active");
5423
5517
  }
5424
- nextClient.registerPendingStageRoute(runId, normalizeGroup(route.group), route.capability);
5518
+ nextClient.registerPendingStageRoute(runId, normalizeGroup(route.group), route.capability, route.stages);
5425
5519
  await nextClient.listSessions();
5426
5520
  if (!pendingStageRouteClientIsCurrent(runId, state, contextAtStart, generationAtStart)) {
5427
5521
  await nextClient.disconnect();
@@ -5469,7 +5563,7 @@ function piIntercomExtension(pi, testOverrides = {}) {
5469
5563
  async function registerPendingStageRoute(activeClient, runId, route) {
5470
5564
  const routeGroup = normalizeGroup(route.group);
5471
5565
  if (clientRegistrationGroup === routeGroup) {
5472
- activeClient.registerPendingStageRoute(runId, routeGroup, route.capability);
5566
+ activeClient.registerPendingStageRoute(runId, routeGroup, route.capability, route.stages);
5473
5567
  return;
5474
5568
  }
5475
5569
  await ensurePendingStageRouteClient(runId, { ...route, group: routeGroup });
@@ -5541,7 +5635,11 @@ function piIntercomExtension(pi, testOverrides = {}) {
5541
5635
  pi.events.on(PENDING_STAGE_ROUTE_EVENT, (payload) => {
5542
5636
  if (!isPendingStageRouteRegistrationEvent(payload))
5543
5637
  return;
5544
- pendingStageRoutes.set(payload.runId, { group: payload.group, capability: payload.capability });
5638
+ pendingStageRoutes.set(payload.runId, {
5639
+ group: payload.group,
5640
+ capability: payload.capability,
5641
+ ...payload.stages === undefined ? {} : { stages: payload.stages }
5642
+ });
5545
5643
  const completion = ensureConnected("background").then((activeClient) => registerPendingStageRoute(activeClient, payload.runId, payload));
5546
5644
  payload.completion = completion;
5547
5645
  completion.catch(() => {});
@@ -5943,6 +6041,9 @@ function renderHeavyToolResult(loadedHeavy, name, args) {
5943
6041
  return renderer(...args);
5944
6042
  return renderIntercomToolResult(name, args);
5945
6043
  }
6044
+ function isRecoverableHeavyInitializationDisconnect(error) {
6045
+ return error instanceof Error && error.message === "Client disconnected";
6046
+ }
5946
6047
  function intercom(pi, options = {}) {
5947
6048
  const inheritedDelegatedSessionName = readSubagentEnv("INTERCOM_SESSION_NAME");
5948
6049
  let heavyAttempt = null;
@@ -6096,8 +6197,10 @@ function intercom(pi, options = {}) {
6096
6197
  }, (error) => {
6097
6198
  if (heavyAttempt?.promise === promise)
6098
6199
  heavyAttempt = null;
6099
- const message = error instanceof Error ? error.message : String(error);
6100
- console.error(`Intercom heavy initialization failed; a later call will retry: ${message}`, error);
6200
+ if (!isRecoverableHeavyInitializationDisconnect(error)) {
6201
+ const message = error instanceof Error ? error.message : String(error);
6202
+ console.error(`Intercom heavy initialization failed; a later call will retry: ${message}`, error);
6203
+ }
6101
6204
  });
6102
6205
  return promise;
6103
6206
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.18-alpha.2",
3
+ "version": "0.9.18-alpha.3",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -75,7 +75,7 @@ intercom({ action: "list" })
75
75
  intercom({ action: "ask", to: "6332faab-1111-4222-8333-123456789abc", message: "Which option should I use?" })
76
76
  ```
77
77
 
78
- Live sessions accept an exact full session ID or exact case-insensitive name. Ordinary `send` to a known workflow stage whose session has not initialized uses the exact `<runId>:<stageKey>` identity. Unknown runs and stages retain the ordinary unknown-target failure.
78
+ Live sessions accept an exact full Intercom session ID or exact case-insensitive name. For workflow stages, first use `intercom({ action: "list" })`: materialized stages appear as `PENDING` or `RUNNING` with canonical `<runId>:<stageId>` targets and actual groups. The invocation context can control owned isolated subgroups by exact target, while sibling subgroups and other runs remain isolated. Use queued `send` for `PENDING`; `ask` is supported only for `RUNNING`, where an exact correlated reply returns to the invocation asker.
79
79
 
80
80
  ### Deliver to workflow stages that have not started
81
81
 
@@ -13,6 +13,34 @@ export interface SessionInfo {
13
13
  group?: string;
14
14
  }
15
15
 
16
+ export interface WorkflowStageRosterEntry {
17
+ readonly kind: "workflow-stage";
18
+ readonly runId: string;
19
+ readonly stageId: string;
20
+ readonly stageName: string;
21
+ readonly target: string;
22
+ readonly lifecycle: "pending" | "running";
23
+ readonly group: string;
24
+ /** Broker session identity, present only while the workflow stage is connected. */
25
+ readonly sessionId?: string;
26
+ }
27
+
28
+ export interface SessionDirectory {
29
+ readonly sessions: SessionInfo[];
30
+ readonly workflowStages: WorkflowStageRosterEntry[];
31
+ }
32
+
33
+ export interface WorkflowStageRosterAnnouncement {
34
+ readonly stageId: string;
35
+ readonly stageName: string;
36
+ readonly target: string;
37
+ readonly lifecycle: "pending" | "running";
38
+ readonly routeEligible: boolean;
39
+ /** Actual stage group after workflow invocation ownership resolution. */
40
+ readonly group: string;
41
+ }
42
+
43
+
16
44
  export interface GroupSummary {
17
45
  group: string;
18
46
  sessionCount: number;
@@ -77,7 +105,7 @@ export type ClientMessage =
77
105
  attemptId?: string;
78
106
  }
79
107
  | { type: "pending_stage_notification_result"; requestId: string; delivered: boolean }
80
- | { type: "register_pending_stage_route"; runId: string; group: string; capability: string }
108
+ | { type: "register_pending_stage_route"; runId: string; group: string; capability: string; stages?: WorkflowStageRosterAnnouncement[] }
81
109
  | {
82
110
  type: "register_live_workflow_stage_route";
83
111
  requestId: string;
@@ -107,7 +135,7 @@ export type ClientMessage =
107
135
  export type BrokerMessage =
108
136
  | { type: "registered"; sessionId: string; supervisorSessionId?: string }
109
137
  | { type: "registration_failed"; reason: string }
110
- | { type: "sessions"; requestId: string; sessions: SessionInfo[] }
138
+ | { type: "sessions"; requestId: string; sessions: SessionInfo[]; workflowStages?: WorkflowStageRosterEntry[] }
111
139
  | { type: "groups"; requestId: string; groups: GroupSummary[] }
112
140
  | { type: "membership_ack"; requestId: string; groups: string[] }
113
141
  | { type: "supervisor_authorized"; requestId: string; capability: string; supervisorSessionId: string; childName: string }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.18-alpha.2",
3
+ "version": "0.9.18-alpha.3",
4
4
  "private": true,
5
5
  "description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.18-alpha.2",
3
+ "version": "0.9.18-alpha.3",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.18-alpha.2",
3
+ "version": "0.9.18-alpha.3",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.18-alpha.3] - 2026-09-01
10
+
11
+ ### Added
12
+
13
+ - Workflow status text, interactive list/detail views, and the persistent `BACKGROUND` panel now identify materialized pending stages by display name and canonical stage ID, show exact Intercom targets only when pre-start delivery is available, and distinguish unavailable pending delivery truthfully.
14
+
15
+ ### Changed
16
+
17
+ - Workflow-authoring guidance now distinguishes inspectable workflow orchestration from cross-cutting extension-hook policy, requires companion-extension dependencies to remain explicit, and directs generated workflows toward readable top-level graphs with cohesive source boundaries.
18
+
19
+ ### Fixed
20
+
21
+ - Fixed pending-stage targets in `/workflow status` cards and the persistent `BACKGROUND` panel being truncated into unusable addresses. Status cards now wrap exact targets, while widget metadata uses only bounded pending-stage forms that fit without displacing existing metadata and width-based omissions show an explicit remaining-stage count. ([#2784](https://github.com/bastani-inc/atomic/issues/2784))
22
+ - Fixed `/workflow status` losing pending-stage delivery capability in its bounded graph projection, run detail ellipsizing exact pending targets at narrow widths, terminal runs advertising undeliverable targets, and narrow status cards truncating canonical stage IDs. Exact targets and IDs now wrap where appropriate, while ended runs report pending delivery as unavailable. ([#2784](https://github.com/bastani-inc/atomic/issues/2784))
23
+ - Fixed projected pending stages from terminal nested child runs advertising unusable Intercom targets through their still-live root, pending widget labels exceeding their width budget and evicting tool/elapsed metadata, duplicated Intercom coaching in model-facing status hints, and singular overflow counts using a plural noun. ([#2784](https://github.com/bastani-inc/atomic/issues/2784))
24
+
25
+ - Fixed explicit and automatic stage groups so they become invocation-owned, collision-free subgroups that remain controllable from the workflow invocation while preserving sibling isolation and their exact persisted identity across active durable resume; restored symmetric isolated reviewer groups for Goal and Ralph ([#2784](https://github.com/bastani-inc/atomic/issues/2784)).
26
+
9
27
  ## [0.9.16] - 2026-08-29
10
28
 
11
29
  Cumulative release of the `0.9.16-alpha.1` – `0.9.16-alpha.11` prereleases. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease sections below.
@@ -86,7 +86,7 @@ Execution, replay, and DBOS hydration are the authoritative topology-validation
86
86
 
87
87
  ### Workflow source layout
88
88
 
89
- Keep a small, readable workflow in one entry file. Do not split short one-use prompts, create one file per stage, add wrapper-only modules, hide the graph across files, or use line counts alone as a module boundary.
89
+ Keep a small, readable workflow in one entry file and write it for human maintainers. Keep the graph and control flow visible in the top-level workflow entry file, use stage names that state each stage's responsibility, and make its inputs, outputs, evidence, and success contract explicit. A developer reading the entry file from top to bottom should be able to identify the graph, branches, gates, artifacts, and stop conditions. Avoid both monolithic prompt blobs and gratuitous fragmentation: do not split short one-use prompts, create one file per stage, add wrapper-only modules, hide the graph across files, or use line counts alone as a module boundary.
90
90
 
91
91
  At a meaningful source boundary that improves clarity, reuse, ownership, or testability, keep the graph and control flow in the top-level workflow entry file and extract cohesive concerns: long or reused prompt builders; shared TypeBox schemas and workflow-specific types; model-policy constants shared by several stages; deterministic helpers with their own testable behavior; or reusable child workflow definitions.
92
92
 
@@ -101,6 +101,12 @@ The existing `.atomic/workflows/release-docs.ts` keeps its graph in the entry fi
101
101
  .atomic/workflows/code-review/model-policy.ts
102
102
  ```
103
103
 
104
+ ### Workflow and extension responsibilities
105
+
106
+ Evaluate Atomic extension hooks when a workflow needs fine-grained, cross-cutting tool or session event control. Workflow TypeScript owns the inspectable DAG, stages, handoffs, durable `ctx.tool` side effects, and gates. Extension hooks own cross-cutting session and model-tool policy such as `tool_call` interception, input mutation, or blocking; `tool_result` transformation; context and provider hooks; lifecycle observation; or reusable custom tools. Use hooks only when cross-stage or cross-workflow event control is materially clearer than embedding the policy in each stage. Do not require a companion extension for ordinary workflow logic. See the authoritative [extension event documentation](../coding-agent/docs/extensions.md#events) for hook contracts and ordering.
107
+
108
+ When a workflow depends on a companion extension, make that dependency explicit and package and document the extension with the workflow. If stages use `tools` allowlists, include any custom tools provided by the extension. Document the hook-driven behavior and keep the graph, stage contracts, artifacts, gates, and stop conditions visible in the workflow entry file so readers can distinguish inspectable workflow orchestration from event policy.
109
+
104
110
  ### Workflow-owned side effects
105
111
 
106
112
  Prefer `ctx.tool(name, args, fn)` for workflow-owned TypeScript operations with side effects, including filesystem writes, network mutations, external API actions, and similar deterministic operations orchestrated directly by the workflow definition. Each invocation creates a non-chat, non-attachable durable graph node before `fn` runs; it may appear before, between, after, or without model stages. Atomic durably caches a completed call's serializable result, so resume returns that result without rerunning `fn` or repeating the side effect. Tool-only workflows are valid tracked execution, while a normal return with no stage, child, tool, or explicit exit remains invalid. Keep pure computation and side-effect-free transformations as ordinary TypeScript. Do not wrap agent-stage internals or every function call indiscriminately. The executor closes tool admission before publishing any terminal outcome; calling a retained `ctx.tool` function afterward returns a rejected native promise and creates no callback, retry, graph node, or checkpoint.
@@ -445,7 +451,7 @@ When an item configures both `schema` and `output`, its successful `structured_o
445
451
 
446
452
  `subagent` is available as a default workflow-stage tool on the same terms as main chat: a stage is a top-level session, so it can delegate once, and the children it launches cannot delegate further. Delegation is one level deep and nothing configures it. `tools` allowlists apply to bundled extension tools as well as built-ins; if a stage sets `tools`, list every non-mandatory tool it should see. Ordinary `intercom` remains registered and active in every workflow model stage even under `noTools: "all"`, restrictive allowlists, or exclusions. Restrictions on `subagent`, `web_search`, `fetch_content`, and every other tool are unchanged. Bundled `@bastani/subagents` agent definitions are available to the `subagent` tool in workflow stages, including workflows launched from a subagent child process.
447
453
 
448
- Send material updates through Intercom to every affected workflow stage, including stages that have not started. Address a known pending stage with ordinary `intercom send` at `<runId>:<stageKey>`; Atomic queues the message with workflow state and delivers it through the existing inbound Intercom path when the stage session initializes, before its first model turn. Live stage delivery is immediate. Use `ask` once the stage session is live and can reply. Unknown stage identities retain the ordinary unknown-target failure. Workflow `answer` handles pending human-input prompts, and workflow `resume` handles paused run control.
454
+ Send material updates through Intercom to every affected workflow stage, including stages that have not started. Address a known pending stage on a nonterminal run with ordinary `intercom send` at `<runId>:<stageKey>`; Atomic queues the message with workflow state and delivers it through the existing inbound Intercom path when the stage session initializes, before its first model turn. Live stage delivery is immediate. Status surfaces never advertise a retained pending stage after its run terminates. Use `ask` once the stage session is live and can reply. Unknown stage identities retain the ordinary unknown-target failure. Workflow `answer` handles pending human-input prompts, and workflow `resume` handles paused run control.
449
455
 
450
456
  ### Model fallbacks
451
457
 
@@ -662,6 +668,8 @@ Named workflow launches always run as **background tasks** in interactive sessio
662
668
 
663
669
  Typing into an attached stage chat and pressing Enter steers: the message is consumed after the current assistant response finishes its tool batch and before the next model request, matching normal session steering. Ctrl+F queues a follow-up, consumed only when the agent would otherwise stop. Queued entries belong to the stage session rather than the pane, so leaving the stage and reattaching restores the pending `Steering:` / `Follow-up:` rows, and a detached stage node carries a `✉ N queued` badge in the graph.
664
670
 
671
+ At normal widths, the persistent `BACKGROUND` panel and interactive `/workflow status` list/detail name materialized pending stages by display name and canonical ID. They show `<runId>:<stageId>` only when pre-start Intercom delivery is available on a nonterminal owning run and label unavailable delivery otherwise; retained pending stages on ended runs, including ended nested child runs projected into a live root, never advertise a target. Interactive status cards and run detail wrap a full target onto continuation rows when needed, and narrow status cards preserve the full canonical stage ID rather than ellipsizing it. The single-line panel uses only pending-stage forms that fit its remaining metadata budget; if none fit, it omits the pending label entirely so mode, progress, live-tool, and elapsed/status metadata remains intact. The narrow widget remains aggregate-only.
672
+
665
673
  Named launches return only after startup admission, while the admitted workflow body and stages remain background work. Pre-body setup failures (including invalid input-bound reusable worktrees) are returned immediately from the original tool call as structured failed results with the concrete error and allocated run id; Atomic does not first claim that the workflow started, and it removes the unadmitted run so corrected inputs can be retried immediately. Failures after admission continue to use normal background status and lifecycle notices.
666
674
 
667
675
  Graceful quit is idempotent for already-paused runs and preserves unresolved `ctx.ui` prompts in DBOS. Pausing or interrupting a stage also holds every queued steering and follow-up item in place: no queued turn, late context-bearing delivery, or workflow continuation starts while the stage is paused, and the existing `resume` action releases the items once in their existing per-queue order without queue release itself starting a provider turn. Stable author-callsite-and-composed-nested-scope reservations are created before prompting and released by exact current-format token generation after answer checkpoint, rejection, or abort. Answering while quit/paused cannot advance workflow code until explicit resume.
@@ -710,6 +718,7 @@ Raw stage-chat prompt answer replay is live-memory only. `StageSnapshot.promptAn
710
718
  - **`answer`** — answers one pending primitive or structured workflow prompt while the authoritative root is nonterminal. It accepts `promptId` plus `response`, `text`, or `message`, but never sends stage chat, steers work, resumes a stage, or starts a model turn.
711
719
 
712
720
  Free-form workflow-stage communication uses ordinary Intercom, not the workflow tool. Send to `<runId>:<stageKey>`; Atomic delivers immediately to live stages and queues messages for known stages that have not started.
721
+ The concise model-facing status listing enumerates pending stages with their display name, canonical ID, literal `pending` lifecycle, `pendingStageDeliveryAvailable` value, and an exact Intercom target only when that target is usable on a nonterminal owning run. Retained pending stages on ended root or nested child runs report unavailable delivery and no target, including when a child stage is projected through a live root. Duplicate names remain distinct through canonical IDs. Live `send` delivery is immediate, a known pending-stage `send` queues before the first model turn, and `ask` requires a live reply-capable stage.
713
722
  - **`reload`** — refreshes workflow resources directly in-process instead of queuing a literal `/workflow reload` chat follow-up.
714
723
  - **`models`** — returns safe model-catalog metadata from the configured registry. Each entry contains `provider` (e.g. `openai`), `id` (e.g. `gpt-4`), `fullId` (e.g. `openai/gpt-4`), `isCurrent` (whether this is the active model), and `availableThinkingLevels`, canonically derived from the registry model's `reasoning` and `thinkingLevelMap` metadata. The result is a configured-auth snapshot: it shows which models are present in the registry with configured authentication, not proof of credentials, entitlements, OAuth freshness, or live provider access. No secrets, tokens, or authentication details are returned.
715
724
 
@@ -9,7 +9,7 @@ import {
9
9
  reviewerFailureText,
10
10
  summarizeReviewConvergence,
11
11
  workflowArtifactDirectoryPath
12
- } from "./chunk-brerg33r.js";
12
+ } from "./chunk-n58a7v26.js";
13
13
  import {
14
14
  fold_usage
15
15
  } from "./chunk-7at6dnkr.js";
@@ -8,7 +8,7 @@ import {
8
8
  reverify_consolidated_batch,
9
9
  reviewerFailureText,
10
10
  summarizeReviewConvergence
11
- } from "./chunk-brerg33r.js";
11
+ } from "./chunk-n58a7v26.js";
12
12
  import {
13
13
  fold_usage
14
14
  } from "./chunk-7at6dnkr.js";
@@ -700,7 +700,6 @@ function compactStage(stage) {
700
700
  inputRequest,
701
701
  notices,
702
702
  mcpScope: _mcpScope,
703
- pendingStageDeliveryAvailable: _pendingStageDeliveryAvailable,
704
703
  attemptedModels: _attemptedModels,
705
704
  modelAttempts: _modelAttempts,
706
705
  result: _result,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  goal_default
3
- } from "./chunk-ngffz3y8.js";
4
- import"./chunk-brerg33r.js";
3
+ } from "./chunk-fghhy2a5.js";
4
+ import"./chunk-n58a7v26.js";
5
5
  import"./chunk-7at6dnkr.js";
6
6
  import"./chunk-7430zyas.js";
7
7
  import"./chunk-6fqs7c01.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ralph_default
3
- } from "./chunk-6v0yv8tj.js";
3
+ } from "./chunk-h3r2vkzc.js";
4
4
  import {
5
5
  tournament_default
6
6
  } from "./chunk-2dqb5s2q.js";
@@ -18,8 +18,8 @@ import {
18
18
  } from "./chunk-hzzn6adg.js";
19
19
  import {
20
20
  goal_default
21
- } from "./chunk-ngffz3y8.js";
22
- import"./chunk-brerg33r.js";
21
+ } from "./chunk-fghhy2a5.js";
22
+ import"./chunk-n58a7v26.js";
23
23
  import"./chunk-7at6dnkr.js";
24
24
  import"./chunk-7430zyas.js";
25
25
  import {