@bastani/atomic 0.9.10 → 0.9.11-alpha.2

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 (204) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  3. package/dist/builtin/cursor/package.json +3 -3
  4. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  5. package/dist/builtin/intercom/package.json +3 -3
  6. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  7. package/dist/builtin/mcp/package.json +3 -3
  8. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  9. package/dist/builtin/subagents/package.json +4 -4
  10. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  11. package/dist/builtin/web-access/package.json +2 -2
  12. package/dist/builtin/workflows/CHANGELOG.md +23 -0
  13. package/dist/builtin/workflows/README.md +8 -3
  14. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +1 -1
  15. package/dist/builtin/workflows/package.json +2 -2
  16. package/dist/builtin/workflows/src/durable/backend.ts +2 -2
  17. package/dist/builtin/workflows/src/durable/child-primitive.ts +3 -0
  18. package/dist/builtin/workflows/src/durable/completed-catalog.ts +182 -33
  19. package/dist/builtin/workflows/src/durable/completed-inspection.ts +9 -4
  20. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +173 -0
  21. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +36 -12
  22. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +31 -8
  23. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +9 -3
  24. package/dist/builtin/workflows/src/durable/factory.ts +35 -6
  25. package/dist/builtin/workflows/src/durable/local-command.ts +11 -2
  26. package/dist/builtin/workflows/src/durable/resume-runtime.ts +6 -2
  27. package/dist/builtin/workflows/src/durable/stage-primitive.ts +48 -10
  28. package/dist/builtin/workflows/src/durable/types.ts +11 -0
  29. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +9 -9
  30. package/dist/builtin/workflows/src/engine/run-durable-topology.ts +89 -0
  31. package/dist/builtin/workflows/src/engine/run.ts +18 -16
  32. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +14 -2
  33. package/dist/builtin/workflows/src/extension/public-types.ts +3 -4
  34. package/dist/builtin/workflows/src/extension/render-call.ts +4 -0
  35. package/dist/builtin/workflows/src/extension/render-result.ts +31 -1
  36. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +3 -0
  37. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +5 -2
  38. package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -2
  39. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +1 -0
  40. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +88 -2
  41. package/dist/builtin/workflows/src/extension/workflow-tool.ts +13 -0
  42. package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +3 -0
  43. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +6 -3
  44. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +2 -2
  45. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +4 -1
  46. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +2 -3
  47. package/dist/cli/args.d.ts.map +1 -1
  48. package/dist/cli/args.js +2 -0
  49. package/dist/cli/args.js.map +1 -1
  50. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  51. package/dist/core/agent-session-compaction.js +7 -1
  52. package/dist/core/agent-session-compaction.js.map +1 -1
  53. package/dist/core/agent-session-events.d.ts.map +1 -1
  54. package/dist/core/agent-session-events.js +2 -1
  55. package/dist/core/agent-session-events.js.map +1 -1
  56. package/dist/core/agent-session-retry.d.ts.map +1 -1
  57. package/dist/core/agent-session-retry.js +7 -0
  58. package/dist/core/agent-session-retry.js.map +1 -1
  59. package/dist/core/agent-session-runtime.d.ts +13 -0
  60. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  61. package/dist/core/agent-session-runtime.js +12 -0
  62. package/dist/core/agent-session-runtime.js.map +1 -1
  63. package/dist/core/agent-session-services.d.ts.map +1 -1
  64. package/dist/core/agent-session-services.js +3 -0
  65. package/dist/core/agent-session-services.js.map +1 -1
  66. package/dist/core/agent-session-tree.d.ts.map +1 -1
  67. package/dist/core/agent-session-tree.js +4 -1
  68. package/dist/core/agent-session-tree.js.map +1 -1
  69. package/dist/core/agent-session-types.d.ts +15 -0
  70. package/dist/core/agent-session-types.d.ts.map +1 -1
  71. package/dist/core/agent-session-types.js.map +1 -1
  72. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  73. package/dist/core/atomic-guide-command.js +2 -0
  74. package/dist/core/atomic-guide-command.js.map +1 -1
  75. package/dist/core/auth-storage-backends.d.ts +4 -0
  76. package/dist/core/auth-storage-backends.d.ts.map +1 -1
  77. package/dist/core/auth-storage-backends.js +48 -3
  78. package/dist/core/auth-storage-backends.js.map +1 -1
  79. package/dist/core/auth-storage.d.ts.map +1 -1
  80. package/dist/core/auth-storage.js +27 -11
  81. package/dist/core/auth-storage.js.map +1 -1
  82. package/dist/core/codex-errors.d.ts +3 -0
  83. package/dist/core/codex-errors.d.ts.map +1 -0
  84. package/dist/core/codex-errors.js +12 -0
  85. package/dist/core/codex-errors.js.map +1 -0
  86. package/dist/core/compaction/branch-summarization.d.ts +5 -1
  87. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  88. package/dist/core/compaction/branch-summarization.js +7 -8
  89. package/dist/core/compaction/branch-summarization.js.map +1 -1
  90. package/dist/core/compaction/compaction-runner.d.ts +3 -1
  91. package/dist/core/compaction/compaction-runner.d.ts.map +1 -1
  92. package/dist/core/compaction/compaction-runner.js +6 -1
  93. package/dist/core/compaction/compaction-runner.js.map +1 -1
  94. package/dist/core/compaction/range-planner.d.ts +3 -0
  95. package/dist/core/compaction/range-planner.d.ts.map +1 -1
  96. package/dist/core/compaction/range-planner.js +2 -1
  97. package/dist/core/compaction/range-planner.js.map +1 -1
  98. package/dist/core/model-registry.js +2 -2
  99. package/dist/core/model-registry.js.map +1 -1
  100. package/dist/core/model-resolver-defaults.d.ts.map +1 -1
  101. package/dist/core/model-resolver-defaults.js +2 -0
  102. package/dist/core/model-resolver-defaults.js.map +1 -1
  103. package/dist/core/prompt-templates.d.ts +1 -0
  104. package/dist/core/prompt-templates.d.ts.map +1 -1
  105. package/dist/core/prompt-templates.js +6 -4
  106. package/dist/core/prompt-templates.js.map +1 -1
  107. package/dist/core/remote-catalog-provider.d.ts +1 -1
  108. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  109. package/dist/core/remote-catalog-provider.js +25 -6
  110. package/dist/core/remote-catalog-provider.js.map +1 -1
  111. package/dist/core/sdk-exports.d.ts +1 -1
  112. package/dist/core/sdk-exports.d.ts.map +1 -1
  113. package/dist/core/sdk-exports.js.map +1 -1
  114. package/dist/core/sdk.d.ts.map +1 -1
  115. package/dist/core/sdk.js +4 -1
  116. package/dist/core/sdk.js.map +1 -1
  117. package/dist/core/session-manager-core.d.ts +1 -0
  118. package/dist/core/session-manager-core.d.ts.map +1 -1
  119. package/dist/core/session-manager-core.js +7 -4
  120. package/dist/core/session-manager-core.js.map +1 -1
  121. package/dist/core/session-manager-validation.d.ts.map +1 -1
  122. package/dist/core/session-manager-validation.js +3 -16
  123. package/dist/core/session-manager-validation.js.map +1 -1
  124. package/dist/core/slash-commands.js +1 -1
  125. package/dist/core/slash-commands.js.map +1 -1
  126. package/dist/core/summarization-retry.d.ts +11 -0
  127. package/dist/core/summarization-retry.d.ts.map +1 -0
  128. package/dist/core/summarization-retry.js +21 -0
  129. package/dist/core/summarization-retry.js.map +1 -0
  130. package/dist/core/tools/read.js +1 -1
  131. package/dist/core/tools/read.js.map +1 -1
  132. package/dist/index.d.ts +1 -1
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js.map +1 -1
  135. package/dist/main.d.ts.map +1 -1
  136. package/dist/main.js +2 -0
  137. package/dist/main.js.map +1 -1
  138. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  139. package/dist/modes/interactive/components/chat-session-host-events.js +16 -0
  140. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  141. package/dist/modes/interactive/components/footer.js +2 -2
  142. package/dist/modes/interactive/components/footer.js.map +1 -1
  143. package/dist/modes/interactive/interactive-agent-events.js +6 -0
  144. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  145. package/dist/modes/interactive/interactive-auth-routing.d.ts +2 -1
  146. package/dist/modes/interactive/interactive-auth-routing.d.ts.map +1 -1
  147. package/dist/modes/interactive/interactive-auth-routing.js +10 -5
  148. package/dist/modes/interactive/interactive-auth-routing.js.map +1 -1
  149. package/dist/modes/interactive/interactive-autocomplete.js +12 -2
  150. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  151. package/dist/modes/interactive/interactive-model-catalog-startup.d.ts +13 -0
  152. package/dist/modes/interactive/interactive-model-catalog-startup.d.ts.map +1 -0
  153. package/dist/modes/interactive/interactive-model-catalog-startup.js +24 -0
  154. package/dist/modes/interactive/interactive-model-catalog-startup.js.map +1 -0
  155. package/dist/modes/interactive/interactive-model-routing.js +4 -3
  156. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  157. package/dist/modes/interactive/interactive-queueing.js +3 -2
  158. package/dist/modes/interactive/interactive-queueing.js.map +1 -1
  159. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  160. package/dist/modes/interactive/interactive-startup.js +4 -7
  161. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  162. package/dist/modes/interactive/interactive-summarization-retry-events.d.ts +8 -0
  163. package/dist/modes/interactive/interactive-summarization-retry-events.d.ts.map +1 -0
  164. package/dist/modes/interactive/interactive-summarization-retry-events.js +40 -0
  165. package/dist/modes/interactive/interactive-summarization-retry-events.js.map +1 -0
  166. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts +3 -1
  167. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
  168. package/dist/modes/interactive-engine/extension-ui-bridge.js +6 -0
  169. package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
  170. package/dist/modes/interactive-engine/isolated-runtime.d.ts +4 -5
  171. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  172. package/dist/modes/interactive-engine/isolated-runtime.js +14 -24
  173. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  174. package/dist/modes/interactive-engine/remote-model-catalog.d.ts +15 -0
  175. package/dist/modes/interactive-engine/remote-model-catalog.d.ts.map +1 -0
  176. package/dist/modes/interactive-engine/remote-model-catalog.js +67 -0
  177. package/dist/modes/interactive-engine/remote-model-catalog.js.map +1 -0
  178. package/dist/modes/rpc/rpc-client-api.d.ts +9 -1
  179. package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
  180. package/dist/modes/rpc/rpc-client-api.js +12 -0
  181. package/dist/modes/rpc/rpc-client-api.js.map +1 -1
  182. package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
  183. package/dist/modes/rpc/rpc-command-handler.js +35 -0
  184. package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
  185. package/dist/modes/rpc/rpc-types.d.ts +78 -7
  186. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  187. package/dist/modes/rpc/rpc-types.js.map +1 -1
  188. package/docs/docs.json +9 -1
  189. package/docs/extensions.md +3 -3
  190. package/docs/json.md +7 -1
  191. package/docs/models/artificial-analysis-index.md +82 -0
  192. package/docs/models/model-selection.md +63 -0
  193. package/docs/models/pareto-efficiency.md +64 -0
  194. package/docs/prompt-templates.md +1 -0
  195. package/docs/providers.md +9 -1
  196. package/docs/quickstart.md +1 -1
  197. package/docs/rpc.md +76 -0
  198. package/docs/sdk.md +3 -0
  199. package/docs/tui.md +2 -2
  200. package/docs/workflows.md +26 -13
  201. package/examples/extensions/README.md +1 -1
  202. package/examples/extensions/sandbox/package-lock.json +3 -3
  203. package/npm-shrinkwrap.json +37 -37
  204. package/package.json +7 -7
