@bastani/atomic 0.9.3-alpha.1 → 0.9.3-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 (175) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +21 -0
  3. package/dist/builtin/cursor/README.md +2 -1
  4. package/dist/builtin/cursor/package.json +2 -2
  5. package/dist/builtin/cursor/src/cursor-models-raw.json +2 -9
  6. package/dist/builtin/cursor/src/model-mapper.ts +14 -3
  7. package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +22 -0
  8. package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +53 -13
  9. package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +24 -7
  10. package/dist/builtin/cursor/src/proto/protobuf-codec.ts +3 -2
  11. package/dist/builtin/cursor/src/stream.ts +5 -11
  12. package/dist/builtin/cursor/src/transport-types.ts +3 -0
  13. package/dist/builtin/cursor/src/transport.ts +1 -0
  14. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  15. package/dist/builtin/intercom/package.json +1 -1
  16. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  17. package/dist/builtin/mcp/package.json +1 -1
  18. package/dist/builtin/subagents/CHANGELOG.md +15 -0
  19. package/dist/builtin/subagents/package.json +1 -1
  20. package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -0
  21. package/dist/builtin/subagents/src/extension/index.ts +6 -3
  22. package/dist/builtin/subagents/src/extension/schemas.ts +0 -5
  23. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -4
  24. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +15 -1
  25. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +35 -1
  26. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -2
  27. package/dist/builtin/subagents/src/shared/types-async.ts +1 -0
  28. package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +27 -5
  29. package/dist/builtin/subagents/src/tui/render-layout.ts +27 -4
  30. package/dist/builtin/subagents/src/tui/render-result-animation.ts +22 -31
  31. package/dist/builtin/subagents/src/tui/render-result-compact.ts +6 -6
  32. package/dist/builtin/subagents/src/tui/render-result.ts +20 -19
  33. package/dist/builtin/subagents/src/tui/render-status-progress.ts +3 -3
  34. package/dist/builtin/subagents/src/tui/render-widget.ts +46 -7
  35. package/dist/builtin/subagents/src/tui/render.ts +2 -2
  36. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  37. package/dist/builtin/web-access/package.json +1 -1
  38. package/dist/builtin/workflows/CHANGELOG.md +49 -0
  39. package/dist/builtin/workflows/README.md +1 -1
  40. package/dist/builtin/workflows/package.json +1 -1
  41. package/dist/builtin/workflows/src/authoring.d.ts +1 -1
  42. package/dist/builtin/workflows/src/durable/backend.ts +343 -0
  43. package/dist/builtin/workflows/src/durable/child-primitive.ts +79 -0
  44. package/dist/builtin/workflows/src/durable/dbos-backend.ts +421 -0
  45. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +171 -0
  46. package/dist/builtin/workflows/src/durable/factory.ts +96 -0
  47. package/dist/builtin/workflows/src/durable/file-backend.ts +433 -0
  48. package/dist/builtin/workflows/src/durable/index.ts +73 -0
  49. package/dist/builtin/workflows/src/durable/resume-catalog.ts +217 -0
  50. package/dist/builtin/workflows/src/durable/resume-runtime.ts +299 -0
  51. package/dist/builtin/workflows/src/durable/scoped-backend.ts +171 -0
  52. package/dist/builtin/workflows/src/durable/stage-primitive.ts +284 -0
  53. package/dist/builtin/workflows/src/durable/tool-primitive.ts +180 -0
  54. package/dist/builtin/workflows/src/durable/types.ts +168 -0
  55. package/dist/builtin/workflows/src/durable/ui-primitive.ts +96 -0
  56. package/dist/builtin/workflows/src/engine/options.ts +3 -0
  57. package/dist/builtin/workflows/src/engine/primitives/parallel.ts +2 -2
  58. package/dist/builtin/workflows/src/engine/primitives/task.ts +4 -4
  59. package/dist/builtin/workflows/src/engine/primitives/ui.ts +22 -8
  60. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +8 -0
  61. package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +69 -0
  62. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +31 -0
  63. package/dist/builtin/workflows/src/engine/run.ts +148 -6
  64. package/dist/builtin/workflows/src/engine/runtime.ts +8 -2
  65. package/dist/builtin/workflows/src/extension/extension-factory.ts +6 -12
  66. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +5 -1
  67. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
  68. package/dist/builtin/workflows/src/extension/runtime.ts +48 -9
  69. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +143 -4
  70. package/dist/builtin/workflows/src/runs/background/quit.ts +61 -0
  71. package/dist/builtin/workflows/src/runs/background/status.ts +1 -0
  72. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +5 -5
  73. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +74 -33
  74. package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +20 -1
  75. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +8 -7
  76. package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
  77. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +1 -1
  78. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +19 -2
  79. package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +4 -0
  80. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -10
  81. package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +5 -1
  82. package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +25 -0
  83. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -0
  84. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +16 -0
  85. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +20 -0
  86. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +23 -1
  87. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +30 -1
  88. package/dist/builtin/workflows/src/shared/store-public-types.ts +6 -2
  89. package/dist/builtin/workflows/src/shared/store-run-methods.ts +12 -6
  90. package/dist/builtin/workflows/src/shared/types.ts +55 -0
  91. package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -1
  92. package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +41 -0
  93. package/dist/builtin/workflows/src/tui/graph-view-input.ts +82 -24
  94. package/dist/builtin/workflows/src/tui/graph-view-render.ts +7 -0
  95. package/dist/builtin/workflows/src/tui/graph-view-state.ts +22 -2
  96. package/dist/builtin/workflows/src/tui/graph-view-types.ts +4 -5
  97. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -11
  98. package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +9 -3
  99. package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +11 -2
  100. package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +35 -0
  101. package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +51 -17
  102. package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +36 -0
  103. package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +5 -1
  104. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +3 -1
  105. package/dist/builtin/workflows/src/tui/status-list.ts +14 -2
  106. package/dist/builtin/workflows/src/tui/widget.ts +23 -8
  107. package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +5 -4
  108. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
  109. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +151 -0
  110. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  111. package/dist/core/extensions/loader-virtual-modules.js +47 -30
  112. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  113. package/dist/core/messages.d.ts +1 -0
  114. package/dist/core/messages.d.ts.map +1 -1
  115. package/dist/core/messages.js +46 -1
  116. package/dist/core/messages.js.map +1 -1
  117. package/dist/core/sdk.d.ts.map +1 -1
  118. package/dist/core/sdk.js +12 -0
  119. package/dist/core/sdk.js.map +1 -1
  120. package/dist/core/session-manager-core.d.ts +15 -7
  121. package/dist/core/session-manager-core.d.ts.map +1 -1
  122. package/dist/core/session-manager-core.js +20 -9
  123. package/dist/core/session-manager-core.js.map +1 -1
  124. package/dist/core/session-manager-entries.d.ts +2 -2
  125. package/dist/core/session-manager-entries.d.ts.map +1 -1
  126. package/dist/core/session-manager-entries.js +9 -3
  127. package/dist/core/session-manager-entries.js.map +1 -1
  128. package/dist/core/session-manager-history.d.ts.map +1 -1
  129. package/dist/core/session-manager-history.js +2 -1
  130. package/dist/core/session-manager-history.js.map +1 -1
  131. package/dist/core/session-manager-list.d.ts +3 -3
  132. package/dist/core/session-manager-list.d.ts.map +1 -1
  133. package/dist/core/session-manager-list.js +27 -8
  134. package/dist/core/session-manager-list.js.map +1 -1
  135. package/dist/core/session-manager-storage.d.ts +3 -1
  136. package/dist/core/session-manager-storage.d.ts.map +1 -1
  137. package/dist/core/session-manager-storage.js +55 -12
  138. package/dist/core/session-manager-storage.js.map +1 -1
  139. package/dist/core/session-manager-tool-dependencies.d.ts +10 -0
  140. package/dist/core/session-manager-tool-dependencies.d.ts.map +1 -0
  141. package/dist/core/session-manager-tool-dependencies.js +133 -0
  142. package/dist/core/session-manager-tool-dependencies.js.map +1 -0
  143. package/dist/core/session-manager-types.d.ts +22 -0
  144. package/dist/core/session-manager-types.d.ts.map +1 -1
  145. package/dist/core/session-manager-types.js.map +1 -1
  146. package/dist/core/session-manager.d.ts +2 -2
  147. package/dist/core/session-manager.d.ts.map +1 -1
  148. package/dist/core/session-manager.js +1 -1
  149. package/dist/core/session-manager.js.map +1 -1
  150. package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +1 -0
  151. package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/chat-session-host-runtime.js +12 -0
  153. package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
  154. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts +4 -0
  155. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -0
  156. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js +131 -0
  157. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -0
  158. package/dist/modes/interactive/components/chat-session-host.d.ts +2 -0
  159. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  160. package/dist/modes/interactive/components/chat-session-host.js +7 -1
  161. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  162. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/chat-transcript.js +15 -4
  164. package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
  165. package/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  166. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  167. package/dist/modes/interactive/components/tool-execution.js +26 -0
  168. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  169. package/docs/compaction.md +2 -0
  170. package/docs/models.md +1 -1
  171. package/docs/providers.md +2 -1
  172. package/docs/session-format.md +6 -0
  173. package/docs/sessions.md +6 -0
  174. package/docs/workflows.md +105 -3
  175. package/package.json +4 -3
