@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.
- package/CHANGELOG.md +37 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +3 -3
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +3 -3
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +23 -0
- package/dist/builtin/workflows/README.md +8 -3
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +1 -1
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/durable/backend.ts +2 -2
- package/dist/builtin/workflows/src/durable/child-primitive.ts +3 -0
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +182 -33
- package/dist/builtin/workflows/src/durable/completed-inspection.ts +9 -4
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +173 -0
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +36 -12
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +31 -8
- package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +9 -3
- package/dist/builtin/workflows/src/durable/factory.ts +35 -6
- package/dist/builtin/workflows/src/durable/local-command.ts +11 -2
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +6 -2
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +48 -10
- package/dist/builtin/workflows/src/durable/types.ts +11 -0
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +9 -9
- package/dist/builtin/workflows/src/engine/run-durable-topology.ts +89 -0
- package/dist/builtin/workflows/src/engine/run.ts +18 -16
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +14 -2
- package/dist/builtin/workflows/src/extension/public-types.ts +3 -4
- package/dist/builtin/workflows/src/extension/render-call.ts +4 -0
- package/dist/builtin/workflows/src/extension/render-result.ts +31 -1
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +3 -0
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +5 -2
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -2
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +1 -0
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +88 -2
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +13 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +3 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +6 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +2 -2
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +4 -1
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +2 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +7 -1
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +2 -1
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +7 -0
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +13 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +12 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-tree.d.ts.map +1 -1
- package/dist/core/agent-session-tree.js +4 -1
- package/dist/core/agent-session-tree.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +15 -0
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +2 -0
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/auth-storage-backends.d.ts +4 -0
- package/dist/core/auth-storage-backends.d.ts.map +1 -1
- package/dist/core/auth-storage-backends.js +48 -3
- package/dist/core/auth-storage-backends.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +27 -11
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/codex-errors.d.ts +3 -0
- package/dist/core/codex-errors.d.ts.map +1 -0
- package/dist/core/codex-errors.js +12 -0
- package/dist/core/codex-errors.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts +5 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +7 -8
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction-runner.d.ts +3 -1
- package/dist/core/compaction/compaction-runner.d.ts.map +1 -1
- package/dist/core/compaction/compaction-runner.js +6 -1
- package/dist/core/compaction/compaction-runner.js.map +1 -1
- package/dist/core/compaction/range-planner.d.ts +3 -0
- package/dist/core/compaction/range-planner.d.ts.map +1 -1
- package/dist/core/compaction/range-planner.js +2 -1
- package/dist/core/compaction/range-planner.js.map +1 -1
- package/dist/core/model-registry.js +2 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver-defaults.d.ts.map +1 -1
- package/dist/core/model-resolver-defaults.js +2 -0
- package/dist/core/model-resolver-defaults.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +1 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +6 -4
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +25 -6
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/sdk-exports.d.ts +1 -1
- package/dist/core/sdk-exports.d.ts.map +1 -1
- package/dist/core/sdk-exports.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +4 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +1 -0
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +7 -4
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-validation.d.ts.map +1 -1
- package/dist/core/session-manager-validation.js +3 -16
- package/dist/core/session-manager-validation.js.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/summarization-retry.d.ts +11 -0
- package/dist/core/summarization-retry.d.ts.map +1 -0
- package/dist/core/summarization-retry.js +21 -0
- package/dist/core/summarization-retry.js.map +1 -0
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +16 -0
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/components/footer.js +2 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +6 -0
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-auth-routing.d.ts +2 -1
- package/dist/modes/interactive/interactive-auth-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-auth-routing.js +10 -5
- package/dist/modes/interactive/interactive-auth-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js +12 -2
- package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/dist/modes/interactive/interactive-model-catalog-startup.d.ts +13 -0
- package/dist/modes/interactive/interactive-model-catalog-startup.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-model-catalog-startup.js +24 -0
- package/dist/modes/interactive/interactive-model-catalog-startup.js.map +1 -0
- package/dist/modes/interactive/interactive-model-routing.js +4 -3
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-queueing.js +3 -2
- package/dist/modes/interactive/interactive-queueing.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +4 -7
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-summarization-retry-events.d.ts +8 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.js +40 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.js.map +1 -0
- package/dist/modes/interactive-engine/extension-ui-bridge.d.ts +3 -1
- package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
- package/dist/modes/interactive-engine/extension-ui-bridge.js +6 -0
- package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts +4 -5
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +14 -24
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/interactive-engine/remote-model-catalog.d.ts +15 -0
- package/dist/modes/interactive-engine/remote-model-catalog.d.ts.map +1 -0
- package/dist/modes/interactive-engine/remote-model-catalog.js +67 -0
- package/dist/modes/interactive-engine/remote-model-catalog.js.map +1 -0
- package/dist/modes/rpc/rpc-client-api.d.ts +9 -1
- package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-api.js +12 -0
- package/dist/modes/rpc/rpc-client-api.js.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.js +35 -0
- package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +78 -7
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/docs.json +9 -1
- package/docs/extensions.md +3 -3
- package/docs/json.md +7 -1
- package/docs/models/artificial-analysis-index.md +82 -0
- package/docs/models/model-selection.md +63 -0
- package/docs/models/pareto-efficiency.md +64 -0
- package/docs/prompt-templates.md +1 -0
- package/docs/providers.md +9 -1
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +76 -0
- package/docs/sdk.md +3 -0
- package/docs/tui.md +2 -2
- package/docs/workflows.md +26 -13
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/sandbox/package-lock.json +3 -3
- package/npm-shrinkwrap.json +37 -37
- package/package.json +7 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** DBOS-
|
|
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
|
|
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)
|
|
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
|
-
/**
|
|
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
|
|
35
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
265
|
+
export function durableStageCheckpointMetadata(
|
|
266
|
+
stage: StageSnapshot,
|
|
267
|
+
run?: DurableStageRunTopology,
|
|
268
|
+
): Partial<DurableStageCheckpoint> {
|
|
260
269
|
return {
|
|
261
|
-
topology: {
|
|
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
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
344
|
-
|
|
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:
|
|
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
|
|
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", () =>
|
|
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
|
|
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
|
|
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);
|