@@ -1,4 +1,4 @@
1
- /** DBOS-only durable backend factory and internal test injection seam. */
1
+ /** DBOS-first durable backend factory with a non-durable last-resort fallback. */
2
2
 
3
3
  import { InMemoryDurableBackend, type DurableWorkflowBackend } from "./backend.js";
4
4
  import {
@@ -9,8 +9,9 @@ import {
9
9
 
10
10
  let injectedBackend: DurableWorkflowBackend | undefined;
11
11
  let initializedBackend: DurableWorkflowBackend | undefined;
12
+ let initializing: Promise<DurableWorkflowBackend> | undefined;
12
13
 
13
- /** Return the injected test backend or the process-wide ready DBOS backend. */
14
+ /** Return the injected test backend or the process-wide initialized backend. */
14
15
  export function getDurableBackend(): DurableWorkflowBackend {
15
16
  const backend = injectedBackend ?? initializedBackend ?? getReadyDbosBackendSync();
16
17
  if (backend === undefined) throw new DbosNotReadyError();
@@ -20,7 +21,10 @@ export function getDurableBackend(): DurableWorkflowBackend {
20
21
  /** Internal injection seam. Production initialization uses DBOS. */
21
22
  export function setDurableBackend(backend: DurableWorkflowBackend | undefined): void {
22
23
  injectedBackend = backend;
23
- if (backend === undefined) initializedBackend = undefined;
24
+ if (backend === undefined) {
25
+ initializedBackend = undefined;
26
+ initializing = undefined;
27
+ }
24
28
  }
25
29
 
26
30
  /** Create an isolated current-interface backend for tests only. */
@@ -28,9 +32,34 @@ export function createInMemoryTestBackend(): InMemoryDurableBackend {
28
32
  return new InMemoryDurableBackend();
29
33
  }
30
34
 
31
- /** Configure, register, launch, and install the mandatory DBOS backend. */
35
+ /**
36
+ * Configure, register, launch, and install the DBOS backend.
37
+ *
38
+ * When no durable backend can be provisioned (no `DBOS_SYSTEM_DATABASE_URL`,
39
+ * embedded Postgres unavailable — e.g. running as root without an
40
+ * unprivileged account — and no Docker), workflows degrade to a process-local
41
+ * in-memory backend with a loud warning instead of refusing to run at all.
42
+ * Non-durable runs execute normally but do not survive the process:
43
+ * `/workflow resume` after exit has nothing to restore.
44
+ */
32
45
  export async function initializeDurableBackend(): Promise<DurableWorkflowBackend> {
33
46
  if (injectedBackend !== undefined) return injectedBackend;
34
- initializedBackend ??= await getReadyDbosBackend();
35
- return initializedBackend;
47
+ if (initializedBackend !== undefined) return initializedBackend;
48
+ initializing ??= getReadyDbosBackend()
49
+ .catch((error: unknown) => degradeToNonDurableBackend(error))
50
+ .then((backend) => {
51
+ initializedBackend = backend;
52
+ return backend;
53
+ });
54
+ return await initializing;
55
+ }
56
+
57
+ function degradeToNonDurableBackend(error: unknown): DurableWorkflowBackend {
58
+ const detail = error instanceof Error ? error.message : String(error);
59
+ console.error(
60
+ "atomic-workflows: durable backend unavailable — continuing NON-DURABLY with an in-memory backend. "
61
+ + "Workflow runs will execute, but their state will not survive this process and `/workflow resume` "
62
+ + `after exit will not work. Restore durability by fixing Postgres provisioning: ${detail}`,
63
+ );
64
+ return new InMemoryDurableBackend();
36
65
  }
@@ -11,16 +11,25 @@ export interface LocalCommandResult {
11
11
 
12
12
  const OUTPUT_LIMIT_BYTES = 16_384;
13
13
 
14
+ export interface LocalCommandOptions {
15
+ readonly env?: Readonly<Record<string, string>>;
16
+ /** POSIX drop-privilege identity for the spawned process (root only). */
17
+ readonly uid?: number;
18
+ readonly gid?: number;
19
+ }
20
+
14
21
  export function runLocalCommand(
15
22
  command: string,
16
23
  args: readonly string[],
17
- env?: Readonly<Record<string, string>>,
24
+ options?: LocalCommandOptions,
18
25
  ): Promise<LocalCommandResult> {
19
26
  return new Promise((resolve, reject) => {
20
27
  const child = spawn(command, [...args], {
21
28
  stdio: ["ignore", "pipe", "pipe"],
22
29
  windowsHide: true,
23
- ...(env !== undefined ? { env: { ...process.env, ...env } } : {}),
30
+ ...(options?.env !== undefined ? { env: { ...process.env, ...options.env } } : {}),
31
+ ...(options?.uid !== undefined ? { uid: options.uid } : {}),
32
+ ...(options?.gid !== undefined ? { gid: options.gid } : {}),
24
33
  });
25
34
  let stdout = "";
26
35
  let stderr = "";
@@ -18,7 +18,7 @@
18
18
  * top-level workflow id."
19
19
  */
20
20
 
21
- import type { WorkflowInputValues } from "../shared/types.js";
21
+ import type { WorkflowDefinition, WorkflowInputValues } from "../shared/types.js";
22
22
  import type { WorkflowRegistry } from "../workflows/registry.js";
23
23
  import type { RunOpts } from "../runs/foreground/executor-types.js";
24
24
  import { launchDetachedUntilStartup, workflowStartupFailureMessage } from "../runs/background/startup-admission.js";
@@ -44,6 +44,8 @@ export interface ResumeDurableDeps {
44
44
  readonly baseRunOpts: RunOpts;
45
45
  /** Durable backend override (defaults to the global singleton). */
46
46
  readonly durableBackend?: DurableWorkflowBackend;
47
+ /** Resolve a definition from its original invocation directory after restart. */
48
+ readonly resolveDefinition?: (name: string, cwd: string | undefined) => Promise<WorkflowDefinition | undefined>;
47
49
  /** Job tracker used by the detached resume launch. */
48
50
  readonly jobs?: JobTracker;
49
51
  }
@@ -146,7 +148,9 @@ export async function resumeDurableWorkflow(
146
148
  return { ok: false, reason: "not_resumable", message: `Workflow ${resolved.workflowId.slice(0, 8)} is ${handle.status}, not resumable.` };
147
149
  }
148
150
 
149
- const def = deps.registry.get(handle.name);
151
+ const def = handle.invocationCwd === undefined
152
+ ? deps.registry.get(handle.name)
153
+ : await deps.resolveDefinition?.(handle.name, handle.invocationCwd) ?? deps.registry.get(handle.name);
150
154
  if (def === undefined) {
151
155
  return { ok: false, reason: "workflow_not_found", message: `Workflow definition not found: ${handle.name}` };
152
156
  }
@@ -9,7 +9,7 @@ import { durableHash } from "./backend.js";
9
9
  import { recordCheckpointDurably } from "./tool-primitive.js";
10
10
  import { elapsedStageMs } from "../shared/timing.js";
11
11
  import { RESUME_CONTINUATION_PROMPT } from "../shared/resume-continuation.js";
12
- import { DURABLE_STAGE_TOPOLOGY_VERSION, type DurableStageCheckpoint } from "./types.js";
12
+ import { DURABLE_STAGE_TOPOLOGY_VERSION, type DurableStageCheckpoint, type DurableStageRunTopology } from "./types.js";
13
13
  export type DurableCompletedStageCheckpoint = DurableStageCheckpoint & { readonly output: WorkflowSerializableValue };
14
14
 
15
15
  export interface DurableStageDeps {
@@ -18,13 +18,14 @@ export interface DurableStageDeps {
18
18
  readonly nextCheckpointId: () => string;
19
19
  readonly nextReplayKey: (stageName: string) => string;
20
20
  readonly replayKeyForCompletedStage?: (stage: StageSnapshot) => string | undefined;
21
+ readonly runTopology?: DurableStageRunTopology;
21
22
  readonly now?: () => number;
22
23
  }
23
24
 
24
25
  export async function recordStageCheckpoint(deps: DurableStageDeps, stage: StageSnapshot): Promise<boolean> {
25
26
  if (stage.status !== "completed") return false;
26
27
  const replayKey = deps.replayKeyForCompletedStage?.(stage) ?? stage.replayKey ?? deps.nextReplayKey(stage.name);
27
- const metadata = checkpointMetadata(stage);
28
+ const metadata = durableStageCheckpointMetadata(stage, deps.runTopology);
28
29
  const hasExistingOutput = deps.backend.getStageOutput(deps.workflowId, replayKey) !== undefined;
29
30
  const checkpoint: DurableStageCheckpoint = hasExistingOutput
30
31
  ? {
@@ -62,13 +63,17 @@ function stageSessionDurationBucket(durationMs: number | undefined): number {
62
63
  return Math.floor((durationMs ?? 0) / STAGE_SESSION_DURATION_BUCKET_MS);
63
64
  }
64
65
 
65
- export async function recordStageSessionCheckpoint(deps: DurableStageDeps, stage: StageSnapshot): Promise<boolean> {
66
+ export async function recordStageSessionCheckpoint(
67
+ deps: DurableStageDeps,
68
+ stage: StageSnapshot,
69
+ options?: { readonly force?: boolean },
70
+ ): Promise<boolean> {
66
71
  const replayKey = deps.replayKeyForCompletedStage?.(stage) ?? stage.replayKey ?? deps.nextReplayKey(stage.name);
67
72
  if (stage.sessionFile === undefined) return false;
68
73
  const checkpointNow = deps.now?.() ?? Date.now();
69
74
  const durationMs = elapsedStageMs(stage, checkpointNow) ?? 0;
70
75
  const current = deps.backend.getStageSession(deps.workflowId, replayKey);
71
- if (current !== undefined
76
+ if (options?.force !== true && current !== undefined
72
77
  && current.sessionId === stage.sessionId
73
78
  && current.sessionFile === stage.sessionFile
74
79
  && current.startedAt === stage.startedAt
@@ -79,6 +84,7 @@ export async function recordStageSessionCheckpoint(deps: DurableStageDeps, stage
79
84
  checkpointId: stageSessionCheckpointId(replayKey, stage, durationMs),
80
85
  name: stage.name,
81
86
  replayKey,
87
+ ...durableStageCheckpointMetadata(stage, deps.runTopology),
82
88
  ...(stage.sessionId !== undefined ? { sessionId: stage.sessionId } : {}),
83
89
  sessionFile: stage.sessionFile,
84
90
  ...(stage.startedAt !== undefined ? { startedAt: stage.startedAt } : {}),
@@ -256,9 +262,17 @@ function stageOutput(stage: StageSnapshot): WorkflowSerializableValue {
256
262
  return { status: stage.status, stageId: stage.id };
257
263
  }
258
264
 
259
- function checkpointMetadata(stage: StageSnapshot): Partial<DurableStageCheckpoint> {
265
+ export function durableStageCheckpointMetadata(
266
+ stage: StageSnapshot,
267
+ run?: DurableStageRunTopology,
268
+ ): Partial<DurableStageCheckpoint> {
260
269
  return {
261
- topology: { version: DURABLE_STAGE_TOPOLOGY_VERSION, stageId: stage.id, parentIds: [...stage.parentIds] },
270
+ topology: {
271
+ version: DURABLE_STAGE_TOPOLOGY_VERSION,
272
+ stageId: stage.id,
273
+ parentIds: [...stage.parentIds],
274
+ ...(run !== undefined ? { run: { ...run } } : {}),
275
+ },
262
276
  ...(stage.startedAt !== undefined ? { startedAt: stage.startedAt } : {}),
263
277
  ...(stage.endedAt !== undefined ? { endedAt: stage.endedAt } : {}),
264
278
  ...(stage.durationMs !== undefined ? { durationMs: stage.durationMs } : {}),
@@ -311,6 +325,7 @@ function mergeCheckpointHydrationMetadata(
311
325
  if (checkpoints.length === 0) return replayValueCheckpoint;
312
326
  return {
313
327
  ...replayValueCheckpoint,
328
+ ...preferredHydrationTopology(replayValueCheckpoint, checkpoints),
314
329
  ...(replayValueCheckpoint.startedAt === undefined ? metadataValue(checkpoints, "startedAt") : {}),
315
330
  ...(replayValueCheckpoint.endedAt === undefined ? metadataValue(checkpoints, "endedAt") : {}),
316
331
  ...(replayValueCheckpoint.durationMs === undefined ? metadataValue(checkpoints, "durationMs") : {}),
@@ -323,6 +338,19 @@ function mergeCheckpointHydrationMetadata(
323
338
  ...(replayValueCheckpoint.modelAttempts === undefined ? metadataValue(checkpoints, "modelAttempts") : {}),
324
339
  };
325
340
  }
341
+ function preferredHydrationTopology(
342
+ replayValueCheckpoint: DurableCompletedStageCheckpoint,
343
+ checkpoints: readonly DurableStageCheckpoint[],
344
+ ): Pick<DurableStageCheckpoint, "topology"> | Record<string, never> {
345
+ for (let index = checkpoints.length - 1; index >= 0; index -= 1) {
346
+ const topology = checkpoints[index]?.topology;
347
+ if (topology?.run !== undefined) return { topology };
348
+ }
349
+ return replayValueCheckpoint.topology === undefined
350
+ ? metadataValue(checkpoints, "topology")
351
+ : {};
352
+ }
353
+
326
354
 
327
355
  function metadataValue<K extends keyof DurableStageCheckpoint>(
328
356
  checkpoints: readonly DurableStageCheckpoint[],
@@ -390,6 +418,7 @@ export function recordCachedStageIntoStore(
390
418
  id: stageId, name, status: "completed", parentIds: parentIds !== undefined ? Object.freeze([...parentIds]) : [],
391
419
  startedAt: checkpoint?.startedAt ?? endedAt, endedAt, durationMs: checkpoint?.durationMs ?? 0, result,
392
420
  replayKey, replayed: true, skippedReason: "durable checkpoint replay", toolEvents: [], attachable: false,
421
+ ...(checkpoint?.topology !== undefined ? { replayedFromStageId: checkpoint.topology.stageId } : {}),
393
422
  ...(workflowChild !== undefined ? { workflowChild } : {}),
394
423
  ...(checkpoint?.sessionId !== undefined ? { sessionId: checkpoint.sessionId } : {}),
395
424
  ...(checkpoint?.sessionFile !== undefined ? { sessionFile: checkpoint.sessionFile } : {}),
@@ -420,10 +449,19 @@ export function recordCachedStageWithTracker(
420
449
  ): void {
421
450
  const stageId = cachedStageId(runId, replayKey);
422
451
  let parentIds = tracker.onSpawn(stageId, name);
423
- const scopeParentIds = stageFailFastScope?.parentIds ?? [];
424
- if (stageFailFastScope !== undefined) {
425
- tracker.replaceParents(stageId, scopeParentIds);
426
- parentIds = [...scopeParentIds];
452
+ const sourceParents = checkpoint.topology?.run?.runId === runId
453
+ ? checkpoint.topology.parentIds
454
+ : undefined;
455
+ const run = store.runs().find((candidate) => candidate.id === runId);
456
+ const restored = sourceParents?.map((sourceId) =>
457
+ run?.stages.find((stage) => stage.replayedFromStageId === sourceId)?.id
458
+ );
459
+ if (restored !== undefined && restored.every((id): id is string => id !== undefined)) {
460
+ parentIds = restored;
461
+ tracker.replaceParents(stageId, parentIds);
462
+ } else if (stageFailFastScope !== undefined) {
463
+ parentIds = [...(stageFailFastScope.parentIds ?? [])];
464
+ tracker.replaceParents(stageId, parentIds);
427
465
  }
428
466
  recordCachedStageIntoStore(store, runId, name, replayKey, checkpoint.output, completedStageReplayKeys, parentIds, checkpoint);
429
467
  tracker.onSettle(stageId);
@@ -110,11 +110,22 @@ export interface DurableUiCheckpoint {
110
110
 
111
111
  export const DURABLE_STAGE_TOPOLOGY_VERSION = 1 as const;
112
112
 
113
+ /** Durable ownership metadata needed to rebuild nested workflow runs. */
114
+ export interface DurableStageRunTopology {
115
+ readonly runId: string;
116
+ readonly runName: string;
117
+ readonly parentRunId?: string;
118
+ readonly parentStageId?: string;
119
+ readonly rootRunId?: string;
120
+ }
121
+
113
122
  /** Versioned source-stage lineage used to reconstruct completed DAGs after restart. */
114
123
  export interface DurableStageTopology {
115
124
  readonly version: typeof DURABLE_STAGE_TOPOLOGY_VERSION;
116
125
  readonly stageId: string;
117
126
  readonly parentIds: readonly string[];
127
+ /** Owning run and boundary linkage for nested workflow graph reconstruction. */
128
+ readonly run?: DurableStageRunTopology;
118
129
  }
119
130
 
120
131
  /** A `ctx.stage(...)` / `ctx.task(...)` durable checkpoint or resumable session marker. */
@@ -1,11 +1,12 @@
1
1
  import type { RunSnapshot, StageSnapshot } from "../shared/store-types.js";
2
2
  import { recordStageSessionCheckpoint, type DurableStageDeps } from "../durable/stage-primitive.js";
3
+ import type { StageSessionCheckpointOptions } from "../runs/foreground/executor-types.js";
3
4
  import { recordRunTimingCheckpoint } from "../durable/run-timing.js";
4
5
 
5
6
  export interface DurableStageSessionRecorderInput {
6
7
  readonly runId: string;
7
8
  readonly deps: DurableStageDeps;
8
- readonly onStageSession?: (runId: string, snapshot: StageSnapshot) => unknown;
9
+ readonly onStageSession?: (runId: string, snapshot: StageSnapshot, options?: StageSessionCheckpointOptions) => unknown;
9
10
  /**
10
11
  * Live root-run snapshot. When present, stage-session checkpoints also
11
12
  * refresh the debounced run-level elapsed record so a durable resume can
@@ -17,15 +18,14 @@ export interface DurableStageSessionRecorderInput {
17
18
 
18
19
  export function createDurableStageSessionRecorder(
19
20
  input: DurableStageSessionRecorderInput,
20
- ): (stageRunId: string, snapshot: StageSnapshot) => void {
21
- return (stageRunId, snapshot) => {
21
+ ): (stageRunId: string, snapshot: StageSnapshot, options?: StageSessionCheckpointOptions) => Promise<void> {
22
+ return async (stageRunId, snapshot, options) => {
22
23
  if (stageRunId === input.runId) {
23
- void recordStageSessionCheckpoint(input.deps, snapshot).then(() => {
24
- if (input.runSnapshot !== undefined) {
25
- recordRunTimingCheckpoint(input.deps.backend, input.runSnapshot, { debounce: true });
26
- }
27
- }).catch(() => {});
24
+ await recordStageSessionCheckpoint(input.deps, snapshot, { force: options?.forceDurable === true });
25
+ if (input.runSnapshot !== undefined) {
26
+ recordRunTimingCheckpoint(input.deps.backend, input.runSnapshot, { debounce: options?.forceDurable !== true });
27
+ }
28
28
  }
29
- void input.onStageSession?.(stageRunId, snapshot);
29
+ await input.onStageSession?.(stageRunId, snapshot, options);
30
30
  };
31
31
  }
@@ -0,0 +1,89 @@
1
+ import { durableHash, type DurableWorkflowBackend } from "../durable/backend.js";
2
+ import { durableNestedRunSnapshots } from "../durable/completed-catalog.js";
3
+ import {
4
+ durableStageCheckpointMetadata,
5
+ recordCachedStageWithTracker,
6
+ type DurableCompletedStageCheckpoint,
7
+ type DurableStageDeps,
8
+ } from "../durable/stage-primitive.js";
9
+ import type { DurableStageRunTopology } from "../durable/types.js";
10
+ import type { GraphFrontierTracker } from "./graph-inference.js";
11
+ import type { RunSnapshot } from "../shared/store-types.js";
12
+ import type { Store } from "../shared/store.js";
13
+ import type { ParallelFailFastScope } from "../runs/foreground/executor-types.js";
14
+
15
+ export function durableRunTopology(run: RunSnapshot): DurableStageRunTopology {
16
+ return {
17
+ runId: run.id,
18
+ runName: run.name,
19
+ ...(run.parentRunId !== undefined ? { parentRunId: run.parentRunId } : {}),
20
+ ...(run.parentStageId !== undefined ? { parentStageId: run.parentStageId } : {}),
21
+ ...(run.rootRunId !== undefined ? { rootRunId: run.rootRunId } : {}),
22
+ };
23
+ }
24
+
25
+ export function createDurableStageDeps(input: {
26
+ readonly backend: DurableWorkflowBackend;
27
+ readonly run: RunSnapshot;
28
+ readonly nextCheckpointId: () => string;
29
+ readonly nextReplayKey: (stageName: string) => string;
30
+ readonly completedReplayKeys: Map<string, string>;
31
+ }): DurableStageDeps {
32
+ return {
33
+ workflowId: input.run.id,
34
+ backend: input.backend,
35
+ nextCheckpointId: input.nextCheckpointId,
36
+ nextReplayKey: input.nextReplayKey,
37
+ replayKeyForCompletedStage: (stage) => input.completedReplayKeys.get(stage.id),
38
+ runTopology: durableRunTopology(input.run),
39
+ };
40
+ }
41
+
42
+ export function createDurableCachedStageRecorder(input: {
43
+ readonly store: Store;
44
+ readonly tracker: GraphFrontierTracker;
45
+ readonly run: RunSnapshot;
46
+ readonly backend: DurableWorkflowBackend;
47
+ readonly rootBackend: DurableWorkflowBackend;
48
+ readonly completedStageReplayKeys: Map<string, string>;
49
+ }): {
50
+ readonly record: (name: string, replayKey: string, checkpoint: DurableCompletedStageCheckpoint, scope?: ParallelFailFastScope) => void;
51
+ readonly metadata: (replayKey: string) => Partial<DurableCompletedStageCheckpoint>;
52
+ } {
53
+ return {
54
+ record(name, replayKey, checkpoint, scope): void {
55
+ recordCachedStageWithTracker(
56
+ input.store, input.tracker, input.run.id, name, replayKey, checkpoint,
57
+ input.completedStageReplayKeys, scope,
58
+ );
59
+ const stage = input.store.runs().find((run) => run.id === input.run.id)?.stages
60
+ .find((candidate) => candidate.replayKey === replayKey);
61
+ if (stage !== undefined) {
62
+ input.backend.recordCheckpoint({
63
+ kind: "stage", workflowId: input.run.id,
64
+ checkpointId: `stage-replay-meta:${durableHash({ replayKey, stageId: stage.id, parentIds: stage.parentIds })}`,
65
+ name, replayKey, completedAt: Date.now(),
66
+ ...durableStageCheckpointMetadata(stage, durableRunTopology(input.run)),
67
+ });
68
+ }
69
+ if (workflowChildRunId(checkpoint) === undefined) return;
70
+ const durableRootId = input.run.rootRunId ?? input.run.id;
71
+ for (const childRun of durableNestedRunSnapshots(input.rootBackend, durableRootId)) {
72
+ if (!input.store.runs().some((candidate) => candidate.id === childRun.id)) input.store.recordRunStart(childRun);
73
+ }
74
+ },
75
+ metadata(replayKey) {
76
+ const stage = input.run.stages.find((candidate) => candidate.replayKey === replayKey);
77
+ return stage === undefined ? {} : durableStageCheckpointMetadata(stage, durableRunTopology(input.run));
78
+ },
79
+ };
80
+ }
81
+
82
+ function workflowChildRunId(checkpoint: DurableCompletedStageCheckpoint): string | undefined {
83
+ const output = checkpoint.output;
84
+ if (typeof output !== "object" || output === null || Array.isArray(output)) return undefined;
85
+ const record = output as Record<string, import("../shared/types.js").WorkflowSerializableValue>;
86
+ const runId = record["runId"];
87
+ const workflow = record["workflow"];
88
+ return typeof runId === "string" && typeof workflow === "string" ? runId : undefined;
89
+ }
@@ -39,14 +39,14 @@ import { isWorkflowDefinition, workflowDefinitionRequirementMessage } from "../r
39
39
  import { getDurableBackend } from "../durable/factory.js";
40
40
  import { classifyReturnedRunStatus } from "./run-returned-status.js";
41
41
  import { createToolPrimitive, createCheckpointIdGenerator } from "../durable/tool-primitive.js";
42
- import { createDurableStagePrimitive, createDurableTaskPrimitive, recordStageCheckpoint, createStageReplayKeyGenerator, recordCachedStageWithTracker } from "../durable/stage-primitive.js";
43
- import { inheritedRunElapsedMs } from "../durable/run-timing.js";
44
- import type { DurableCompletedStageCheckpoint } from "../durable/stage-primitive.js";
42
+ import { createDurableStagePrimitive, createDurableTaskPrimitive, recordStageCheckpoint, createStageReplayKeyGenerator } from "../durable/stage-primitive.js";
43
+ import { inheritedRunElapsedMs, recordRunTimingCheckpoint } from "../durable/run-timing.js";
45
44
  import { createDurableChildWorkflowPrimitive } from "../durable/child-primitive.js";
46
45
  import { ScopedDurableBackend, type DurableScope } from "../durable/scoped-backend.js";
47
46
  import { finalizeDurableTerminalStatus } from "./run-durable-finalize.js";
48
47
  import { createDurableStageSessionRecorder } from "./run-durable-stage-session.js";
49
48
  import type { DurableWorkflowBackend } from "../durable/backend.js";
49
+ import { createDurableCachedStageRecorder, createDurableStageDeps } from "./run-durable-topology.js";
50
50
 
51
51
  type WorkflowRunInputArgument = Parameters<typeof resolveAndValidateInputs>[1];
52
52
 
@@ -201,13 +201,11 @@ export async function run<
201
201
  const checkpointIdGenerator = createCheckpointIdGenerator();
202
202
  const stageReplayKeyGenerator = createStageReplayKeyGenerator(runId);
203
203
  const completedStageReplayKeys = new Map<string, string>();
204
- const durableStageDeps = {
205
- workflowId: runId,
206
- backend: durableBackend,
207
- nextCheckpointId: checkpointIdGenerator,
208
- nextReplayKey: stageReplayKeyGenerator,
209
- replayKeyForCompletedStage: (stage: StageSnapshot) => completedStageReplayKeys.get(stage.id),
210
- };
204
+ const durableStageDeps = createDurableStageDeps({
205
+ backend: durableBackend, run: runSnapshot,
206
+ nextCheckpointId: checkpointIdGenerator, nextReplayKey: stageReplayKeyGenerator,
207
+ completedReplayKeys: completedStageReplayKeys,
208
+ });
211
209
  const userOnStageEnd = opts.onStageEnd;
212
210
  const durableOnStageEnd = async (stageRunId: string, snapshot: StageSnapshot): Promise<void> => {
213
211
  if (stageRunId === runId && snapshot.status === "completed") {
@@ -340,18 +338,21 @@ export async function run<
340
338
 
341
339
  // Durable ctx.ui wrapper — caches completed user responses so a resumed workflow does not re-ask answered prompts.
342
340
  const durableUiDeps = { workflowId: runId, backend: durableBackend, nextCheckpointId: checkpointIdGenerator };
343
- const recordCachedStage = (name: string, replayKey: string, checkpoint: DurableCompletedStageCheckpoint): void =>
344
- recordCachedStageWithTracker(activeStore, tracker, runId, name, replayKey, checkpoint, completedStageReplayKeys);
341
+ const cachedStage = createDurableCachedStageRecorder({
342
+ store: activeStore, tracker, run: runSnapshot, backend: durableBackend,
343
+ rootBackend, completedStageReplayKeys,
344
+ });
345
345
  const durableTask = createDurableTaskPrimitive({
346
346
  workflowId: runId, backend: durableBackend,
347
347
  nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName), task: taskRunners.task,
348
- recordCachedTask: (name, replayKey, checkpoint, scope) =>
349
- recordCachedStageWithTracker(activeStore, tracker, runId, name, replayKey, checkpoint, completedStageReplayKeys, scope),
348
+ recordCachedTask: cachedStage.record,
350
349
  });
351
350
  const durableWorkflow = createDurableChildWorkflowPrimitive({
352
351
  workflowId: runId, rootWorkflowId: opts.parentRun?.rootRunId ?? runId, backend: durableBackend,
353
352
  nextReplayKey: nextDurableChildReplayKey, setChildDurableScope: (scope) => { pendingChildDurableScope = scope; },
354
- recordCachedStage, workflow,
353
+ recordCachedStage: cachedStage.record,
354
+ checkpointMetadata: cachedStage.metadata,
355
+ workflow,
355
356
  });
356
357
  const ctx: WorkflowRunContext<TInputs> = {
357
358
  inputs: resolvedInputs as TInputs,
@@ -380,7 +381,7 @@ export async function run<
380
381
  workflowId: runId,
381
382
  backend: durableBackend,
382
383
  nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName),
383
- recordCachedStage,
384
+ recordCachedStage: cachedStage.record,
384
385
  stage: (name, options, replayKey) => {
385
386
  const stage = runtime.stage(name, options);
386
387
  const stageId = activeStore.runs().find((r) => r.id === runId)?.stages.at(-1)?.id;
@@ -429,6 +430,7 @@ export async function run<
429
430
  const returned = classifyReturnedRunStatus(result, runSnapshot);
430
431
  const recorded = activeStore.recordRunEnd(runId, returned.status, result, returned.error, returned.metadata);
431
432
  appendRunEndWhenRecorded(opts.persistence, recorded, { runId, status: returned.status, result, ...(returned.error !== undefined ? { error: returned.error } : {}), ...(returned.metadata ?? {}), ...(runSnapshot.endedAt !== undefined ? { endedAt: runSnapshot.endedAt } : {}), ...(runSnapshot.durationMs !== undefined ? { durationMs: runSnapshot.durationMs } : {}), ts: Date.now() });
433
+ if (opts.parentRun === undefined) recordRunTimingCheckpoint(durableBackend, runSnapshot);
432
434
  durableBackend.setWorkflowStatus(runId, returned.status, undefined, returned.metadata?.resumable);
433
435
  await durableBackend.flush();
434
436
  return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status: returned.status, result, error: returned.error }, opts.onRunEnd);
@@ -16,10 +16,22 @@ import { shutdownDbos } from "../durable/dbos-lifecycle.js";
16
16
 
17
17
  let processShutdownInstalled = false;
18
18
 
19
+ /**
20
+ * Session dispose and process exit must never crash on durability teardown:
21
+ * a genuine flush/stop failure is diagnostic, not fatal, and an unhandled
22
+ * rejection here turns an otherwise-successful run into a nonzero exit.
23
+ */
24
+ function shutdownDbosQuietly(): Promise<void> {
25
+ return shutdownDbos().catch((error: unknown) => {
26
+ const detail = error instanceof Error ? error.message : String(error);
27
+ console.error(`atomic-workflows: DBOS durability shutdown failed: ${detail}`);
28
+ });
29
+ }
30
+
19
31
  function installDbosProcessShutdown(): void {
20
32
  if (processShutdownInstalled) return;
21
33
  processShutdownInstalled = true;
22
- process.once("beforeExit", () => shutdownDbos());
34
+ process.once("beforeExit", () => void shutdownDbosQuietly());
23
35
  }
24
36
 
25
37
  export interface WorkflowLifecycleRegistrationDeps {
@@ -123,6 +135,6 @@ export function registerWorkflowLifecycleHandlers(
123
135
  deps.storeWidgetRef.current = null;
124
136
  runtimeState.resetWorkflowDiscoveryForSession();
125
137
  runtimeState.setNotificationsActive(false);
126
- await shutdownDbos();
138
+ await shutdownDbosQuietly();
127
139
  });
128
140
  }
@@ -2,6 +2,7 @@ import type {
2
2
  CreateAgentSessionOptions,
3
3
  DefaultResourceLoaderInheritanceSnapshot,
4
4
  } from "@bastani/atomic";
5
+ import type { Api, Model } from "@earendil-works/pi-ai/compat";
5
6
  import type { SessionManager } from "../shared/persistence-restore.js";
6
7
  import type { StageSessionRuntime } from "../runs/foreground/stage-runner.js";
7
8
  import type { RunStatus, StageStatus } from "../shared/store-types.js";
@@ -58,10 +59,7 @@ export interface PiCommandOptions {
58
59
  getArgumentCompletions?: (partial: string) => PiArgumentCompletionResult | Promise<PiArgumentCompletionResult>;
59
60
  }
60
61
 
61
- export interface PiRuntimeModel {
62
- readonly provider: string;
63
- readonly id: string;
64
- }
62
+ export type PiRuntimeModel = Model<Api>;
65
63
 
66
64
  export interface PiRuntimeModelRegistry {
67
65
  getAvailable(): PiRuntimeModel[];
@@ -222,6 +220,7 @@ export interface WorkflowToolArgs {
222
220
  workflow?: string;
223
221
  inputs?: WorkflowInputValues;
224
222
  action?:
223
+ | "models"
225
224
  | "run"
226
225
  | "list"
227
226
  | "get"
@@ -11,6 +11,7 @@ export interface WorkflowToolArgs {
11
11
  workflow?: string;
12
12
  inputs?: WorkflowInputValues;
13
13
  action?:
14
+ | "models"
14
15
  | "run"
15
16
  | "list"
16
17
  | "get"
@@ -106,6 +107,9 @@ export function renderCall(args: WorkflowToolArgs, opts: RenderCallOpts = {}): s
106
107
  case "get":
107
108
  line = name === undefined ? "workflow: get" : `workflow: get ${quoted(name)}`;
108
109
  break;
110
+ case "models":
111
+ line = "workflow: list configured models";
112
+ break;
109
113
  default:
110
114
  line = name === undefined ? `workflow: ${action}` : `workflow: ${action} ${quoted(name)}`;
111
115
  break;
@@ -149,6 +149,14 @@ type ReloadResult = WorkflowReloadReport & { action: "reload"; status: "ok" | "n
149
149
  type InterruptResult = { action: "interrupt"; runId: string; status: string; message: string };
150
150
  type QuitResult = { action: "quit"; runId: string; status: string; message: string };
151
151
  type ResumeResult = { action: "resume"; runId: string; status: string; message: string };
152
+ export interface ModelCatalogEntry {
153
+ provider: string;
154
+ id: string;
155
+ fullId: string;
156
+ isCurrent: boolean;
157
+ availableThinkingLevels?: readonly string[];
158
+ }
159
+ type ModelsResult = { action: "models"; models: ModelCatalogEntry[] };
152
160
 
153
161
  export type WorkflowToolResult =
154
162
  | ListResult
@@ -165,7 +173,8 @@ export type WorkflowToolResult =
165
173
  | ReloadResult
166
174
  | InterruptResult
167
175
  | QuitResult
168
- | ResumeResult;
176
+ | ResumeResult
177
+ | ModelsResult;
169
178
 
170
179
  export interface RenderResultOpts {
171
180
  isPartial?: boolean;
@@ -432,6 +441,27 @@ export function renderResult(result: WorkflowToolResult, opts?: RenderResultOpts
432
441
  return renderNotice("WORKFLOW RESUME", `${r.runId}: ${r.message}`, opts, themed);
433
442
  }
434
443
 
444
+ case "models": {
445
+ const r = result as ModelsResult;
446
+ if (r.models.length === 0) {
447
+ return renderNotice("WORKFLOW MODELS", "no models in configured catalog — configured-auth snapshot, not proof of credentials, entitlements, OAuth freshness, or live provider access.", opts, themed);
448
+ }
449
+ const currentLine = r.models.find((model) => model.isCurrent);
450
+ const lines = r.models.map((model) => {
451
+ const levels = model.availableThinkingLevels?.length
452
+ ? ` [levels: ${model.availableThinkingLevels.join(", ")}]`
453
+ : "";
454
+ return `${model.provider}/${model.id}${model.isCurrent ? " (current)" : ""}${levels}`;
455
+ }).join("; ");
456
+ const suffix = currentLine !== undefined ? "" : " (no current model)";
457
+ return renderNotice(
458
+ "WORKFLOW MODELS",
459
+ `${lines}${suffix} — configured-auth catalog snapshot, not proof of credentials, entitlements, OAuth freshness, or live provider access.`,
460
+ opts,
461
+ themed,
462
+ );
463
+ }
464
+
435
465
  default: {
436
466
  // Runtime guard — handles values coerced from external sources.
437
467
  const fallback = result as { action: string; message?: string };
@@ -20,6 +20,7 @@ import {
20
20
  } from "../durable/completed-inspection.js";
21
21
  import type { ResumableWorkflowEntry } from "../durable/types.js";
22
22
  import type { DurableWorkflowCatalogEntries } from "../durable/backend.js";
23
+ import { discoverWorkflows } from "./discovery.js";
23
24
 
24
25
  export interface DurableResumeRuntime {
25
26
  resumeDurableWorkflow(
@@ -76,6 +77,8 @@ export function createDurableResumeRuntime(
76
77
  registry: deps.registry,
77
78
  baseRunOpts: deps.baseRunOpts(options?.policy),
78
79
  durableBackend: backend,
80
+ resolveDefinition: async (name, cwd) =>
81
+ (await discoverWorkflows({ cwd: cwd ?? deps.runtimeCwd })).registry.get(name),
79
82
  ...(deps.jobs !== undefined ? { jobs: deps.jobs } : {}),
80
83
  };
81
84
  return await resumeDurableWorkflowAdapter(workflowIdOrPrefix, adapterDeps, preparedCatalog);