@bastani/atomic 0.9.13-alpha.2 → 0.9.13-alpha.4
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 +30 -0
- package/README.md +1 -1
- package/dist/builtin/i-have-adhd/package.json +1 -1
- package/dist/builtin/intercom/CHANGELOG.md +14 -0
- package/dist/builtin/intercom/README.md +12 -12
- package/dist/builtin/intercom/incoming-message-delivery.ts +1 -1
- package/dist/builtin/intercom/index.ts +1 -1
- package/dist/builtin/intercom/intercom-tool.ts +1 -1
- package/dist/builtin/intercom/intercom-utils.ts +3 -6
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/result-renderers.ts +1 -1
- package/dist/builtin/intercom/session-target.ts +1 -15
- package/dist/builtin/intercom/skills/intercom/SKILL.md +6 -6
- package/dist/builtin/intercom/terminal-ordering-barrier.ts +2 -2
- package/dist/builtin/intercom/ui/inline-message.ts +7 -5
- package/dist/builtin/intercom/ui/session-list.ts +23 -10
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +19 -0
- package/dist/builtin/subagents/README.md +29 -73
- package/dist/builtin/subagents/package.json +2 -2
- package/dist/builtin/subagents/prompts/parallel-cleanup.md +1 -1
- package/dist/builtin/subagents/prompts/parallel-review.md +1 -1
- package/dist/builtin/subagents/prompts/review-loop.md +2 -2
- package/dist/builtin/subagents/skills/subagent/SKILL.md +23 -59
- package/dist/builtin/subagents/skills/tmux/SKILL.md +1 -1
- package/dist/builtin/subagents/src/extension/control-notices.ts +2 -3
- package/dist/builtin/subagents/src/extension/doctor.ts +2 -14
- package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -17
- package/dist/builtin/subagents/src/extension/index.ts +9 -70
- package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
- package/dist/builtin/subagents/src/extension/prompt-guidance.ts +2 -2
- package/dist/builtin/subagents/src/extension/schemas.ts +2 -8
- package/dist/builtin/subagents/src/extension/tool-description.ts +2 -3
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +5 -46
- package/dist/builtin/subagents/src/runs/{background → foreground}/completion-notification.ts +2 -2
- package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +25 -88
- package/dist/builtin/subagents/src/runs/{background → foreground}/notify.ts +5 -5
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +11 -28
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +3 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +0 -5
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +30 -20
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +4 -4
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +0 -13
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +104 -48
- package/dist/builtin/subagents/src/runs/inprocess/index.ts +1 -1
- package/dist/builtin/subagents/src/runs/inprocess/runner.ts +5 -5
- package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-api.ts +0 -7
- package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-core.ts +0 -10
- package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-projection.ts +3 -108
- package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-sanitize.ts +0 -4
- package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +1 -1
- package/dist/builtin/subagents/src/shared/formatters.ts +3 -5
- package/dist/builtin/subagents/src/shared/model-resolution.ts +78 -0
- package/dist/builtin/subagents/src/shared/status-format.ts +4 -11
- package/dist/builtin/subagents/src/shared/types-config.ts +10 -9
- package/dist/builtin/subagents/src/shared/types-nested.ts +127 -0
- package/dist/builtin/subagents/src/shared/types-results.ts +2 -8
- package/dist/builtin/subagents/src/shared/types-runtime.ts +0 -9
- package/dist/builtin/subagents/src/shared/types.ts +1 -1
- package/dist/builtin/subagents/src/shared/utils.ts +1 -109
- package/dist/builtin/subagents/src/slash/slash-commands.ts +7 -17
- package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -252
- package/dist/builtin/subagents/src/tui/render-layout.ts +4 -4
- package/dist/builtin/subagents/src/tui/render-result.ts +0 -9
- package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -34
- package/dist/builtin/subagents/src/tui/render.ts +0 -2
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +23 -0
- package/dist/builtin/workflows/README.md +3 -3
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +11 -0
- package/dist/builtin/workflows/src/durable/completed-catalog-stage-groups.ts +63 -25
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +166 -25
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +42 -24
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +2 -1
- package/dist/builtin/workflows/src/durable/run-timing.ts +28 -7
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +4 -0
- package/dist/builtin/workflows/src/durable/targeted-inspection.ts +110 -0
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +2 -2
- package/dist/builtin/workflows/src/engine/runtime.ts +19 -2
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +750 -168
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +6 -0
- package/dist/builtin/workflows/src/extension/ui-surface.ts +4 -0
- package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +6 -1
- package/dist/builtin/workflows/src/extension/workflow-targets.ts +18 -10
- package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +31 -16
- package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +75 -3
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +53 -6
- package/dist/builtin/workflows/src/runs/background/run-inspect.ts +5 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +94 -60
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +7 -7
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +25 -4
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +4 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +142 -4
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-session-heartbeat.ts +88 -0
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +1 -0
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +1 -0
- package/dist/builtin/workflows/src/tui/run-detail.ts +25 -12
- package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +27 -8
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +17 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +20 -3
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +10 -1
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +1 -0
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +22 -5
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-export.d.ts.map +1 -1
- package/dist/core/agent-session-export.js +2 -1
- package/dist/core/agent-session-export.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +10 -4
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +68 -0
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-queue-pause.d.ts.map +1 -1
- package/dist/core/agent-session-queue-pause.js +6 -0
- package/dist/core/agent-session-queue-pause.js.map +1 -1
- package/dist/core/agent-session-summary.d.ts +21 -0
- package/dist/core/agent-session-summary.d.ts.map +1 -0
- package/dist/core/agent-session-summary.js +156 -0
- package/dist/core/agent-session-summary.js.map +1 -0
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +0 -4
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session-transfer.d.ts.map +1 -1
- package/dist/core/agent-session-transfer.js +0 -2
- package/dist/core/agent-session-transfer.js.map +1 -1
- package/dist/core/agent-session-tree.d.ts.map +1 -1
- package/dist/core/agent-session-tree.js +6 -0
- package/dist/core/agent-session-tree.js.map +1 -1
- package/dist/core/agent-session.d.ts +21 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +22 -5
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.js +2 -2
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/index.d.ts +1 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/session-summarization.d.ts +41 -0
- package/dist/core/compaction/session-summarization.d.ts.map +1 -0
- package/dist/core/compaction/session-summarization.js +97 -0
- package/dist/core/compaction/session-summarization.js.map +1 -0
- package/dist/core/extensions/ui-types.d.ts +4 -0
- package/dist/core/extensions/ui-types.d.ts.map +1 -1
- package/dist/core/extensions/ui-types.js.map +1 -1
- package/dist/core/keybindings.d.ts +0 -5
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +0 -2
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +3 -1
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +9 -1
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +24 -1
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +48 -0
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +10 -0
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-types.d.ts +10 -1
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +3 -0
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +5 -0
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +4 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/slash-commands.js +2 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts +0 -15
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -81
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/fetch-url.d.ts +12 -0
- package/dist/core/tools/fetch-url.d.ts.map +1 -1
- package/dist/core/tools/fetch-url.js +76 -10
- package/dist/core/tools/fetch-url.js.map +1 -1
- package/dist/core/tools/index.js +4 -4
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/session-temp-dir.d.ts +2 -2
- package/dist/core/tools/session-temp-dir.d.ts.map +1 -1
- package/dist/core/tools/session-temp-dir.js +6 -7
- package/dist/core/tools/session-temp-dir.js.map +1 -1
- package/dist/core/usage-totals.js +1 -1
- package/dist/core/usage-totals.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/host-session-picker.d.ts.map +1 -1
- package/dist/modes/interactive/components/host-session-picker.js +1 -0
- package/dist/modes/interactive/components/host-session-picker.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-list.js +29 -4
- package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/transcript-follow-indicator.d.ts +14 -0
- package/dist/modes/interactive/components/transcript-follow-indicator.d.ts.map +1 -0
- package/dist/modes/interactive/components/transcript-follow-indicator.js +36 -0
- package/dist/modes/interactive/components/transcript-follow-indicator.js.map +1 -0
- package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector-content.js +6 -0
- package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-custom-ui.js +3 -0
- package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.js +0 -3
- package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +7 -1
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +3 -3
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-slash-commands.js +2 -5
- package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +6 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-transcript-follow.d.ts +2 -0
- package/dist/modes/interactive/interactive-transcript-follow.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-transcript-follow.js +6 -0
- package/dist/modes/interactive/interactive-transcript-follow.js.map +1 -0
- package/dist/modes/interactive/interactive-tui.d.ts +13 -1
- package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-tui.js +59 -1
- package/dist/modes/interactive/interactive-tui.js.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts +2 -0
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
- package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
- package/dist/modes/interactive-engine/protocol.d.ts +1 -0
- package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
- package/dist/modes/interactive-engine/protocol.js +5 -1
- package/dist/modes/interactive-engine/protocol.js.map +1 -1
- package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
- package/dist/modes/interactive-engine/remote-component.js +21 -9
- package/dist/modes/interactive-engine/remote-component.js.map +1 -1
- package/docs/environment-variables.md +1 -1
- package/docs/intercom.md +20 -21
- package/docs/keybindings.md +2 -5
- package/docs/quickstart.md +5 -1
- package/docs/sdk.md +1 -1
- package/docs/session-format.md +15 -0
- package/docs/sessions.md +8 -0
- package/docs/settings.md +6 -0
- package/docs/subagents.md +19 -31
- package/docs/tools.md +3 -5
- package/docs/usage.md +3 -1
- package/docs/windows.md +1 -1
- package/docs/workflows.md +60 -32
- package/npm-shrinkwrap.json +29 -29
- package/package.json +2 -2
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +0 -331
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +0 -161
- package/dist/builtin/subagents/src/runs/inprocess/background-single.ts +0 -195
- package/dist/builtin/subagents/src/runs/inprocess/background.ts +0 -90
- package/dist/builtin/subagents/src/shared/types-async.ts +0 -247
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +0 -217
- package/dist/builtin/subagents/src/tui/render-widget.ts +0 -366
- package/dist/core/async/format.d.ts +0 -3
- package/dist/core/async/format.d.ts.map +0 -1
- package/dist/core/async/format.js +0 -52
- package/dist/core/async/format.js.map +0 -1
- package/dist/core/async/job-manager.d.ts +0 -40
- package/dist/core/async/job-manager.d.ts.map +0 -1
- package/dist/core/async/job-manager.js +0 -293
- package/dist/core/async/job-manager.js.map +0 -1
- package/dist/core/async/session-manager.d.ts +0 -16
- package/dist/core/async/session-manager.d.ts.map +0 -1
- package/dist/core/async/session-manager.js +0 -34
- package/dist/core/async/session-manager.js.map +0 -1
- package/dist/core/async/types.d.ts +0 -23
- package/dist/core/async/types.d.ts.map +0 -1
- package/dist/core/async/types.js +0 -2
- package/dist/core/async/types.js.map +0 -1
- package/dist/core/tools/bash-async-execution.d.ts +0 -27
- package/dist/core/tools/bash-async-execution.d.ts.map +0 -1
- package/dist/core/tools/bash-async-execution.js +0 -81
- package/dist/core/tools/bash-async-execution.js.map +0 -1
- package/dist/core/tools/bash-async-jobs.d.ts +0 -30
- package/dist/core/tools/bash-async-jobs.d.ts.map +0 -1
- package/dist/core/tools/bash-async-jobs.js +0 -87
- package/dist/core/tools/bash-async-jobs.js.map +0 -1
- package/dist/core/tools/bash-async-output.d.ts +0 -13
- package/dist/core/tools/bash-async-output.d.ts.map +0 -1
- package/dist/core/tools/bash-async-output.js +0 -122
- package/dist/core/tools/bash-async-output.js.map +0 -1
|
@@ -20,9 +20,14 @@ import {
|
|
|
20
20
|
validStageGroup,
|
|
21
21
|
workflowChildFromDraft,
|
|
22
22
|
} from "./completed-catalog-stage-groups.js";
|
|
23
|
+
import { isDurableWorkflowResumable } from "./resume-eligibility.js";
|
|
23
24
|
import { resolveDurableEntry } from "./resume-runtime.js";
|
|
24
25
|
import { priorRunElapsedMs, RUN_TIMING_CHECKPOINT_NAME } from "./run-timing.js";
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
directChildTopologyError,
|
|
28
|
+
groupByDurableStageKey,
|
|
29
|
+
immutableStageGroupError,
|
|
30
|
+
} from "./stage-topology-validation.js";
|
|
26
31
|
import { workflowToolOutcomeFromValue } from "./tool-outcome.js";
|
|
27
32
|
import {
|
|
28
33
|
DURABLE_STAGE_TOPOLOGY_VERSION,
|
|
@@ -94,9 +99,13 @@ export function completedWorkflowRunSnapshots(
|
|
|
94
99
|
if (handle === undefined || (handle.status !== "completed" && handle.status !== "failed")) return [];
|
|
95
100
|
const checkpoints = backend.listCheckpoints(entry.workflowId);
|
|
96
101
|
if (checkpoints.length === 0) return [];
|
|
97
|
-
const runs = runSnapshotsFromCheckpoints(
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
const runs = runSnapshotsFromCheckpoints(
|
|
103
|
+
checkpoints,
|
|
104
|
+
handle.workflowId,
|
|
105
|
+
handle.name,
|
|
106
|
+
handle.updatedAt,
|
|
107
|
+
COMPLETED_RECONSTRUCTION,
|
|
108
|
+
).map((run) => ({ ...run, stages: run.stages.map(validatedStageTranscript) }));
|
|
100
109
|
const rootIndex = runs.findIndex((run) => run.id === handle.workflowId);
|
|
101
110
|
if (rootIndex < 0) return [];
|
|
102
111
|
const rootDuration =
|
|
@@ -121,6 +130,59 @@ export function completedWorkflowRunSnapshots(
|
|
|
121
130
|
return [root, ...runs.filter((_, index) => index !== rootIndex)];
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
/** Rebuild one retained root, including in-progress nested runs, without relaxing topology validation. */
|
|
134
|
+
export function durableWorkflowRunSnapshots(
|
|
135
|
+
backend: DurableWorkflowBackend,
|
|
136
|
+
handle: import("./types.js").DurableWorkflowHandle,
|
|
137
|
+
): readonly RunSnapshot[] {
|
|
138
|
+
const checkpoints = backend.listCheckpoints(handle.workflowId);
|
|
139
|
+
if (checkpoints.length === 0) {
|
|
140
|
+
return [rootSnapshot(backend, handle, { stages: [], toolNodes: [] })];
|
|
141
|
+
}
|
|
142
|
+
const requiresTerminalTopology = handle.status === "completed" || handle.status === "failed";
|
|
143
|
+
const runs = runSnapshotsFromCheckpoints(
|
|
144
|
+
checkpoints,
|
|
145
|
+
handle.workflowId,
|
|
146
|
+
handle.name,
|
|
147
|
+
handle.updatedAt,
|
|
148
|
+
requiresTerminalTopology ? COMPLETED_RECONSTRUCTION : ACTIVE_INSPECTION_RECONSTRUCTION,
|
|
149
|
+
);
|
|
150
|
+
const rootIndex = runs.findIndex((run) => run.id === handle.workflowId);
|
|
151
|
+
if (rootIndex < 0) return [];
|
|
152
|
+
return [rootSnapshot(backend, handle, runs[rootIndex]!), ...runs.filter((_, index) => index !== rootIndex)];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function rootSnapshot(
|
|
156
|
+
backend: DurableWorkflowBackend,
|
|
157
|
+
handle: import("./types.js").DurableWorkflowHandle,
|
|
158
|
+
graph: Pick<RunSnapshot, "stages" | "toolNodes">,
|
|
159
|
+
): RunSnapshot {
|
|
160
|
+
const terminal = handle.status === "completed" || handle.status === "failed" || handle.status === "cancelled";
|
|
161
|
+
const priorDurationMs = priorRunElapsedMs(backend, handle.workflowId);
|
|
162
|
+
const terminalDurationMs = priorDurationMs ?? Math.max(0, handle.updatedAt - handle.createdAt);
|
|
163
|
+
return {
|
|
164
|
+
id: handle.workflowId,
|
|
165
|
+
name: handle.name,
|
|
166
|
+
inputs: { ...handle.inputs } as WorkflowInputValues,
|
|
167
|
+
status: handle.status,
|
|
168
|
+
stages: graph.stages,
|
|
169
|
+
toolNodes: graph.toolNodes,
|
|
170
|
+
startedAt: handle.createdAt,
|
|
171
|
+
...(terminal
|
|
172
|
+
? { endedAt: handle.updatedAt, durationMs: terminalDurationMs }
|
|
173
|
+
: priorDurationMs === undefined
|
|
174
|
+
? {}
|
|
175
|
+
: { accumulatedDurationMs: priorDurationMs }),
|
|
176
|
+
...(handle.error !== undefined ? { error: handle.error } : {}),
|
|
177
|
+
...(handle.failureKind !== undefined ? { failureKind: handle.failureKind } : {}),
|
|
178
|
+
...(handle.failureCode !== undefined ? { failureCode: handle.failureCode } : {}),
|
|
179
|
+
...(handle.failureRecoverability !== undefined ? { failureRecoverability: handle.failureRecoverability } : {}),
|
|
180
|
+
...(handle.failureDisposition !== undefined ? { failureDisposition: handle.failureDisposition } : {}),
|
|
181
|
+
...(handle.failedToolNodeId !== undefined ? { failedToolNodeId: handle.failedToolNodeId } : {}),
|
|
182
|
+
resumable: isDurableWorkflowResumable(handle),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
124
186
|
/** Rebuild completed nested runs while a paused root is replaying cached boundaries. */
|
|
125
187
|
export function durableNestedRunSnapshots(
|
|
126
188
|
backend: DurableWorkflowBackend,
|
|
@@ -133,7 +195,7 @@ export function durableNestedRunSnapshots(
|
|
|
133
195
|
rootWorkflowId,
|
|
134
196
|
handle.name,
|
|
135
197
|
handle.updatedAt,
|
|
136
|
-
|
|
198
|
+
LEGACY_ACTIVE_RECONSTRUCTION,
|
|
137
199
|
).filter((run) => run.id !== rootWorkflowId);
|
|
138
200
|
}
|
|
139
201
|
|
|
@@ -224,14 +286,44 @@ function summarizeCompletedToolResult(value: WorkflowSerializableValue): string
|
|
|
224
286
|
return serialized.length <= 240 ? serialized : `${flattenTruncatedString(serialized.slice(0, 237))}...`;
|
|
225
287
|
}
|
|
226
288
|
|
|
289
|
+
interface ReconstructionPolicy {
|
|
290
|
+
readonly requireTerminal: boolean;
|
|
291
|
+
readonly failClosed: boolean;
|
|
292
|
+
readonly retainActive: boolean;
|
|
293
|
+
readonly failOnUnreachable: boolean;
|
|
294
|
+
readonly requireActiveBoundaryFingerprint: boolean;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const COMPLETED_RECONSTRUCTION: ReconstructionPolicy = {
|
|
298
|
+
requireTerminal: true,
|
|
299
|
+
failClosed: true,
|
|
300
|
+
retainActive: false,
|
|
301
|
+
failOnUnreachable: false,
|
|
302
|
+
requireActiveBoundaryFingerprint: false,
|
|
303
|
+
};
|
|
304
|
+
const ACTIVE_INSPECTION_RECONSTRUCTION: ReconstructionPolicy = {
|
|
305
|
+
requireTerminal: false,
|
|
306
|
+
failClosed: true,
|
|
307
|
+
retainActive: true,
|
|
308
|
+
failOnUnreachable: true,
|
|
309
|
+
requireActiveBoundaryFingerprint: true,
|
|
310
|
+
};
|
|
311
|
+
const LEGACY_ACTIVE_RECONSTRUCTION: ReconstructionPolicy = {
|
|
312
|
+
requireTerminal: false,
|
|
313
|
+
failClosed: false,
|
|
314
|
+
retainActive: true,
|
|
315
|
+
failOnUnreachable: false,
|
|
316
|
+
requireActiveBoundaryFingerprint: false,
|
|
317
|
+
};
|
|
318
|
+
|
|
227
319
|
function runSnapshotsFromCheckpoints(
|
|
228
320
|
checkpoints: readonly DurableCheckpoint[],
|
|
229
321
|
rootRunId: string,
|
|
230
322
|
rootRunName: string,
|
|
231
323
|
fallbackCompletedAt: number,
|
|
232
|
-
|
|
324
|
+
policy: ReconstructionPolicy = COMPLETED_RECONSTRUCTION,
|
|
233
325
|
): RunSnapshot[] {
|
|
234
|
-
if (!validBoundaryRecordSet(checkpoints,
|
|
326
|
+
if (!validBoundaryRecordSet(checkpoints, policy.requireTerminal, policy.requireActiveBoundaryFingerprint)) return [];
|
|
235
327
|
const stageRecords = checkpoints.filter(
|
|
236
328
|
(checkpoint): checkpoint is DurableStageCheckpoint => checkpoint.kind === "stage",
|
|
237
329
|
);
|
|
@@ -248,7 +340,10 @@ function runSnapshotsFromCheckpoints(
|
|
|
248
340
|
.map((records) => mergeStageGroup(records, stageRecords, checkpoints))
|
|
249
341
|
.filter(
|
|
250
342
|
(draft) =>
|
|
251
|
-
|
|
343
|
+
policy.retainActive ||
|
|
344
|
+
draft.topology?.run === undefined ||
|
|
345
|
+
draft.endedAt !== undefined ||
|
|
346
|
+
draft.output !== undefined,
|
|
252
347
|
)
|
|
253
348
|
.map(
|
|
254
349
|
(draft): StageDraft =>
|
|
@@ -267,14 +362,46 @@ function runSnapshotsFromCheckpoints(
|
|
|
267
362
|
.sort(compareDraftSourceOrder);
|
|
268
363
|
const drafts = checkpointDrafts(checkpoints);
|
|
269
364
|
const toolCheckpoints = drafts.tools;
|
|
365
|
+
if (policy.failClosed) {
|
|
366
|
+
for (const draft of candidates) {
|
|
367
|
+
const boundary = draft.topology?.boundary;
|
|
368
|
+
if (boundary === undefined || childRunIdFromDraft(draft) === undefined) continue;
|
|
369
|
+
if (boundary.replayScope !== draft.replayKey) return [];
|
|
370
|
+
const expectedChildRun = {
|
|
371
|
+
runId: boundary.child.runId,
|
|
372
|
+
runName: boundary.child.runName,
|
|
373
|
+
parentRunId: boundary.child.parentRunId,
|
|
374
|
+
parentStageId: boundary.child.parentStageId,
|
|
375
|
+
rootRunId: boundary.child.rootRunId,
|
|
376
|
+
};
|
|
377
|
+
const childToolIds = new Set(
|
|
378
|
+
toolCheckpoints.flatMap((checkpoint) =>
|
|
379
|
+
checkpoint.topology?.run?.runId === expectedChildRun.runId ? [checkpoint.topology.nodeId] : [],
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
if (
|
|
383
|
+
directChildTopologyError(stageRecords, boundary.replayScope, expectedChildRun, true, childToolIds) !==
|
|
384
|
+
undefined
|
|
385
|
+
)
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
270
389
|
if (candidates.length === 0 && toolCheckpoints.length === 0) {
|
|
271
|
-
|
|
390
|
+
const supportedNonGraphState = checkpoints.every(
|
|
391
|
+
(checkpoint) =>
|
|
392
|
+
checkpoint.kind === "ui" ||
|
|
393
|
+
(checkpoint.kind === "tool" && checkpoint.argsHash === RUN_TIMING_CHECKPOINT_NAME),
|
|
394
|
+
);
|
|
395
|
+
if (policy.failClosed && supportedNonGraphState) {
|
|
396
|
+
return [emptyGraphRun(rootRunId, rootRunName, fallbackCompletedAt)];
|
|
397
|
+
}
|
|
398
|
+
return policy.failClosed ? [] : [syntheticRun(rootRunId, rootRunName, checkpoints.length, fallbackCompletedAt)];
|
|
272
399
|
}
|
|
273
400
|
|
|
274
401
|
const grouped = new Map<string, StageDraft[]>();
|
|
275
402
|
for (const draft of candidates) {
|
|
276
403
|
if (draft.topology?.version !== DURABLE_STAGE_TOPOLOGY_VERSION) {
|
|
277
|
-
if (
|
|
404
|
+
if (policy.failClosed) return [];
|
|
278
405
|
continue;
|
|
279
406
|
}
|
|
280
407
|
const runId = draft.topology.run?.runId ?? rootRunId;
|
|
@@ -287,7 +414,9 @@ function runSnapshotsFromCheckpoints(
|
|
|
287
414
|
if (!grouped.has(runId)) grouped.set(runId, []);
|
|
288
415
|
}
|
|
289
416
|
for (const group of grouped.values()) group.sort(compareDraftSourceOrder);
|
|
417
|
+
const groupedCount = grouped.size;
|
|
290
418
|
retainReachableRunGroups(grouped, rootRunId);
|
|
419
|
+
if (policy.failOnUnreachable && grouped.size !== groupedCount) return [];
|
|
291
420
|
if (!validateRunGroups(grouped, rootRunId, toolCheckpoints, checkpoints)) return [];
|
|
292
421
|
const syntheticChildren: Array<{
|
|
293
422
|
readonly parentRunId: string;
|
|
@@ -302,7 +431,7 @@ function runSnapshotsFromCheckpoints(
|
|
|
302
431
|
!isSyntheticExitedChild(draft, parentRunId, child.runId, rootRunId) ||
|
|
303
432
|
childScopedEvidenceExists(checkpoints, draft.replayKey, child.runId)
|
|
304
433
|
) {
|
|
305
|
-
if (
|
|
434
|
+
if (policy.failClosed) return [];
|
|
306
435
|
continue;
|
|
307
436
|
}
|
|
308
437
|
syntheticChildren.push({ parentRunId, owner: draft, child });
|
|
@@ -311,14 +440,10 @@ function runSnapshotsFromCheckpoints(
|
|
|
311
440
|
|
|
312
441
|
const idMaps = new Map<string, Map<string, string>>();
|
|
313
442
|
for (const [runId, runDrafts] of grouped) {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
(checkpoint.topology?.run?.runId ?? rootRunId) === runId
|
|
317
|
-
? [checkpoint.topology?.nodeId ?? checkpoint.checkpointId]
|
|
318
|
-
: [],
|
|
319
|
-
),
|
|
443
|
+
const ownedTools = toolCheckpoints.filter(
|
|
444
|
+
(checkpoint) => (checkpoint.topology?.run?.runId ?? rootRunId) === runId,
|
|
320
445
|
);
|
|
321
|
-
if (!validStageGroup(runDrafts, runId,
|
|
446
|
+
if (!validStageGroup(runDrafts, runId, rootRunId, ownedTools)) return [];
|
|
322
447
|
const ids = new Map<string, string>();
|
|
323
448
|
let hasIdentityConflict = false;
|
|
324
449
|
runDrafts.forEach((draft) => {
|
|
@@ -331,7 +456,7 @@ function runSnapshotsFromCheckpoints(
|
|
|
331
456
|
}
|
|
332
457
|
});
|
|
333
458
|
if (hasIdentityConflict) {
|
|
334
|
-
if (
|
|
459
|
+
if (policy.failClosed) return [];
|
|
335
460
|
grouped.delete(runId);
|
|
336
461
|
continue;
|
|
337
462
|
}
|
|
@@ -356,7 +481,7 @@ function runSnapshotsFromCheckpoints(
|
|
|
356
481
|
(checkpoint) => checkpoint.topology?.parentIds.some((parentId) => !ids.has(parentId)) === true,
|
|
357
482
|
);
|
|
358
483
|
if (hasUnknownParents) {
|
|
359
|
-
if (
|
|
484
|
+
if (policy.failClosed) return [];
|
|
360
485
|
continue;
|
|
361
486
|
}
|
|
362
487
|
const stages = runDrafts.map((draft) =>
|
|
@@ -377,6 +502,8 @@ function runSnapshotsFromCheckpoints(
|
|
|
377
502
|
...toolNodes.map((tool) => tool.endedAt ?? fallbackCompletedAt),
|
|
378
503
|
);
|
|
379
504
|
const owner = runId === rootRunId ? undefined : boundaryOwner(grouped, runId);
|
|
505
|
+
const status = owner === undefined ? "completed" : childRunStatus(owner);
|
|
506
|
+
const terminal = status !== "running" && status !== "pending" && status !== "paused" && status !== "blocked";
|
|
380
507
|
const parentRunId = run?.parentRunId ?? rootRunId;
|
|
381
508
|
const boundarySourceId = grouped.get(parentRunId)?.find((draft) => childRunIdFromDraft(draft) === runId)
|
|
382
509
|
?.topology?.stageId;
|
|
@@ -390,12 +517,11 @@ function runSnapshotsFromCheckpoints(
|
|
|
390
517
|
id: runId,
|
|
391
518
|
name: run?.runName ?? rootRunName,
|
|
392
519
|
inputs: {},
|
|
393
|
-
status
|
|
520
|
+
status,
|
|
394
521
|
stages,
|
|
395
522
|
toolNodes,
|
|
396
523
|
startedAt,
|
|
397
|
-
endedAt,
|
|
398
|
-
durationMs: Math.max(0, endedAt - startedAt),
|
|
524
|
+
...(terminal ? { endedAt, durationMs: Math.max(0, endedAt - startedAt) } : {}),
|
|
399
525
|
...(run?.parentRunId !== undefined ? { parentRunId: run.parentRunId } : {}),
|
|
400
526
|
...(parentStageId !== undefined ? { parentStageId } : {}),
|
|
401
527
|
...(run?.rootRunId !== undefined ? { rootRunId: run.rootRunId } : {}),
|
|
@@ -412,14 +538,14 @@ function runSnapshotsFromCheckpoints(
|
|
|
412
538
|
}
|
|
413
539
|
for (const { parentRunId, owner, child } of syntheticChildren) {
|
|
414
540
|
if (!emittedRunIds.has(parentRunId)) {
|
|
415
|
-
if (
|
|
541
|
+
if (policy.failClosed) return [];
|
|
416
542
|
continue;
|
|
417
543
|
}
|
|
418
544
|
const topology = owner.topology!;
|
|
419
545
|
const boundary = topology.boundary!;
|
|
420
546
|
const parentStageId = idMaps.get(parentRunId)?.get(topology.stageId);
|
|
421
547
|
if (parentStageId === undefined) {
|
|
422
|
-
if (
|
|
548
|
+
if (policy.failClosed) return [];
|
|
423
549
|
continue;
|
|
424
550
|
}
|
|
425
551
|
const startedAt = owner.startedAt ?? owner.firstCompletedAt;
|
|
@@ -490,6 +616,21 @@ function stageResult(draft: StageDraft): string | undefined {
|
|
|
490
616
|
return typeof draft.output === "string" ? draft.output : JSON.stringify(draft.output);
|
|
491
617
|
}
|
|
492
618
|
|
|
619
|
+
function emptyGraphRun(runId: string, runName: string, completedAt: number): RunSnapshot {
|
|
620
|
+
return {
|
|
621
|
+
id: runId,
|
|
622
|
+
name: runName,
|
|
623
|
+
inputs: {},
|
|
624
|
+
status: "completed",
|
|
625
|
+
stages: [],
|
|
626
|
+
toolNodes: [],
|
|
627
|
+
startedAt: completedAt,
|
|
628
|
+
endedAt: completedAt,
|
|
629
|
+
durationMs: 0,
|
|
630
|
+
resumable: false,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
493
634
|
function syntheticRun(runId: string, runName: string, checkpointCount: number, completedAt: number): RunSnapshot {
|
|
494
635
|
return {
|
|
495
636
|
id: runId,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type DurableInactiveDeleteResult,
|
|
6
6
|
type DurableWorkflowBackend,
|
|
7
7
|
type DurableWorkflowCatalogEntries,
|
|
8
|
+
type DurableWorkflowHydrationResult,
|
|
8
9
|
InMemoryDurableBackend,
|
|
9
10
|
type WorkflowRegistrationInput,
|
|
10
11
|
} from "./backend.js";
|
|
@@ -439,15 +440,21 @@ export class DbosDurableBackend implements DurableWorkflowBackend {
|
|
|
439
440
|
}
|
|
440
441
|
|
|
441
442
|
async hydrateWorkflow(workflowId: string): Promise<void> {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
443
|
+
await this.hydrateWorkflowForInspection(workflowId);
|
|
444
|
+
}
|
|
445
|
+
async hydrateWorkflowForInspection(workflowId: string): Promise<DurableWorkflowHydrationResult> {
|
|
446
|
+
if (this.locallyRegistered.has(workflowId)) {
|
|
447
|
+
const handle = this.getLoadableWorkflow(workflowId);
|
|
448
|
+
return handle === undefined ? { kind: "malformed" } : { kind: "current", handle };
|
|
447
449
|
}
|
|
450
|
+
const info = await this.sdk.retrieveWorkflow(workflowId);
|
|
451
|
+
if (info !== undefined) return await this.hydrateInfo(info);
|
|
448
452
|
const records = await this.sdk.listStepRecords(workflowId);
|
|
449
453
|
const deletion = classifyDbosDeletionTombstone(records, workflowId);
|
|
450
|
-
if (deletion
|
|
454
|
+
if (deletion === "absent" && records.length === 0) return { kind: "absent" };
|
|
455
|
+
await this.suppressWorkflow(workflowId);
|
|
456
|
+
if (deletion === "absent") return { kind: "malformed" };
|
|
457
|
+
return { kind: deletion === "current" ? "deleted" : "malformed" };
|
|
451
458
|
}
|
|
452
459
|
async hydrateResumableWorkflows(): Promise<void> {
|
|
453
460
|
const all = await this.sdk.listAllWorkflows();
|
|
@@ -457,12 +464,17 @@ export class DbosDurableBackend implements DurableWorkflowBackend {
|
|
|
457
464
|
}
|
|
458
465
|
}
|
|
459
466
|
|
|
460
|
-
private async hydrateInfo(info: DbosWorkflowInfo): Promise<
|
|
467
|
+
private async hydrateInfo(info: DbosWorkflowInfo): Promise<DurableWorkflowHydrationResult> {
|
|
461
468
|
const records = await this.sdk.listStepRecords(info.workflowId);
|
|
469
|
+
const deletion = classifyDbosDeletionTombstone(records, info.workflowId);
|
|
470
|
+
if (deletion !== "absent") {
|
|
471
|
+
await this.suppressWorkflow(info.workflowId);
|
|
472
|
+
return { kind: deletion === "current" ? "deleted" : "malformed" };
|
|
473
|
+
}
|
|
462
474
|
const metadata = classifyLatestMetadata(records, info.workflowId);
|
|
463
475
|
if (metadata.kind !== "current") {
|
|
464
476
|
await this.suppressWorkflow(info.workflowId);
|
|
465
|
-
return;
|
|
477
|
+
return { kind: "malformed" };
|
|
466
478
|
}
|
|
467
479
|
const checkpoints: DurableCheckpoint[] = [];
|
|
468
480
|
for (const record of records) {
|
|
@@ -475,7 +487,7 @@ export class DbosDurableBackend implements DurableWorkflowBackend {
|
|
|
475
487
|
const classified = classifyCheckpointPayload(info.workflowId, record.stepName, record.output);
|
|
476
488
|
if (classified.kind === "unknown") {
|
|
477
489
|
await this.suppressWorkflow(info.workflowId);
|
|
478
|
-
return;
|
|
490
|
+
return { kind: "malformed" };
|
|
479
491
|
}
|
|
480
492
|
checkpoints.push(classified.checkpoint);
|
|
481
493
|
}
|
|
@@ -485,22 +497,28 @@ export class DbosDurableBackend implements DurableWorkflowBackend {
|
|
|
485
497
|
this.applyMetadata(info.workflowId, metadata.metadata);
|
|
486
498
|
for (const checkpoint of checkpoints) this.mem.recordCheckpoint(checkpoint);
|
|
487
499
|
const current = this.mem.getWorkflow(info.workflowId);
|
|
488
|
-
if (current
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
metadata.metadata.pendingPrompts,
|
|
492
|
-
records,
|
|
493
|
-
metadata.metadata.promptReservationEpoch,
|
|
494
|
-
);
|
|
495
|
-
// Re-register instead of setWorkflowStatus: hydration is a read-side
|
|
496
|
-
// reconstruction and must preserve the authoritative updatedAt, which
|
|
497
|
-
// doubles as the cross-session liveness heartbeat for running handles.
|
|
498
|
-
this.applyMetadata(info.workflowId, {
|
|
499
|
-
...metadata.metadata,
|
|
500
|
-
pendingPrompts,
|
|
501
|
-
completedCheckpoints: current.completedCheckpoints,
|
|
502
|
-
});
|
|
500
|
+
if (current === undefined) {
|
|
501
|
+
await this.suppressWorkflow(info.workflowId);
|
|
502
|
+
return { kind: "malformed" };
|
|
503
503
|
}
|
|
504
|
+
const pendingPrompts = this.promptReservations.hydrate(
|
|
505
|
+
info.workflowId,
|
|
506
|
+
metadata.metadata.pendingPrompts,
|
|
507
|
+
records,
|
|
508
|
+
metadata.metadata.promptReservationEpoch,
|
|
509
|
+
);
|
|
510
|
+
// Re-register instead of setWorkflowStatus: hydration is a read-side
|
|
511
|
+
// reconstruction and must preserve the authoritative updatedAt, which
|
|
512
|
+
// doubles as the cross-session liveness heartbeat for running handles.
|
|
513
|
+
this.applyMetadata(info.workflowId, {
|
|
514
|
+
...metadata.metadata,
|
|
515
|
+
pendingPrompts,
|
|
516
|
+
completedCheckpoints: current.completedCheckpoints,
|
|
517
|
+
});
|
|
518
|
+
const handle = this.getLoadableWorkflow(info.workflowId);
|
|
519
|
+
if (handle !== undefined) return { kind: "current", handle };
|
|
520
|
+
await this.suppressWorkflow(info.workflowId);
|
|
521
|
+
return { kind: "malformed" };
|
|
504
522
|
}
|
|
505
523
|
|
|
506
524
|
private async suppressWorkflow(workflowId: string): Promise<void> {
|
|
@@ -477,7 +477,8 @@ function isModelUsage(value: WorkflowSerializableValue): boolean {
|
|
|
477
477
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
478
478
|
const usage = value as Record<string, WorkflowSerializableValue>;
|
|
479
479
|
return ["input", "output", "cacheRead", "cacheWrite", "cost", "turns"].every(
|
|
480
|
-
(key) =>
|
|
480
|
+
(key) =>
|
|
481
|
+
usage[key] === undefined || (typeof usage[key] === "number" && Number.isFinite(usage[key]) && usage[key] >= 0),
|
|
481
482
|
);
|
|
482
483
|
}
|
|
483
484
|
|
|
@@ -56,16 +56,39 @@ export function recordRunTimingCheckpoint(
|
|
|
56
56
|
run: RunSnapshot,
|
|
57
57
|
options?: { readonly debounce?: boolean; readonly now?: number },
|
|
58
58
|
): boolean {
|
|
59
|
+
const checkpoint = runTimingCheckpoint(backend, run, options);
|
|
60
|
+
if (checkpoint === undefined) return false;
|
|
61
|
+
backend.recordCheckpoint(checkpoint);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Await the timing write so an active turn observes persistent storage faults at once. */
|
|
66
|
+
export async function recordRunTimingCheckpointAsync(
|
|
67
|
+
backend: DurableWorkflowBackend,
|
|
68
|
+
run: RunSnapshot,
|
|
69
|
+
options?: { readonly debounce?: boolean; readonly now?: number },
|
|
70
|
+
): Promise<boolean> {
|
|
71
|
+
const checkpoint = runTimingCheckpoint(backend, run, options);
|
|
72
|
+
if (checkpoint === undefined) return false;
|
|
73
|
+
await backend.recordCheckpointAsync(checkpoint);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runTimingCheckpoint(
|
|
78
|
+
backend: DurableWorkflowBackend,
|
|
79
|
+
run: RunSnapshot,
|
|
80
|
+
options?: { readonly debounce?: boolean; readonly now?: number },
|
|
81
|
+
): DurableToolCheckpoint | undefined {
|
|
59
82
|
const now = options?.now ?? Date.now();
|
|
60
83
|
const elapsedMs = elapsedRunMs(run, now);
|
|
61
|
-
if (elapsedMs <= 0) return
|
|
62
|
-
if (backend.listCheckpoints(run.id).length === 0) return
|
|
84
|
+
if (elapsedMs <= 0) return undefined;
|
|
85
|
+
if (backend.listCheckpoints(run.id).length === 0) return undefined;
|
|
63
86
|
const recorded = priorRunElapsedMs(backend, run.id);
|
|
64
87
|
if (recorded !== undefined) {
|
|
65
|
-
if (elapsedMs <= recorded) return
|
|
66
|
-
if (options?.debounce === true && timingBucket(elapsedMs) === timingBucket(recorded)) return
|
|
88
|
+
if (elapsedMs <= recorded) return undefined;
|
|
89
|
+
if (options?.debounce === true && timingBucket(elapsedMs) === timingBucket(recorded)) return undefined;
|
|
67
90
|
}
|
|
68
|
-
|
|
91
|
+
return {
|
|
69
92
|
kind: "tool",
|
|
70
93
|
workflowId: run.id,
|
|
71
94
|
checkpointId: `run-timing:${elapsedMs}`,
|
|
@@ -74,8 +97,6 @@ export function recordRunTimingCheckpoint(
|
|
|
74
97
|
output: { elapsedMs },
|
|
75
98
|
completedAt: now,
|
|
76
99
|
};
|
|
77
|
-
backend.recordCheckpoint(checkpoint);
|
|
78
|
-
return true;
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
/**
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
DurableInactiveDeleteResult,
|
|
25
25
|
DurableWorkflowBackend,
|
|
26
26
|
DurableWorkflowCatalogEntries,
|
|
27
|
+
DurableWorkflowHydrationResult,
|
|
27
28
|
WorkflowRegistrationInput,
|
|
28
29
|
} from "./backend.js";
|
|
29
30
|
import {
|
|
@@ -210,6 +211,9 @@ export class ScopedDurableBackend implements DurableWorkflowBackend {
|
|
|
210
211
|
hydrateWorkflow(_workflowId: string): Promise<void> {
|
|
211
212
|
return Promise.resolve();
|
|
212
213
|
}
|
|
214
|
+
hydrateWorkflowForInspection(_workflowId: string): Promise<DurableWorkflowHydrationResult> {
|
|
215
|
+
return Promise.resolve({ kind: "absent" });
|
|
216
|
+
}
|
|
213
217
|
|
|
214
218
|
hydrateResumableWorkflows(): Promise<void> {
|
|
215
219
|
return Promise.resolve();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { inspectRun, type RunDetail } from "../runs/background/run-inspect.js";
|
|
2
|
+
import { createStore, type Store } from "../shared/store.js";
|
|
3
|
+
import type { RunSnapshot } from "../shared/store-types.js";
|
|
4
|
+
import type { DurableWorkflowBackend } from "./backend.js";
|
|
5
|
+
import { durableWorkflowRunSnapshots } from "./completed-catalog.js";
|
|
6
|
+
import { getAtomicExecutorId } from "./dbos-sdk-handle.js";
|
|
7
|
+
import { isForeignLiveWorkflow, isLiveRunningWorkflow } from "./resume-eligibility.js";
|
|
8
|
+
|
|
9
|
+
export type TargetedDurableInspection =
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: "found";
|
|
12
|
+
readonly detail: RunDetail;
|
|
13
|
+
readonly runs: readonly RunSnapshot[];
|
|
14
|
+
readonly store: Store;
|
|
15
|
+
}
|
|
16
|
+
| { readonly kind: "absent" | "deleted" | "malformed"; readonly message: string };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hydrate and reconstruct one exact durable root for inspection only.
|
|
20
|
+
*
|
|
21
|
+
* This path never claims ownership, changes durable status, starts execution,
|
|
22
|
+
* or restores snapshots into the current session store.
|
|
23
|
+
*/
|
|
24
|
+
export async function inspectTargetedDurableWorkflow(
|
|
25
|
+
backend: DurableWorkflowBackend,
|
|
26
|
+
workflowId: string,
|
|
27
|
+
now: number = Date.now(),
|
|
28
|
+
): Promise<TargetedDurableInspection> {
|
|
29
|
+
const hydrated = backend.hydrateWorkflowForInspection
|
|
30
|
+
? await backend.hydrateWorkflowForInspection(workflowId)
|
|
31
|
+
: await hydrateWithoutClassification(backend, workflowId);
|
|
32
|
+
if (hydrated.kind !== "current") return hydrationFailure(workflowId, hydrated.kind);
|
|
33
|
+
const handle = hydrated.handle;
|
|
34
|
+
if (handle.rootWorkflowId !== undefined && handle.rootWorkflowId !== handle.workflowId) {
|
|
35
|
+
return malformed(workflowId, "the requested id belongs to a nested workflow rather than a durable root");
|
|
36
|
+
}
|
|
37
|
+
const reconstructed = durableWorkflowRunSnapshots(backend, handle);
|
|
38
|
+
if (reconstructed.length === 0)
|
|
39
|
+
return malformed(workflowId, "durable checkpoint topology is malformed or incomplete");
|
|
40
|
+
const runs = structuredClone(reconstructed);
|
|
41
|
+
|
|
42
|
+
const inspectionStore = createStore();
|
|
43
|
+
for (const run of runs) inspectionStore.recordRunStart(structuredClone(run));
|
|
44
|
+
const inspected = inspectRun(workflowId, { store: inspectionStore });
|
|
45
|
+
if (!inspected.ok) return malformed(workflowId, "the durable checkpoint graph has no reciprocal root");
|
|
46
|
+
|
|
47
|
+
const foreignLive = isForeignLiveWorkflow(handle, getAtomicExecutorId(), now);
|
|
48
|
+
const live = isLiveRunningWorkflow(handle, now);
|
|
49
|
+
const crashed = handle.status === "running" && !live;
|
|
50
|
+
const resumeGuidance = crashed
|
|
51
|
+
? inspected.detail.resumable === true
|
|
52
|
+
? `This workflow appears to have crashed and is resumable. Resume it explicitly with /workflow resume ${workflowId}.`
|
|
53
|
+
: "This workflow appears to have crashed, but its retained state is not resumable."
|
|
54
|
+
: foreignLive
|
|
55
|
+
? "This workflow is actively running in another Atomic session. Inspect it here, but control it from its owner session."
|
|
56
|
+
: live
|
|
57
|
+
? "This workflow still has a fresh durable heartbeat. Inspect it here without starting another executor."
|
|
58
|
+
: terminalGuidance(handle.status, workflowId, inspected.detail.resumable === true);
|
|
59
|
+
return {
|
|
60
|
+
kind: "found",
|
|
61
|
+
runs,
|
|
62
|
+
store: inspectionStore,
|
|
63
|
+
detail: {
|
|
64
|
+
...inspected.detail,
|
|
65
|
+
...(crashed ? { status: "crashed" as const } : {}),
|
|
66
|
+
...(live ? { resumable: false } : {}),
|
|
67
|
+
...(foreignLive ? { ownerActiveElsewhere: true } : {}),
|
|
68
|
+
resumeGuidance,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function terminalGuidance(status: string, workflowId: string, resumable: boolean): string {
|
|
74
|
+
if (resumable)
|
|
75
|
+
return `This retained workflow is ${status} and resumable. Resume it explicitly with /workflow resume ${workflowId}.`;
|
|
76
|
+
return `This retained workflow is ${status} and available for read-only inspection.`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function hydrateWithoutClassification(
|
|
80
|
+
backend: DurableWorkflowBackend,
|
|
81
|
+
workflowId: string,
|
|
82
|
+
): Promise<import("./backend.js").DurableWorkflowHydrationResult> {
|
|
83
|
+
await backend.hydrateWorkflow(workflowId);
|
|
84
|
+
const handle = backend.getLoadableWorkflow(workflowId);
|
|
85
|
+
return handle === undefined ? { kind: "absent" } : { kind: "current", handle };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hydrationFailure(
|
|
89
|
+
workflowId: string,
|
|
90
|
+
kind: Exclude<TargetedDurableInspection["kind"], "found">,
|
|
91
|
+
): TargetedDurableInspection {
|
|
92
|
+
switch (kind) {
|
|
93
|
+
case "absent":
|
|
94
|
+
return {
|
|
95
|
+
kind,
|
|
96
|
+
message: `Run not found: ${workflowId} (not present in the current session or durable store).`,
|
|
97
|
+
};
|
|
98
|
+
case "deleted":
|
|
99
|
+
return { kind, message: `Durable workflow was deleted: ${workflowId}` };
|
|
100
|
+
case "malformed":
|
|
101
|
+
return malformed(workflowId, "current DBOS metadata or checkpoints are malformed");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function malformed(workflowId: string, reason: string): TargetedDurableInspection {
|
|
106
|
+
return {
|
|
107
|
+
kind: "malformed",
|
|
108
|
+
message: `Durable workflow ${workflowId} cannot be inspected safely: ${reason}.`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { recordRunTimingCheckpointAsync } from "../durable/run-timing.js";
|
|
2
2
|
import { type DurableStageDeps, recordStageSessionCheckpoint } from "../durable/stage-primitive.js";
|
|
3
3
|
import type { StageSessionCheckpointOptions } from "../runs/foreground/executor-types.js";
|
|
4
4
|
import type { RunSnapshot, StageSnapshot } from "../shared/store-types.js";
|
|
@@ -27,7 +27,7 @@ export function createDurableStageSessionRecorder(
|
|
|
27
27
|
if (stageRunId === input.runId) {
|
|
28
28
|
await recordStageSessionCheckpoint(input.deps, snapshot, { force: options?.forceDurable === true });
|
|
29
29
|
if (input.runSnapshot !== undefined) {
|
|
30
|
-
|
|
30
|
+
await recordRunTimingCheckpointAsync(input.deps.backend, input.runSnapshot, {
|
|
31
31
|
debounce: options?.forceDurable !== true,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
@@ -180,8 +180,25 @@ export class EngineRuntime {
|
|
|
180
180
|
return {
|
|
181
181
|
apply: () => {
|
|
182
182
|
if (!this.childRunOptions.mcp || !hasScope) return;
|
|
183
|
-
if (depth
|
|
184
|
-
|
|
183
|
+
if (depth > 0) {
|
|
184
|
+
depth += 1;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
depth = 1;
|
|
188
|
+
try {
|
|
189
|
+
this.childRunOptions.mcp.setScope(stageId, allow, deny);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
depth = 0;
|
|
192
|
+
try {
|
|
193
|
+
this.childRunOptions.mcp.clearScope(stageId);
|
|
194
|
+
} catch (cleanupError) {
|
|
195
|
+
throw new AggregateError(
|
|
196
|
+
[error, cleanupError],
|
|
197
|
+
"MCP scope setup failed and compensating cleanup failed",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
185
202
|
},
|
|
186
203
|
clear: () => {
|
|
187
204
|
if (!this.childRunOptions.mcp || !hasScope) return;
|
|
@@ -159,6 +159,9 @@ export function createWorkflowExtensionRuntimeState(
|
|
|
159
159
|
resumeDurableWorkflow(workflowId, options) {
|
|
160
160
|
return runtimeRef.current.resumeDurableWorkflow(workflowId, options);
|
|
161
161
|
},
|
|
162
|
+
inspectDurableWorkflow(workflowId) {
|
|
163
|
+
return runtimeRef.current.inspectDurableWorkflow(workflowId);
|
|
164
|
+
},
|
|
162
165
|
listDurableResumable() {
|
|
163
166
|
return runtimeRef.current.listDurableResumable();
|
|
164
167
|
},
|