@@ -1,5 +1,6 @@
1
1
  import type { StageContextWithMeta, StageNoticeInput, LiveStageRuntime } from "./executor-stage-types.js";
2
2
  import type { InternalStageContext } from "./stage-runner.js";
3
+ import type { TrackedStageCaller } from "./executor-stage-call.js";
3
4
 
4
5
  function noticeValue(value: unknown): string {
5
6
  if (typeof value === "string") return value;
@@ -31,7 +32,7 @@ function compactionMeta(result: unknown): string | undefined {
31
32
 
32
33
  export function createStageContext(input: {
33
34
  readonly runtime: LiveStageRuntime;
34
- readonly runTrackedStageCall: <T>(call: () => Promise<T>, eagerSession?: boolean) => Promise<T>;
35
+ readonly runTrackedStageCall: TrackedStageCaller;
35
36
  }): StageContextWithMeta {
36
37
  const { runtime } = input;
37
38
  const recordStageNotice = (notice: StageNoticeInput): void => {
@@ -43,6 +44,19 @@ export function createStageContext(input: {
43
44
  };
44
45
 
45
46
  const innerCtx: InternalStageContext = runtime.innerCtx;
47
+ const sendStreamingUserMessage: InternalStageContext["sendUserMessage"] = async (text, options) => {
48
+ runtime.mcpScope.apply();
49
+ try {
50
+ await innerCtx.sendUserMessage(text, options);
51
+ } finally {
52
+ try {
53
+ runtime.mcpScope.clear();
54
+ } finally {
55
+ runtime.captureStageSessionMeta();
56
+ runtime.applyModelFallbackMeta(innerCtx.__modelFallbackMeta());
57
+ }
58
+ }
59
+ };
46
60
  return {
47
61
  name: innerCtx.name,
48
62
  prompt: (text, promptOptions) => {
@@ -53,6 +67,11 @@ export function createStageContext(input: {
53
67
  runtime.throwIfStageMutationBlocked();
54
68
  return input.runTrackedStageCall(() => innerCtx.complete(text, completeOptions));
55
69
  },
70
+ sendUserMessage: (text, options) => {
71
+ runtime.throwIfStageMutationBlocked();
72
+ if (innerCtx.isStreaming) return sendStreamingUserMessage(text, options);
73
+ return input.runTrackedStageCall(() => innerCtx.sendUserMessage(text, options), { allowFinalized: true });
74
+ },
56
75
  steer: (text) => {
57
76
  runtime.throwIfStageMutationBlocked();
58
77
  return innerCtx.steer(text);
@@ -193,6 +193,7 @@ export function createWorkflowStageFactory(input: {
193
193
  if (meta.sessionId !== undefined) stageSnapshot.sessionId = meta.sessionId;
194
194
  if (meta.sessionFile !== undefined) stageSnapshot.sessionFile = meta.sessionFile;
195
195
  if (meta.sessionId !== undefined || meta.sessionFile !== undefined) input.activeStore.recordStageSession(input.runId, stageId, meta);
196
+ void input.opts.onStageSession?.(input.runId, stageSnapshot);
196
197
  };
197
198
  const releaseLiveHandle = async (): Promise<void> => {
198
199
  if (state.liveHandleReleased) return;
@@ -226,7 +227,7 @@ export function createWorkflowStageFactory(input: {
226
227
  });
227
228
  };
228
229
 
229
- const finalizeStageSnapshot = (): boolean => {
230
+ const finalizeStageSnapshot = async (): Promise<boolean> => {
230
231
  if (state.stageFinalized) return false;
231
232
  if (stageSnapshot.endedAt !== undefined && isTerminalStage(stageSnapshot)) {
232
233
  state.stageFinalized = true;
@@ -242,7 +243,7 @@ export function createWorkflowStageFactory(input: {
242
243
  applyModelFallbackMeta(innerCtx.__modelFallbackMeta());
243
244
  input.activeStore.recordStageEnd(input.runId, stageSnapshot);
244
245
  stageUiBroker.cancelStagePrompt(input.runId, stageId, new Error(`atomic-workflows: stage ${stageId} completed with pending custom UI`));
245
- input.opts.onStageEnd?.(input.runId, stageSnapshot);
246
+ await input.opts.onStageEnd?.(input.runId, stageSnapshot);
246
247
  if (input.opts.persistence) {
247
248
  appendStageStartOnce();
248
249
  appendStageEnd(input.opts.persistence, {
@@ -325,12 +326,12 @@ export function createWorkflowStageFactory(input: {
325
326
  // workflow-exit cleanup and removes the stage from the fail-fast active set.
326
327
  // Later paths must not overwrite the terminal skippedReason; they only abort
327
328
  // and release idempotent live handles.
328
- const skipForParallelFailFast = (): void => {
329
+ const skipForParallelFailFast = async (): Promise<void> => {
329
330
  if (isTerminalStage(stageSnapshot)) return;
330
331
  markSkippedForParallelFailFast();
331
- finalizeStageSnapshot();
332
- void innerCtx.abort().catch(() => {});
333
- void dropStageControlForCompletion().catch(() => {});
332
+ await finalizeStageSnapshot();
333
+ await innerCtx.abort().catch(() => {});
334
+ await dropStageControlForCompletion().catch(() => {});
334
335
  };
335
336
  stageFailFastScope?.activeStages.set(stageId, { skip: skipForParallelFailFast });
336
337
  runtime.unregisterWorkflowExitCleanup = input.exit.registerWorkflowExitCleanup(stageId, {
@@ -339,7 +340,7 @@ export function createWorkflowStageFactory(input: {
339
340
  if (!isTerminalStage(stageSnapshot)) {
340
341
  stageSnapshot.status = "skipped";
341
342
  stageSnapshot.skippedReason = input.exit.workflowExitSkippedReason(reason);
342
- finalizeStageSnapshot();
343
+ await finalizeStageSnapshot();
343
344
  }
344
345
  await innerCtx.abort().catch(() => {});
345
346
  await releaseLiveHandle().catch(() => {});
@@ -95,6 +95,7 @@ export function createReplayStageContext(input: {
95
95
  name,
96
96
  prompt: replayText,
97
97
  complete: replayText,
98
+ sendUserMessage: async () => rejectReplayMutation("send a user message"),
98
99
  steer: async () => rejectReplayMutation("steer"),
99
100
  followUp: async () => rejectReplayMutation("follow up"),
100
101
  subscribe: () => () => {},
@@ -48,7 +48,7 @@ export interface LiveStageRuntime {
48
48
  readonly captureStageSessionMeta: () => void;
49
49
  readonly applyModelFallbackMeta: (meta: ReturnType<InternalStageContext["__modelFallbackMeta"]>) => void;
50
50
  readonly appendStageStartOnce: () => void;
51
- readonly finalizeStageSnapshot: () => boolean;
51
+ readonly finalizeStageSnapshot: () => Promise<boolean>;
52
52
  readonly releaseLiveHandle: () => Promise<void>;
53
53
  readonly dropStageControlForCompletion: () => Promise<void>;
54
54
  readonly markSkippedForParallelFailFast: () => void;
@@ -70,6 +70,22 @@ export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "s
70
70
  runId?: string;
71
71
  /** Replay completed stages from a failed source run, then resume at this stage. */
72
72
  continuation?: RunContinuationOpts;
73
+ /**
74
+ * Durable workflow backend override (for testing). Defaults to the global
75
+ * backend resolved by `getDurableBackend()`.
76
+ *
77
+ * cross-ref: issue #1498 — DBOS-backed cross-session resumability.
78
+ */
79
+ durableBackend?: import("../../durable/backend.js").DurableWorkflowBackend;
80
+ /**
81
+ * Durable scope for a child workflow run. When set, the child's internal
82
+ * `ctx.tool`/`ctx.ui`/`ctx.stage` checkpoints are routed under the root
83
+ * workflow id with a stable boundary prefix so an interrupted child does
84
+ * not re-execute completed side effects on parent resume.
85
+ *
86
+ * cross-ref: issue #1498.
87
+ */
88
+ durableScope?: import("../../durable/scoped-backend.js").DurableScope;
73
89
  /** Internal parent linkage for nested ctx.workflow(...) runs. */
74
90
  parentRun?: {
75
91
  readonly runId: string;
@@ -78,7 +94,8 @@ export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "s
78
94
  };
79
95
  onRunStart?: (snapshot: RunSnapshot) => void;
80
96
  onStageStart?: (runId: string, snapshot: StageSnapshot) => void;
81
- onStageEnd?: (runId: string, snapshot: StageSnapshot) => void;
97
+ onStageEnd?: (runId: string, snapshot: StageSnapshot) => unknown;
98
+ onStageSession?: (runId: string, snapshot: StageSnapshot) => unknown;
82
99
  onRunEnd?: (runId: string, status: RunStatus, result?: WorkflowOutputValues, error?: string, exitReason?: string) => void;
83
100
  }
84
101
 
@@ -94,7 +111,7 @@ export interface RunResult<TOutputs extends WorkflowOutputValues = WorkflowOutpu
94
111
  }
95
112
 
96
113
  export interface ParallelFailFastStage {
97
- readonly skip: () => void;
114
+ readonly skip: () => Promise<void>;
98
115
  }
99
116
 
100
117
  export interface ParallelFailFastScope {
@@ -92,6 +92,10 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
92
92
  return lastAssistantText;
93
93
  },
94
94
 
95
+ async sendUserMessage(text, options) {
96
+ await controller.sendUserMessage(text, options);
97
+ },
98
+
95
99
  async steer(text) {
96
100
  await (await controller.ensureSession()).steer(text);
97
101
  },
@@ -1,10 +1,11 @@
1
1
  import { getModelDefaultContextWindow, getSupportedContextWindows, SessionManager, shouldApplyCodexFastModeForScope, type AgentSession, type CreateAgentSessionOptions, type PromptOptions, type StructuredOutputCapture } from "@bastani/atomic";
2
- import type { StageContext, StageExecutionMeta, StageOptions, WorkflowModelAttempt, WorkflowModelCatalogPort } from "../../shared/types.js";
2
+ import type { StageContext, StageExecutionMeta, StageOptions, StageSendUserMessageOptions, StageUserMessageContent, WorkflowModelAttempt, WorkflowModelCatalogPort } from "../../shared/types.js";
3
3
  import { buildModelCandidatesFromCatalog, errorMessage, isRetryableModelFailure, workflowModelId, type WorkflowResolvedModelCandidate } from "../shared/model-fallback.js";
4
4
  import { WorkflowPromptModelFailure, lastAssistantTextFromSession, latestTerminalAssistantFailureSince } from "./stage-runner-messages.js";
5
5
  import { missingAdapter, stripWorkflowOnlyOptions, unavailableSync } from "./stage-runner-options.js";
6
6
  import { asAgentSession, disposeStageSession, normalizeSessionCreateResult } from "./stage-runner-session.js";
7
7
  import { structuredOutputToolErrorFromEvent } from "./stage-runner-structured-output.js";
8
+ import { sendStageUserMessage } from "./stage-runner-send-user-message.js";
8
9
  import type { AgentSessionConsumer, StageModelFallbackMeta, StageRunnerOpts, StageSessionCreateOptions, StageSessionCreateResult, StageSessionEvent, StageSessionRuntime, WorkflowFastModeSettingsManager } from "./stage-runner-types.js";
9
10
 
10
11
  type PauseRequest = {
@@ -60,7 +61,6 @@ export class StageSessionController {
60
61
  get latestStructuredOutputToolError(): string | undefined { return this.latestStructuredOutputToolErrorValue; }
61
62
 
62
63
  resetStructuredOutputToolError(): void { this.latestStructuredOutputToolErrorValue = undefined; }
63
-
64
64
  requireSession(property: string): StageSessionRuntime {
65
65
  if (!this.session) unavailableSync(property);
66
66
  return this.session;
@@ -98,6 +98,10 @@ export class StageSessionController {
98
98
  return this.ensureSession(consumer);
99
99
  }
100
100
 
101
+ async sendUserMessage(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void> {
102
+ await sendStageUserMessage(await this.ensureSession("prompt"), content, options);
103
+ }
104
+
101
105
  async promptWithFallback(
102
106
  text: string,
103
107
  sdkOptions: PromptOptions | undefined,
@@ -242,14 +246,10 @@ export class StageSessionController {
242
246
  fallbackThinkingLevels: undefined,
243
247
  };
244
248
  if (resumeOptions?.restoreSavedModel) delete optionsForCandidate.model;
245
- // Pin a tiered model's natural default (short) context window when neither
246
- // the `(1m)` model-string token nor an explicit stage-level contextWindow
247
- // selects one for a fresh (non-resumed) stage session. This prevents a
248
- // persisted interactive context-window preference (e.g. a previously
249
- // selected long tier) from leaking into workflow stages, so a tiered model
250
- // uses its short tier unless the author explicitly opts into the long tier
251
- // via the `(1m)` token or the numeric contextWindow option. Single-window
252
- // models carry no selectable long tier, so they are left untouched.
249
+ // Pin a tiered model's short default context window for fresh, non-resumed
250
+ // stage sessions unless the author selected a long tier via `(1m)` or
251
+ // contextWindow. This prevents persisted interactive long-tier preferences
252
+ // from leaking into workflow stages; single-window models are left alone.
253
253
  if (
254
254
  resumeOptions?.restoreSavedModel !== true &&
255
255
  this.reattachSessionFile === undefined &&
@@ -18,6 +18,8 @@ export function stripWorkflowOnlyOptions(
18
18
  fallbackThinkingLevels: _fallbackThinkingLevels,
19
19
  context,
20
20
  forkFromSessionFile,
21
+ resumeFromSessionFile,
22
+ durableReplayKey: _durableReplayKey,
21
23
  sessionDir,
22
24
  gitWorktreeDir: _gitWorktreeDir,
23
25
  baseBranch: _baseBranch,
@@ -26,7 +28,9 @@ export function stripWorkflowOnlyOptions(
26
28
  if (sessionOptions.sessionManager === undefined) {
27
29
  const cwd = sessionOptions.cwd ?? process.cwd();
28
30
  const effectiveSessionDir = sessionDir ?? defaultSessionDir;
29
- if (context === "fork" && forkFromSessionFile !== undefined) {
31
+ if (resumeFromSessionFile !== undefined) {
32
+ sessionOptions.sessionManager = SessionManager.open(resumeFromSessionFile, effectiveSessionDir, cwd);
33
+ } else if (context === "fork" && forkFromSessionFile !== undefined) {
30
34
  sessionOptions.sessionManager = SessionManager.forkFrom(forkFromSessionFile, cwd, effectiveSessionDir);
31
35
  } else if (effectiveSessionDir !== undefined) {
32
36
  sessionOptions.sessionManager = SessionManager.create(cwd, effectiveSessionDir);
@@ -0,0 +1,25 @@
1
+ import type { StageSendUserMessageOptions, StageUserMessageContent } from "../../shared/types.js";
2
+ import type { StageSessionRuntime } from "./stage-runner-types.js";
3
+
4
+ function unsupportedContentError(): Error {
5
+ return new Error("atomic-workflows: this stage session adapter does not support non-string sendUserMessage content; provide a runtime sendUserMessage implementation for text/image blocks.");
6
+ }
7
+
8
+ export async function sendStageUserMessage(
9
+ activeSession: StageSessionRuntime,
10
+ content: StageUserMessageContent,
11
+ options?: StageSendUserMessageOptions,
12
+ ): Promise<void> {
13
+ const deliverAs = activeSession.isStreaming ? options?.deliverAs ?? "followUp" : options?.deliverAs;
14
+ if (activeSession.sendUserMessage !== undefined) {
15
+ await activeSession.sendUserMessage(content, deliverAs === undefined ? undefined : { deliverAs });
16
+ return;
17
+ }
18
+ if (typeof content !== "string") throw unsupportedContentError();
19
+ if (activeSession.isStreaming) {
20
+ if (deliverAs === "steer") await activeSession.steer(content);
21
+ else await activeSession.followUp(content);
22
+ return;
23
+ }
24
+ await activeSession.prompt(content);
25
+ }
@@ -6,6 +6,8 @@ import type {
6
6
  import type {
7
7
  CompleteStageOpts,
8
8
  StageContext,
9
+ StageSendUserMessageOptions,
10
+ StageUserMessageContent,
9
11
  StageExecutionMeta,
10
12
  StageOptions,
11
13
  WorkflowExecutionMode,
@@ -26,6 +28,7 @@ export type WorkflowFastModeSettingsManager = {
26
28
 
27
29
  export interface StageSessionRuntime {
28
30
  prompt(text: string, options?: PromptOptions): Promise<string | void>;
31
+ sendUserMessage?(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void>;
29
32
  steer(text: string): Promise<void>;
30
33
  followUp(text: string): Promise<void>;
31
34
  subscribe(listener: (event: StageSessionEvent) => void): () => void;
@@ -40,6 +40,16 @@ export interface WorkflowModelFallbackFields {
40
40
  readonly fallbackThinkingLevels?: readonly string[];
41
41
  }
42
42
  export type WorkflowModelValue = string | object;
43
+ export interface StageTextContent {
44
+ readonly type: "text";
45
+ readonly text: string;
46
+ }
47
+ export interface StageImageContent {
48
+ readonly type: "image";
49
+ readonly data: string;
50
+ readonly mimeType: string;
51
+ }
52
+ export type StageUserMessageContent = string | readonly (StageTextContent | StageImageContent)[];
43
53
  export type WorkflowStageResult<TSchemaDef extends TSchema | undefined = undefined> = [TSchemaDef] extends [TSchema] ? Static<TSchemaDef> : string;
44
54
  export interface WorkflowModelUsage extends WorkflowSerializableObject {
45
55
  readonly input?: number;
@@ -178,6 +188,7 @@ export interface WorkflowPersistencePort {
178
188
  }
179
189
  export interface StageSessionRuntime {
180
190
  prompt(text: string, options?: PromptOptions): Promise<string | void>;
191
+ sendUserMessage?(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void>;
181
192
  steer(text: string): Promise<void>;
182
193
  followUp(text: string): Promise<void>;
183
194
  subscribe(listener: (event: never) => void): () => void;
@@ -236,10 +247,15 @@ export interface StageAdapters {
236
247
  readonly prompt?: PromptAdapter;
237
248
  readonly complete?: CompleteAdapter;
238
249
  }
250
+ export type StageUserMessageDelivery = "steer" | "followUp";
251
+ export interface StageSendUserMessageOptions {
252
+ readonly deliverAs?: StageUserMessageDelivery;
253
+ }
239
254
  export interface StageContext<TSchemaDef extends TSchema | undefined = undefined> {
240
255
  readonly name: string;
241
256
  prompt(text: string, options?: StagePromptOptions): Promise<WorkflowStageResult<TSchemaDef>>;
242
257
  complete(text: string, options?: CompleteStageOpts): Promise<string>;
258
+ sendUserMessage(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void>;
243
259
  steer(text: string): Promise<void>;
244
260
  followUp(text: string): Promise<void>;
245
261
  subscribe(listener: (event: never) => void): () => void;
@@ -55,6 +55,18 @@ export interface WorkflowModelFallbackFields {
55
55
  }
56
56
 
57
57
  export type WorkflowModelValue = string | object;
58
+ // Standalone authoring contract mirror of shared/types.ts StageUserMessageContent,
59
+ // whose runtime source of truth is derived from AgentSession["sendUserMessage"].
60
+ export interface StageTextContent {
61
+ readonly type: "text";
62
+ readonly text: string;
63
+ }
64
+ export interface StageImageContent {
65
+ readonly type: "image";
66
+ readonly data: string;
67
+ readonly mimeType: string;
68
+ }
69
+ export type StageUserMessageContent = string | readonly (StageTextContent | StageImageContent)[];
58
70
  export type WorkflowStageResult<TSchemaDef extends TSchema | undefined = undefined> = [TSchemaDef] extends [TSchema]
59
71
  ? Static<TSchemaDef>
60
72
  : string;
@@ -223,6 +235,7 @@ export interface WorkflowPersistencePort {
223
235
 
224
236
  export interface StageSessionRuntime {
225
237
  prompt(text: string, options?: PromptOptions): Promise<string | void>;
238
+ sendUserMessage?(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void>;
226
239
  steer(text: string): Promise<void>;
227
240
  followUp(text: string): Promise<void>;
228
241
  subscribe(listener: (event: never) => void): () => void;
@@ -284,10 +297,17 @@ export interface StageAdapters {
284
297
  readonly complete?: CompleteAdapter;
285
298
  }
286
299
 
300
+ export type StageUserMessageDelivery = "steer" | "followUp";
301
+
302
+ export interface StageSendUserMessageOptions {
303
+ readonly deliverAs?: StageUserMessageDelivery;
304
+ }
305
+
287
306
  export interface StageContext<TSchemaDef extends TSchema | undefined = undefined> {
288
307
  readonly name: string;
289
308
  prompt(text: string, options?: StagePromptOptions): Promise<WorkflowStageResult<TSchemaDef>>;
290
309
  complete(text: string, options?: CompleteStageOpts): Promise<string>;
310
+ sendUserMessage(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void>;
291
311
  steer(text: string): Promise<void>;
292
312
  followUp(text: string): Promise<void>;
293
313
  subscribe(listener: (event: never) => void): () => void;
@@ -57,6 +57,28 @@ export interface WorkflowRunContext<TInputs extends WorkflowInputValues = Workfl
57
57
  parallel(steps: readonly WorkflowTaskStep[], options?: WorkflowParallelOptions): Promise<WorkflowTaskResult[]>;
58
58
  workflow<TChildInputs extends WorkflowInputValues, TChildOutputs extends WorkflowOutputValues, TChildRunInputs extends WorkflowInputValues = TChildInputs>(definition: WorkflowDefinition<TChildInputs, TChildOutputs, TChildRunInputs> & TDefinitionBrand, ...args: WorkflowRunChildArgs<TChildRunInputs>): Promise<WorkflowChildResult<TChildOutputs>>;
59
59
  readonly ui: WorkflowUIContext;
60
+ /**
61
+ * Durable cached tool execution. Runs arbitrary TypeScript code and caches
62
+ * the result durably so completed side effects are not repeated on resume.
63
+ * Only `ctx.*` blocks (tool, ui, stage, task, chain, parallel, workflow)
64
+ * produce durable checkpoints.
65
+ *
66
+ * cross-ref: issue #1498 — DBOS-backed cross-session resumability.
67
+ */
68
+ tool: WorkflowToolPrimitive;
69
+ }
70
+ /**
71
+ * `ctx.tool` primitive signature. Runs an async function and caches the result.
72
+ */
73
+ export interface WorkflowToolPrimitive {
74
+ <TValue extends WorkflowSerializableValue>(name: string, args: Readonly<Record<string, WorkflowSerializableValue>>, fn: () => Promise<TValue>, options?: WorkflowToolOptions): Promise<TValue>;
75
+ }
76
+ /** Options for `ctx.tool`. */
77
+ export interface WorkflowToolOptions {
78
+ readonly retriesAllowed?: boolean;
79
+ readonly maxAttempts?: number;
80
+ readonly intervalMs?: number;
81
+ readonly backoffRate?: number;
60
82
  }
61
83
  export type WorkflowRunFn<TInputs extends WorkflowInputValues = WorkflowInputValues, TOutputs extends WorkflowOutputValues = WorkflowOutputValues, TDefinitionBrand extends object = {}> = (ctx: WorkflowRunContext<TInputs, TDefinitionBrand, TOutputs>) => Promise<TOutputs> | TOutputs;
62
84
  export interface WorkflowRuntimeConfig {
@@ -140,7 +162,7 @@ export interface RunOpts {
140
162
  readonly parentRun?: WorkflowParentRunLink;
141
163
  readonly onRunStart?: (snapshot: RunSnapshot) => void;
142
164
  readonly onStageStart?: (runId: string, snapshot: StageSnapshot) => void;
143
- readonly onStageEnd?: (runId: string, snapshot: StageSnapshot) => void;
165
+ readonly onStageEnd?: (runId: string, snapshot: StageSnapshot) => unknown;
144
166
  readonly onRunEnd?: (runId: string, status: RunStatus, result?: WorkflowOutputValues, error?: string, exitReason?: string) => void;
145
167
  }
146
168
  export interface WorkflowProgressSummary extends WorkflowSerializableObject {
@@ -104,6 +104,35 @@ export interface WorkflowRunContext<
104
104
  ...args: WorkflowRunChildArgs<TChildRunInputs>
105
105
  ): Promise<WorkflowChildResult<TChildOutputs>>;
106
106
  readonly ui: WorkflowUIContext;
107
+ /**
108
+ * Durable cached tool execution. Runs arbitrary TypeScript code and caches
109
+ * the result durably so completed side effects are not repeated on resume.
110
+ * Only `ctx.*` blocks (tool, ui, stage, task, chain, parallel, workflow)
111
+ * produce durable checkpoints.
112
+ *
113
+ * cross-ref: issue #1498 — DBOS-backed cross-session resumability.
114
+ */
115
+ tool: WorkflowToolPrimitive;
116
+ }
117
+
118
+ /**
119
+ * `ctx.tool` primitive signature. Runs an async function and caches the result.
120
+ */
121
+ export interface WorkflowToolPrimitive {
122
+ <TValue extends WorkflowSerializableValue>(
123
+ name: string,
124
+ args: Readonly<Record<string, WorkflowSerializableValue>>,
125
+ fn: () => Promise<TValue>,
126
+ options?: WorkflowToolOptions,
127
+ ): Promise<TValue>;
128
+ }
129
+
130
+ /** Options for `ctx.tool`. */
131
+ export interface WorkflowToolOptions {
132
+ readonly retriesAllowed?: boolean;
133
+ readonly maxAttempts?: number;
134
+ readonly intervalMs?: number;
135
+ readonly backoffRate?: number;
107
136
  }
108
137
 
109
138
  export type WorkflowRunFn<
@@ -206,7 +235,7 @@ export interface RunOpts {
206
235
  readonly parentRun?: WorkflowParentRunLink;
207
236
  readonly onRunStart?: (snapshot: RunSnapshot) => void;
208
237
  readonly onStageStart?: (runId: string, snapshot: StageSnapshot) => void;
209
- readonly onStageEnd?: (runId: string, snapshot: StageSnapshot) => void;
238
+ readonly onStageEnd?: (runId: string, snapshot: StageSnapshot) => unknown;
210
239
  readonly onRunEnd?: (runId: string, status: RunStatus, result?: WorkflowOutputValues, error?: string, exitReason?: string) => void;
211
240
  }
212
241
 
@@ -30,6 +30,10 @@ export interface RunEndMetadata {
30
30
  readonly exitReason?: string;
31
31
  }
32
32
 
33
+ export interface RunPauseMetadata {
34
+ readonly resumable?: boolean;
35
+ readonly exitReason?: string;
36
+ }
33
37
  export interface RunBlockedMetadata extends RunEndMetadata {
34
38
  readonly failureRecoverability: "recoverable";
35
39
  readonly failedStageId: string;
@@ -204,8 +208,8 @@ export interface Store {
204
208
  recordStageBlocked(runId: string, stageId: string, blockedBy: string): boolean;
205
209
  recordStageUnblocked(runId: string, stageId: string): boolean;
206
210
  recordStageNotice(runId: string, stageId: string, notice: StageNotice): boolean;
207
- /** Mark a run as `paused`. */
208
- recordRunPaused(runId: string, pausedAt?: number): boolean;
211
+ /** Mark a run as `paused`. Optional metadata can annotate resumable quit/detach state. */
212
+ recordRunPaused(runId: string, pausedAt?: number, metadata?: RunPauseMetadata): boolean;
209
213
  /** Restore a run from `paused` back to `running`. */
210
214
  recordRunResumed(runId: string, resumedAt?: number): boolean;
211
215
  /** Drop every run and notice. */
@@ -1,5 +1,5 @@
1
1
  import type { WorkflowOutputValues } from "./types.js";
2
- import type { Store, RunBlockedMetadata, RunEndMetadata } from "./store-public-types.js";
2
+ import type { Store, RunBlockedMetadata, RunEndMetadata, RunPauseMetadata } from "./store-public-types.js";
3
3
  import type { RunSnapshot, RunStatus, StoreSnapshot, WorkflowNotice } from "./store-types.js";
4
4
  import { accumulatePausedDurationMs, elapsedRunMs } from "./timing.js";
5
5
  import { isTopLevelWorkflowRun } from "./run-visibility.js";
@@ -164,14 +164,19 @@ export function createRunStoreMethods(context: StoreContext): RunStoreMethods {
164
164
  return true;
165
165
  },
166
166
 
167
- recordRunPaused(runId: string, pausedAt?: number): boolean {
167
+ recordRunPaused(runId: string, pausedAt?: number, metadata?: RunPauseMetadata): boolean {
168
168
  const run = context.findRun(runId);
169
169
  if (!run) return false;
170
170
  if (TERMINAL_STATUSES.has(run.status)) return false;
171
- if (run.status === "paused") return false;
172
- run.status = "paused";
173
- run.pausedAt = pausedAt ?? Date.now();
174
- run.resumedAt = undefined;
171
+ const wasPaused = run.status === "paused";
172
+ if (!wasPaused) {
173
+ run.status = "paused";
174
+ run.pausedAt = pausedAt ?? Date.now();
175
+ run.resumedAt = undefined;
176
+ }
177
+ if (metadata?.resumable !== undefined) run.resumable = metadata.resumable;
178
+ if (metadata?.exitReason !== undefined) run.exitReason = metadata.exitReason;
179
+ if (wasPaused && metadata === undefined) return false;
175
180
  context.bumpAndNotify();
176
181
  return true;
177
182
  },
@@ -186,6 +191,7 @@ export function createRunStoreMethods(context: StoreContext): RunStoreMethods {
186
191
  run.pausedDurationMs = accumulatePausedDurationMs(run.pausedDurationMs, run.pausedAt, resumedTs);
187
192
  run.resumedAt = resumedTs;
188
193
  run.pausedAt = undefined;
194
+ delete run.exitReason;
189
195
  context.bumpAndNotify();
190
196
  return true;
191
197
  },
@@ -21,6 +21,15 @@ export type { TSchema };
21
21
 
22
22
  export type { AgentSessionEvent, ContextCompactionResult, ModelCycleResult, PromptOptions };
23
23
 
24
+ export type StageUserMessageContent = Parameters<AgentSession["sendUserMessage"]>[0];
25
+
26
+ export type StageUserMessageDelivery = "steer" | "followUp";
27
+
28
+ export interface StageSendUserMessageOptions {
29
+ /** Delivery mode to use when the stage session is already streaming. Defaults to followUp. */
30
+ readonly deliverAs?: StageUserMessageDelivery;
31
+ }
32
+
24
33
  export type WorkflowModelValue = NonNullable<CreateAgentSessionOptions["model"]> | string;
25
34
  export type WorkflowModelUsage = AuthoringContract.WorkflowModelUsage;
26
35
  export type WorkflowModelAttempt = AuthoringContract.WorkflowModelAttempt;
@@ -178,6 +187,10 @@ export interface StageOptions<TSchemaDef extends TSchema | undefined = TSchema |
178
187
  scopedModels?: CreateAgentSessionOptions["scopedModels"];
179
188
  sessionManager?: SessionManager;
180
189
  settingsManager?: SettingsManager;
190
+ /** Internal durable resume hook: reopen this exact Atomic/Pi session file instead of forking. */
191
+ resumeFromSessionFile?: string;
192
+ /** Internal durable replay key used to map a live LM session to durable resume state. */
193
+ durableReplayKey?: string;
181
194
  }
182
195
 
183
196
  // ---------------------------------------------------------------------------
@@ -299,6 +312,15 @@ export interface StageContext<TSchemaDef extends TSchema | undefined = undefined
299
312
  prompt(text: string, options?: StagePromptOptions): Promise<WorkflowStageResult<TSchemaDef>>;
300
313
  complete(text: string, options?: CompleteStageOpts): Promise<string>;
301
314
 
315
+ /**
316
+ * Send a user-authored follow-on message to this stage session.
317
+ *
318
+ * When the session is idle this starts a new user turn immediately. When the
319
+ * session is streaming, the message is queued as a follow-up by default, or
320
+ * as steering when `deliverAs: "steer"` is provided.
321
+ */
322
+ sendUserMessage(content: StageUserMessageContent, options?: StageSendUserMessageOptions): Promise<void>;
323
+
302
324
  /** Queue messages during streaming. */
303
325
  steer(text: string): Promise<void>;
304
326
  followUp(text: string): Promise<void>;
@@ -381,6 +403,39 @@ export interface WorkflowRunContext<
381
403
  ): Promise<WorkflowChildResult<TChildOutputs>>;
382
404
  /** HIL primitives for user interaction during a run. */
383
405
  readonly ui: WorkflowUIContext;
406
+ /**
407
+ * Durable cached tool execution. Runs arbitrary TypeScript code and caches
408
+ * the result so completed side effects are not repeated on resume.
409
+ * Only `ctx.*` blocks produce durable checkpoints.
410
+ *
411
+ * cross-ref: issue #1498 — DBOS-backed cross-session resumability.
412
+ */
413
+ tool: WorkflowToolPrimitive;
414
+ }
415
+
416
+ /**
417
+ * `ctx.tool` primitive signature. Runs an async function and caches the result
418
+ * durably via the durable workflow backend.
419
+ */
420
+ export interface WorkflowToolPrimitive {
421
+ <TValue extends WorkflowSerializableValue>(
422
+ name: string,
423
+ args: Readonly<Record<string, WorkflowSerializableValue>>,
424
+ fn: () => Promise<TValue>,
425
+ options?: WorkflowToolOptions,
426
+ ): Promise<TValue>;
427
+ }
428
+
429
+ /** Options for `ctx.tool`. */
430
+ export interface WorkflowToolOptions {
431
+ /** When true, the tool function is retried on failure. Default false. */
432
+ readonly retriesAllowed?: boolean;
433
+ /** Max retry attempts when retriesAllowed is true. Default 3. */
434
+ readonly maxAttempts?: number;
435
+ /** Initial retry interval in ms. Default 1000. */
436
+ readonly intervalMs?: number;
437
+ /** Backoff multiplier. Default 2. */
438
+ readonly backoffRate?: number;
384
439
  }
385
440
 
386
441
  // ---------------------------------------------------------------------------
@@ -3,7 +3,7 @@ export const HINT_KEYS: Array<{ key: string; label: string }> = [
3
3
  { key: "↵", label: "attach" },
4
4
  { key: "/", label: "stages" },
5
5
  { key: "ctrl+d", label: "detach" },
6
- { key: "q", label: "kill" },
6
+ { key: "q", label: "quit" },
7
7
  ];
8
8
 
9
9
  /**