@bastani/atomic 0.9.8 → 0.9.9-alpha.1
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 +28 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +2 -0
- package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +7 -1
- package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +2 -0
- package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +8 -2
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +2 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +2 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +6 -0
- package/dist/builtin/subagents/src/shared/types-config.ts +4 -0
- package/dist/builtin/subagents/src/shared/types-depth.ts +29 -2
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +22 -0
- package/dist/builtin/workflows/README.md +17 -8
- package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -2
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +26 -9
- package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +1 -1
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +8 -3
- package/dist/builtin/workflows/builtin/ralph-runner.ts +2 -0
- package/dist/builtin/workflows/builtin/shared-prompts.ts +24 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +53 -15
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +245 -0
- package/dist/builtin/workflows/src/durable/completed-inspection.ts +213 -0
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +120 -170
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +84 -8
- package/dist/builtin/workflows/src/durable/dbos-metadata.ts +98 -0
- package/dist/builtin/workflows/src/durable/dbos-tombstone.ts +27 -0
- package/dist/builtin/workflows/src/durable/factory.ts +1 -1
- package/dist/builtin/workflows/src/durable/file-backend.ts +247 -241
- package/dist/builtin/workflows/src/durable/file-lock.ts +153 -0
- package/dist/builtin/workflows/src/durable/file-state.ts +104 -0
- package/dist/builtin/workflows/src/durable/format-version.ts +12 -0
- package/dist/builtin/workflows/src/durable/index.ts +12 -0
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +16 -27
- package/dist/builtin/workflows/src/durable/resume-eligibility.ts +20 -0
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +29 -29
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +20 -3
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +29 -8
- package/dist/builtin/workflows/src/durable/types.ts +3 -0
- package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
- package/dist/builtin/workflows/src/engine/run.ts +14 -14
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +39 -9
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +10 -0
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +133 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +15 -40
- package/dist/builtin/workflows/src/extension/wiring.ts +9 -6
- package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +226 -0
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +66 -112
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +5 -5
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +16 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +50 -43
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-output.ts +91 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-direct.ts +95 -46
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +3 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-task-prompts.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +3 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +36 -7
- package/dist/builtin/workflows/src/runs/shared/worktree-cache-lifecycle.ts +25 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-cwd.ts +103 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-generation.ts +57 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +134 -8
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -0
- package/dist/builtin/workflows/src/shared/resumable-workflow-notices.ts +43 -0
- package/dist/builtin/workflows/src/shared/timing.ts +4 -0
- package/dist/builtin/workflows/src/shared/types.ts +5 -3
- package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -0
- package/dist/builtin/workflows/src/tui/graph-view-input.ts +24 -14
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +43 -13
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +5 -7
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +6 -34
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +95 -233
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-message-queue.d.ts +0 -4
- package/dist/core/agent-session-message-queue.d.ts.map +1 -1
- package/dist/core/agent-session-message-queue.js +1 -5
- package/dist/core/agent-session-message-queue.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +5 -6
- 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-types.d.ts +2 -12
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction-boundary.d.ts +9 -0
- package/dist/core/compaction/compaction-boundary.d.ts.map +1 -0
- package/dist/core/compaction/compaction-boundary.js +115 -0
- package/dist/core/compaction/compaction-boundary.js.map +1 -0
- package/dist/core/compaction/compaction-parameters.d.ts +5 -0
- package/dist/core/compaction/compaction-parameters.d.ts.map +1 -0
- package/dist/core/compaction/compaction-parameters.js +27 -0
- package/dist/core/compaction/compaction-parameters.js.map +1 -0
- package/dist/core/compaction/compaction-runner.d.ts +14 -0
- package/dist/core/compaction/compaction-runner.d.ts.map +1 -0
- package/dist/core/compaction/compaction-runner.js +29 -0
- package/dist/core/compaction/compaction-runner.js.map +1 -0
- package/dist/core/compaction/compaction-types.d.ts +79 -0
- package/dist/core/compaction/compaction-types.d.ts.map +1 -0
- package/dist/core/compaction/compaction-types.js +6 -0
- package/dist/core/compaction/compaction-types.js.map +1 -0
- package/dist/core/compaction/deleted-ranges.d.ts +6 -0
- package/dist/core/compaction/deleted-ranges.d.ts.map +1 -0
- package/dist/core/compaction/deleted-ranges.js +134 -0
- package/dist/core/compaction/deleted-ranges.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -1
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +7 -1
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/range-planner.d.ts +21 -0
- package/dist/core/compaction/range-planner.d.ts.map +1 -0
- package/dist/core/compaction/range-planner.js +145 -0
- package/dist/core/compaction/range-planner.js.map +1 -0
- package/dist/core/compaction/transcript-serialization.d.ts +11 -0
- package/dist/core/compaction/transcript-serialization.d.ts.map +1 -0
- package/dist/core/compaction/transcript-serialization.js +104 -0
- package/dist/core/compaction/transcript-serialization.js.map +1 -0
- package/dist/core/extensions/context-types.d.ts +2 -2
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/event-results.d.ts +2 -2
- package/dist/core/extensions/event-results.d.ts.map +1 -1
- package/dist/core/extensions/event-results.js.map +1 -1
- package/dist/core/extensions/session-events.d.ts +7 -7
- package/dist/core/extensions/session-events.d.ts.map +1 -1
- package/dist/core/extensions/session-events.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +4 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +11 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/provider-context-usage.d.ts.map +1 -1
- package/dist/core/provider-context-usage.js +1 -2
- package/dist/core/provider-context-usage.js.map +1 -1
- package/dist/core/session-manager-archive.d.ts +2 -1
- package/dist/core/session-manager-archive.d.ts.map +1 -1
- package/dist/core/session-manager-archive.js +2 -2
- package/dist/core/session-manager-archive.js.map +1 -1
- package/dist/core/session-manager-classification.d.ts +9 -0
- package/dist/core/session-manager-classification.d.ts.map +1 -0
- package/dist/core/session-manager-classification.js +37 -0
- package/dist/core/session-manager-classification.js.map +1 -0
- package/dist/core/session-manager-core.d.ts +4 -4
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +13 -8
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +4 -3
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +12 -10
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-history.d.ts +6 -23
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +23 -256
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +3 -3
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +1 -1
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +3 -2
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/session-manager-types.d.ts +14 -15
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main-session.d.ts +1 -0
- package/dist/main-session.d.ts.map +1 -1
- package/dist/main-session.js +7 -0
- package/dist/main-session.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +4 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +12 -22
- package/dist/modes/interactive/components/chat-message-renderer.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 +5 -11
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js +3 -0
- package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.d.ts +1 -0
- package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.js +1 -0
- package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.js +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +1 -0
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts +20 -0
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-boundary-message.js +48 -0
- package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -0
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -1
- 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 +3 -0
- package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +3 -47
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +2 -2
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +5 -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-render-chat.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +21 -12
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -5
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +1 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.js +0 -4
- package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +2 -11
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/compaction.md +97 -772
- package/docs/extensions.md +25 -29
- package/docs/json.md +2 -2
- package/docs/rpc.md +32 -24
- package/docs/sdk.md +3 -3
- package/docs/session-format.md +49 -30
- package/docs/sessions.md +5 -3
- package/docs/settings.md +14 -4
- package/docs/usage.md +1 -1
- package/docs/workflows.md +30 -15
- package/examples/extensions/custom-compaction.ts +14 -58
- package/examples/extensions/handoff.ts +2 -3
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
- package/dist/core/compaction/context-assistant-turns.d.ts +0 -42
- package/dist/core/compaction/context-assistant-turns.d.ts.map +0 -1
- package/dist/core/compaction/context-assistant-turns.js +0 -87
- package/dist/core/compaction/context-assistant-turns.js.map +0 -1
- package/dist/core/compaction/context-compaction-critical.d.ts +0 -15
- package/dist/core/compaction/context-compaction-critical.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-critical.js +0 -57
- package/dist/core/compaction/context-compaction-critical.js.map +0 -1
- package/dist/core/compaction/context-compaction-eviction-alternates.d.ts +0 -18
- package/dist/core/compaction/context-compaction-eviction-alternates.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-eviction-alternates.js +0 -186
- package/dist/core/compaction/context-compaction-eviction-alternates.js.map +0 -1
- package/dist/core/compaction/context-compaction-eviction.d.ts +0 -12
- package/dist/core/compaction/context-compaction-eviction.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-eviction.js +0 -222
- package/dist/core/compaction/context-compaction-eviction.js.map +0 -1
- package/dist/core/compaction/context-compaction-metrics.d.ts +0 -28
- package/dist/core/compaction/context-compaction-metrics.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-metrics.js +0 -107
- package/dist/core/compaction/context-compaction-metrics.js.map +0 -1
- package/dist/core/compaction/context-compaction-prompt.d.ts +0 -9
- package/dist/core/compaction/context-compaction-prompt.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-prompt.js +0 -163
- package/dist/core/compaction/context-compaction-prompt.js.map +0 -1
- package/dist/core/compaction/context-compaction-runner.d.ts +0 -11
- package/dist/core/compaction/context-compaction-runner.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-runner.js +0 -273
- package/dist/core/compaction/context-compaction-runner.js.map +0 -1
- package/dist/core/compaction/context-compaction-strategy.d.ts +0 -5
- package/dist/core/compaction/context-compaction-strategy.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-strategy.js +0 -27
- package/dist/core/compaction/context-compaction-strategy.js.map +0 -1
- package/dist/core/compaction/context-compaction-types.d.ts +0 -75
- package/dist/core/compaction/context-compaction-types.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-types.js +0 -6
- package/dist/core/compaction/context-compaction-types.js.map +0 -1
- package/dist/core/compaction/context-compaction.d.ts +0 -10
- package/dist/core/compaction/context-compaction.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction.js +0 -8
- package/dist/core/compaction/context-compaction.js.map +0 -1
- package/dist/core/compaction/context-deletion-application.d.ts +0 -14
- package/dist/core/compaction/context-deletion-application.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-application.js +0 -261
- package/dist/core/compaction/context-deletion-application.js.map +0 -1
- package/dist/core/compaction/context-deletion-store.d.ts +0 -78
- package/dist/core/compaction/context-deletion-store.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-store.js +0 -162
- package/dist/core/compaction/context-deletion-store.js.map +0 -1
- package/dist/core/compaction/context-deletion-targets.d.ts +0 -43
- package/dist/core/compaction/context-deletion-targets.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-targets.js +0 -292
- package/dist/core/compaction/context-deletion-targets.js.map +0 -1
- package/dist/core/compaction/context-deletion-tool-definitions.d.ts +0 -193
- package/dist/core/compaction/context-deletion-tool-definitions.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-tool-definitions.js +0 -98
- package/dist/core/compaction/context-deletion-tool-definitions.js.map +0 -1
- package/dist/core/compaction/context-deletion-tool-helpers.d.ts +0 -19
- package/dist/core/compaction/context-deletion-tool-helpers.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-tool-helpers.js +0 -210
- package/dist/core/compaction/context-deletion-tool-helpers.js.map +0 -1
- package/dist/core/compaction/context-deletion-tools.d.ts +0 -4
- package/dist/core/compaction/context-deletion-tools.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-tools.js +0 -403
- package/dist/core/compaction/context-deletion-tools.js.map +0 -1
- package/dist/core/compaction/context-transcript-analysis.d.ts +0 -11
- package/dist/core/compaction/context-transcript-analysis.d.ts.map +0 -1
- package/dist/core/compaction/context-transcript-analysis.js +0 -228
- package/dist/core/compaction/context-transcript-analysis.js.map +0 -1
- package/dist/core/session-manager-tool-dependencies.d.ts +0 -10
- package/dist/core/session-manager-tool-dependencies.d.ts.map +0 -1
- package/dist/core/session-manager-tool-dependencies.js +0 -133
- package/dist/core/session-manager-tool-dependencies.js.map +0 -1
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts +0 -17
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/context-compaction-summary-message.js +0 -83
- package/dist/modes/interactive/components/context-compaction-summary-message.js.map +0 -1
|
@@ -11,76 +11,159 @@
|
|
|
11
11
|
* cross-ref: issue #1498 — durable fallback when DBOS/Postgres is unavailable.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
chmodSync,
|
|
16
|
-
existsSync,
|
|
17
|
-
mkdirSync,
|
|
18
|
-
readFileSync,
|
|
19
|
-
readdirSync,
|
|
20
|
-
renameSync,
|
|
21
|
-
rmSync,
|
|
22
|
-
statSync,
|
|
23
|
-
writeFileSync,
|
|
24
|
-
} from "node:fs";
|
|
25
|
-
import { hostname } from "node:os";
|
|
26
|
-
import { dirname } from "node:path";
|
|
14
|
+
import { readdirSync, rmSync } from "node:fs";
|
|
27
15
|
import type { DurableCheckpoint, DurableWorkflowHandle, DurableWorkflowStatus } from "./types.js";
|
|
28
16
|
import { InMemoryDurableBackend, type DurableWorkflowBackend } from "./backend.js";
|
|
17
|
+
import { DURABLE_FORMAT_VERSION } from "./format-version.js";
|
|
18
|
+
import { readDurableFileState, type FileDurableRecord, type FileDurableState } from "./file-state.js";
|
|
19
|
+
import { withDurableFileLock, writeDurableFileState } from "./file-lock.js";
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
readonly handle: DurableWorkflowHandle;
|
|
32
|
-
readonly checkpoints: readonly DurableCheckpoint[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface FileDurableState {
|
|
36
|
-
readonly version: number;
|
|
37
|
-
readonly workflows: readonly FileDurableRecord[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface LockOwner {
|
|
41
|
-
readonly pid: number;
|
|
42
|
-
readonly host: string;
|
|
43
|
-
readonly token: string;
|
|
44
|
-
readonly acquiredAt: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const FILE_FORMAT_VERSION = 1;
|
|
48
|
-
const LOCK_OWNER_FILE = "owner.json";
|
|
49
|
-
const STALE_LOCK_MS = 30_000;
|
|
21
|
+
const FILE_FORMAT_VERSION = DURABLE_FORMAT_VERSION;
|
|
50
22
|
|
|
51
23
|
export class FileDurableBackend implements DurableWorkflowBackend {
|
|
52
24
|
public readonly persistent = true;
|
|
53
25
|
private readonly mem = new InMemoryDurableBackend();
|
|
54
26
|
private readonly filePath: string;
|
|
27
|
+
private readonly expectedWorkflowId?: string;
|
|
55
28
|
private loaded = false;
|
|
56
|
-
|
|
57
|
-
|
|
29
|
+
private unknownState = false;
|
|
30
|
+
private suppressedAll = false;
|
|
31
|
+
private readonly deletedWorkflowIds = new Set<string>();
|
|
32
|
+
private readonly revivedWorkflowIds = new Set<string>();
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
filePath: string,
|
|
36
|
+
expectedWorkflowId?: string,
|
|
37
|
+
private readonly writeState: typeof writeDurableFileState = writeDurableFileState,
|
|
38
|
+
) {
|
|
58
39
|
this.filePath = filePath;
|
|
40
|
+
this.expectedWorkflowId = expectedWorkflowId;
|
|
59
41
|
}
|
|
60
|
-
|
|
61
42
|
private ensureLoaded(): void {
|
|
62
43
|
if (this.loaded) return;
|
|
44
|
+
let result = readDurableFileState(this.filePath);
|
|
45
|
+
if (result.kind === "legacy" && this.expectedWorkflowId !== undefined
|
|
46
|
+
&& (result.workflowIds.length === 0 || result.workflowIds.some((id) => id !== this.expectedWorkflowId))) {
|
|
47
|
+
this.unknownState = true;
|
|
48
|
+
this.suppressedAll = true;
|
|
49
|
+
this.loaded = true;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (result.kind === "legacy") {
|
|
53
|
+
this.replaceLegacyState();
|
|
54
|
+
result = readDurableFileState(this.filePath);
|
|
55
|
+
}
|
|
56
|
+
if (result.kind === "unknown" || (result.kind === "current" && !this.matchesExpectedId(result.state))) {
|
|
57
|
+
this.unknownState = true;
|
|
58
|
+
this.suppressedAll = true;
|
|
59
|
+
this.loaded = true;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (result.kind === "current") {
|
|
63
|
+
result.state.deletedWorkflowIds.forEach((id) => this.deletedWorkflowIds.add(id));
|
|
64
|
+
this.mem.importAll(result.state.workflows.filter((record) => !this.deletedWorkflowIds.has(record.handle.workflowId)));
|
|
65
|
+
}
|
|
63
66
|
this.loaded = true;
|
|
64
|
-
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private replaceLegacyState(): void {
|
|
70
|
+
withDurableFileLock(this.filePath, () => {
|
|
71
|
+
const latest = readDurableFileState(this.filePath);
|
|
72
|
+
if (latest.kind !== "legacy") return;
|
|
73
|
+
const ids = this.expectedWorkflowId === undefined
|
|
74
|
+
? latest.workflowIds
|
|
75
|
+
: [this.expectedWorkflowId];
|
|
76
|
+
if (ids.length === 0) {
|
|
77
|
+
rmSync(this.filePath, { force: true });
|
|
78
|
+
this.suppressedAll = true;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
ids.forEach((id) => this.deletedWorkflowIds.add(id));
|
|
82
|
+
this.writeState(this.filePath, emptyState(ids));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private assertWritable(): void {
|
|
87
|
+
if (this.unknownState) throw new Error(`Cannot overwrite unknown durable workflow state format: ${this.filePath}`);
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
private persist(): void {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
72
|
-
|
|
91
|
+
this.assertWritable();
|
|
92
|
+
withDurableFileLock(this.filePath, () => {
|
|
93
|
+
const result = readDurableFileState(this.filePath);
|
|
94
|
+
if (result.kind === "unknown" || (result.kind === "current" && !this.matchesExpectedId(result.state))) {
|
|
95
|
+
this.unknownState = true;
|
|
96
|
+
this.suppressedAll = true;
|
|
97
|
+
this.mem.reset();
|
|
98
|
+
throw new Error(`Cannot overwrite unknown durable workflow state format: ${this.filePath}`);
|
|
99
|
+
}
|
|
100
|
+
const stored = result.kind === "current" ? result.state.workflows : [];
|
|
101
|
+
const deleted = new Set(
|
|
102
|
+
result.kind === "current" ? result.state.deletedWorkflowIds
|
|
103
|
+
: result.kind === "legacy" ? result.workflowIds
|
|
104
|
+
: [],
|
|
105
|
+
);
|
|
106
|
+
this.deletedWorkflowIds.forEach((id) => deleted.add(id));
|
|
107
|
+
this.revivedWorkflowIds.forEach((id) => deleted.delete(id));
|
|
108
|
+
const merged = mergeRecords(stored, this.mem.exportAll()).filter((record) => !deleted.has(record.handle.workflowId));
|
|
109
|
+
const state = currentState(merged, deleted);
|
|
110
|
+
this.writeState(this.filePath, state);
|
|
111
|
+
this.replaceMirror(state);
|
|
112
|
+
this.revivedWorkflowIds.clear();
|
|
73
113
|
});
|
|
74
114
|
}
|
|
115
|
+
private refreshCompatibilityFromDisk(): void {
|
|
116
|
+
const result = readDurableFileState(this.filePath);
|
|
117
|
+
if (result.kind === "current" && this.matchesExpectedId(result.state)) {
|
|
118
|
+
this.unknownState = false;
|
|
119
|
+
this.suppressedAll = false;
|
|
120
|
+
this.replaceMirror(result.state);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (result.kind === "unknown"
|
|
124
|
+
|| (result.kind === "current" && !this.matchesExpectedId(result.state))
|
|
125
|
+
|| (result.kind === "legacy" && this.expectedWorkflowId !== undefined
|
|
126
|
+
&& (result.workflowIds.length === 0 || result.workflowIds.some((id) => id !== this.expectedWorkflowId)))) {
|
|
127
|
+
this.unknownState = true;
|
|
128
|
+
this.suppressedAll = true;
|
|
129
|
+
this.mem.reset();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (result.kind === "legacy") {
|
|
133
|
+
this.loaded = false;
|
|
134
|
+
this.mem.reset();
|
|
135
|
+
this.deletedWorkflowIds.clear();
|
|
136
|
+
this.ensureLoaded();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
private replaceMirror(state: FileDurableState): void {
|
|
142
|
+
this.mem.reset();
|
|
143
|
+
this.deletedWorkflowIds.clear();
|
|
144
|
+
state.deletedWorkflowIds.forEach((id) => this.deletedWorkflowIds.add(id));
|
|
145
|
+
this.mem.importAll(state.workflows.filter((record) => !this.deletedWorkflowIds.has(record.handle.workflowId)));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private matchesExpectedId(state: FileDurableState): boolean {
|
|
149
|
+
if (this.expectedWorkflowId === undefined) return true;
|
|
150
|
+
return state.workflows.every((record) => record.handle.workflowId === this.expectedWorkflowId)
|
|
151
|
+
&& state.deletedWorkflowIds.every((id) => id === this.expectedWorkflowId);
|
|
152
|
+
}
|
|
75
153
|
|
|
76
154
|
registerWorkflow(handle: Parameters<DurableWorkflowBackend["registerWorkflow"]>[0]): void {
|
|
77
155
|
this.ensureLoaded();
|
|
156
|
+
this.assertWritable();
|
|
157
|
+
this.suppressedAll = false;
|
|
158
|
+
this.deletedWorkflowIds.delete(handle.workflowId);
|
|
159
|
+
this.revivedWorkflowIds.add(handle.workflowId);
|
|
78
160
|
this.mem.registerWorkflow(handle);
|
|
79
161
|
this.persist();
|
|
80
162
|
}
|
|
81
163
|
|
|
82
164
|
recordCheckpoint(checkpoint: DurableCheckpoint): void {
|
|
83
165
|
this.ensureLoaded();
|
|
166
|
+
this.assertWritable();
|
|
84
167
|
this.mem.recordCheckpoint(checkpoint);
|
|
85
168
|
this.persist();
|
|
86
169
|
}
|
|
@@ -117,6 +200,7 @@ export class FileDurableBackend implements DurableWorkflowBackend {
|
|
|
117
200
|
|
|
118
201
|
setWorkflowStatus(workflowId: string, status: Parameters<DurableWorkflowBackend["setWorkflowStatus"]>[1], pendingPrompts?: number, resumable?: boolean): void {
|
|
119
202
|
this.ensureLoaded();
|
|
203
|
+
this.assertWritable();
|
|
120
204
|
this.mem.setWorkflowStatus(workflowId, status, pendingPrompts, resumable);
|
|
121
205
|
this.persist();
|
|
122
206
|
}
|
|
@@ -126,66 +210,81 @@ export class FileDurableBackend implements DurableWorkflowBackend {
|
|
|
126
210
|
return this.mem.listResumableWorkflows();
|
|
127
211
|
}
|
|
128
212
|
|
|
213
|
+
listCompletedWorkflows() {
|
|
214
|
+
this.ensureLoaded();
|
|
215
|
+
return this.mem.listCompletedWorkflows();
|
|
216
|
+
}
|
|
217
|
+
|
|
129
218
|
toCacheEntry(workflowId: string) {
|
|
130
219
|
this.ensureLoaded();
|
|
131
220
|
return this.mem.toCacheEntry(workflowId);
|
|
132
221
|
}
|
|
222
|
+
async deleteWorkflow(workflowId: string): Promise<void> {
|
|
223
|
+
this.ensureLoaded();
|
|
224
|
+
this.assertWritable();
|
|
225
|
+
withDurableFileLock(this.filePath, () => {
|
|
226
|
+
const result = readDurableFileState(this.filePath);
|
|
227
|
+
if (result.kind === "unknown" || (result.kind === "current" && !this.matchesExpectedId(result.state))) {
|
|
228
|
+
throw new Error(`Cannot overwrite unknown durable workflow state format: ${this.filePath}`);
|
|
229
|
+
}
|
|
230
|
+
const stored = result.kind === "current" ? result.state.workflows : [];
|
|
231
|
+
const deleted = new Set(
|
|
232
|
+
result.kind === "current" ? result.state.deletedWorkflowIds
|
|
233
|
+
: result.kind === "legacy" ? result.workflowIds
|
|
234
|
+
: [],
|
|
235
|
+
);
|
|
236
|
+
deleted.add(workflowId);
|
|
237
|
+
const merged = mergeRecords(stored, this.mem.exportAll())
|
|
238
|
+
.filter((record) => !deleted.has(record.handle.workflowId));
|
|
239
|
+
const state = currentState(merged, deleted);
|
|
240
|
+
this.writeState(this.filePath, state);
|
|
241
|
+
this.replaceMirror(state);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
isWorkflowLoadable(workflowId: string): boolean {
|
|
246
|
+
this.ensureLoaded();
|
|
247
|
+
this.refreshCompatibilityFromDisk();
|
|
248
|
+
return !this.suppressedAll && !this.deletedWorkflowIds.has(workflowId);
|
|
249
|
+
}
|
|
133
250
|
|
|
134
251
|
reset(): void {
|
|
135
252
|
this.mem.reset();
|
|
136
|
-
|
|
253
|
+
this.unknownState = false;
|
|
254
|
+
this.suppressedAll = false;
|
|
255
|
+
this.deletedWorkflowIds.clear();
|
|
256
|
+
this.revivedWorkflowIds.clear();
|
|
257
|
+
withDurableFileLock(this.filePath, () => this.writeState(this.filePath, emptyState()));
|
|
137
258
|
}
|
|
138
259
|
}
|
|
139
260
|
|
|
140
|
-
/**
|
|
141
|
-
* Directory-backed durable backend that stores each root workflow in its own
|
|
142
|
-
* JSON file while preserving the same DurableWorkflowBackend interface.
|
|
143
|
-
*/
|
|
144
261
|
export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
|
|
145
262
|
public readonly persistent = true;
|
|
146
263
|
private readonly dir: string;
|
|
147
264
|
private readonly fileBackends = new Map<string, FileDurableBackend>();
|
|
265
|
+
private readonly suppressedIds = new Set<string>();
|
|
148
266
|
|
|
149
|
-
constructor(dir: string) {
|
|
150
|
-
this.dir = dir;
|
|
151
|
-
}
|
|
267
|
+
constructor(dir: string) { this.dir = dir; }
|
|
152
268
|
|
|
153
269
|
registerWorkflow(handle: Parameters<DurableWorkflowBackend["registerWorkflow"]>[0]): void {
|
|
154
270
|
this.backendFor(handle.workflowId).registerWorkflow(handle);
|
|
271
|
+
this.suppressedIds.delete(handle.workflowId);
|
|
155
272
|
}
|
|
156
273
|
|
|
157
|
-
recordCheckpoint(checkpoint: DurableCheckpoint): void {
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
getUiResponse(workflowId: string, promptHash: string) {
|
|
166
|
-
return this.backendFor(workflowId).getUiResponse(workflowId, promptHash);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getStageOutput(workflowId: string, replayKey: string) {
|
|
170
|
-
return this.backendFor(workflowId).getStageOutput(workflowId, replayKey);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
getStageSession(workflowId: string, replayKey: string) {
|
|
174
|
-
return this.backendFor(workflowId).getStageSession(workflowId, replayKey);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
listCheckpoints(workflowId: string): readonly DurableCheckpoint[] {
|
|
178
|
-
return this.backendFor(workflowId).listCheckpoints(workflowId);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
getWorkflow(workflowId: string) {
|
|
182
|
-
return this.backendFor(workflowId).getWorkflow(workflowId);
|
|
183
|
-
}
|
|
274
|
+
recordCheckpoint(checkpoint: DurableCheckpoint): void { this.backendFor(checkpoint.workflowId).recordCheckpoint(checkpoint); }
|
|
275
|
+
getToolOutput(workflowId: string, argsHash: string) { return this.backendFor(workflowId).getToolOutput(workflowId, argsHash); }
|
|
276
|
+
getUiResponse(workflowId: string, promptHash: string) { return this.backendFor(workflowId).getUiResponse(workflowId, promptHash); }
|
|
277
|
+
getStageOutput(workflowId: string, replayKey: string) { return this.backendFor(workflowId).getStageOutput(workflowId, replayKey); }
|
|
278
|
+
getStageSession(workflowId: string, replayKey: string) { return this.backendFor(workflowId).getStageSession(workflowId, replayKey); }
|
|
279
|
+
listCheckpoints(workflowId: string): readonly DurableCheckpoint[] { return this.backendFor(workflowId).listCheckpoints(workflowId); }
|
|
280
|
+
getWorkflow(workflowId: string) { return this.backendFor(workflowId).getWorkflow(workflowId); }
|
|
184
281
|
|
|
185
282
|
setWorkflowStatus(workflowId: string, status: DurableWorkflowStatus, pendingPrompts?: number, resumable?: boolean): void {
|
|
186
283
|
const backend = this.backendFor(workflowId);
|
|
187
284
|
backend.setWorkflowStatus(workflowId, status, pendingPrompts, resumable);
|
|
188
|
-
if (isPrunableTerminalStatus(status, resumable)
|
|
285
|
+
if (isPrunableTerminalStatus(status, resumable)
|
|
286
|
+
&& backend.isWorkflowLoadable(workflowId)
|
|
287
|
+
&& backend.getWorkflow(workflowId) !== undefined) this.removeWorkflowFile(workflowId);
|
|
189
288
|
}
|
|
190
289
|
|
|
191
290
|
listResumableWorkflows() {
|
|
@@ -194,24 +293,49 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
|
|
|
194
293
|
return mem.listResumableWorkflows();
|
|
195
294
|
}
|
|
196
295
|
|
|
197
|
-
|
|
198
|
-
|
|
296
|
+
listCompletedWorkflows() {
|
|
297
|
+
const mem = new InMemoryDurableBackend();
|
|
298
|
+
mem.importAll(mergeRecords([], this.readAllRecords()));
|
|
299
|
+
return mem.listCompletedWorkflows();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
toCacheEntry(workflowId: string) { return this.backendFor(workflowId).toCacheEntry(workflowId); }
|
|
303
|
+
|
|
304
|
+
async deleteWorkflow(workflowId: string): Promise<void> {
|
|
305
|
+
await this.backendFor(workflowId).deleteWorkflow(workflowId);
|
|
306
|
+
this.suppressedIds.add(workflowId);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
isWorkflowLoadable(workflowId: string): boolean {
|
|
310
|
+
const filePath = durableStateFileFor(this.dir, workflowId);
|
|
311
|
+
const ownState = readDurableFileState(filePath);
|
|
312
|
+
if (ownState.kind === "current" && stateMatchesWorkflowId(ownState.state, workflowId)) {
|
|
313
|
+
const loadable = this.backendForFile(filePath, workflowId).isWorkflowLoadable(workflowId);
|
|
314
|
+
if (loadable) this.suppressedIds.delete(workflowId);
|
|
315
|
+
else this.suppressedIds.add(workflowId);
|
|
316
|
+
return loadable;
|
|
317
|
+
}
|
|
318
|
+
if (this.suppressedIds.has(workflowId)) return false;
|
|
319
|
+
const loadable = this.backendForFile(filePath, workflowId).isWorkflowLoadable(workflowId);
|
|
320
|
+
if (!loadable) this.suppressedIds.add(workflowId);
|
|
321
|
+
return loadable;
|
|
199
322
|
}
|
|
200
323
|
|
|
201
324
|
reset(): void {
|
|
202
325
|
this.fileBackends.clear();
|
|
326
|
+
this.suppressedIds.clear();
|
|
203
327
|
for (const filePath of this.stateFiles()) this.removeStateFile(filePath);
|
|
204
328
|
for (const lockPath of this.lockDirs()) rmSync(lockPath, { recursive: true, force: true });
|
|
205
329
|
}
|
|
206
330
|
|
|
207
331
|
private backendFor(workflowId: string): FileDurableBackend {
|
|
208
|
-
return this.backendForFile(durableStateFileFor(this.dir, workflowId));
|
|
332
|
+
return this.backendForFile(durableStateFileFor(this.dir, workflowId), workflowId);
|
|
209
333
|
}
|
|
210
334
|
|
|
211
|
-
private backendForFile(filePath: string): FileDurableBackend {
|
|
335
|
+
private backendForFile(filePath: string, expectedWorkflowId: string): FileDurableBackend {
|
|
212
336
|
const existing = this.fileBackends.get(filePath);
|
|
213
337
|
if (existing !== undefined) return existing;
|
|
214
|
-
const backend = new FileDurableBackend(filePath);
|
|
338
|
+
const backend = new FileDurableBackend(filePath, expectedWorkflowId);
|
|
215
339
|
this.fileBackends.set(filePath, backend);
|
|
216
340
|
return backend;
|
|
217
341
|
}
|
|
@@ -221,9 +345,7 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
|
|
|
221
345
|
return readdirSync(this.dir, { withFileTypes: true })
|
|
222
346
|
.filter((entry) => entry.isFile() && entry.name.startsWith("workflow-") && entry.name.endsWith(".json"))
|
|
223
347
|
.map((entry) => `${this.dir}/${entry.name}`);
|
|
224
|
-
} catch {
|
|
225
|
-
return [];
|
|
226
|
-
}
|
|
348
|
+
} catch { return []; }
|
|
227
349
|
}
|
|
228
350
|
|
|
229
351
|
private lockDirs(): readonly string[] {
|
|
@@ -231,18 +353,37 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
|
|
|
231
353
|
return readdirSync(this.dir, { withFileTypes: true })
|
|
232
354
|
.filter((entry) => entry.isDirectory() && entry.name.startsWith("workflow-") && entry.name.endsWith(".json.lock"))
|
|
233
355
|
.map((entry) => `${this.dir}/${entry.name}`);
|
|
234
|
-
} catch {
|
|
235
|
-
return [];
|
|
236
|
-
}
|
|
356
|
+
} catch { return []; }
|
|
237
357
|
}
|
|
238
358
|
|
|
239
359
|
private readAllRecords(): readonly FileDurableRecord[] {
|
|
240
|
-
return this.stateFiles().flatMap((filePath) =>
|
|
360
|
+
return this.stateFiles().flatMap((filePath) => {
|
|
361
|
+
const workflowId = workflowIdFromStateFile(this.dir, filePath);
|
|
362
|
+
if (workflowId === undefined) return [];
|
|
363
|
+
const result = readDurableFileState(filePath);
|
|
364
|
+
const embeddedIds = result.kind === "current"
|
|
365
|
+
? [...result.state.workflows.map((record) => record.handle.workflowId), ...result.state.deletedWorkflowIds]
|
|
366
|
+
: result.kind === "legacy" ? result.workflowIds : [];
|
|
367
|
+
const mismatched = embeddedIds.filter((id) => id !== workflowId);
|
|
368
|
+
if (mismatched.length > 0) {
|
|
369
|
+
this.suppressedIds.add(workflowId);
|
|
370
|
+
mismatched.forEach((id) => this.suppressedIds.add(id));
|
|
371
|
+
this.backendForFile(filePath, workflowId).isWorkflowLoadable(workflowId);
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
const backend = this.backendForFile(filePath, workflowId);
|
|
375
|
+
if (!backend.isWorkflowLoadable(workflowId)) {
|
|
376
|
+
this.suppressedIds.add(workflowId);
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
this.suppressedIds.delete(workflowId);
|
|
380
|
+
const current = readDurableFileState(filePath);
|
|
381
|
+
if (current.kind !== "current" || !stateMatchesWorkflowId(current.state, workflowId)) return [];
|
|
382
|
+
return current.state.workflows.filter((record) => !current.state.deletedWorkflowIds.includes(record.handle.workflowId));
|
|
383
|
+
});
|
|
241
384
|
}
|
|
242
385
|
|
|
243
|
-
private removeWorkflowFile(workflowId: string): void {
|
|
244
|
-
this.removeStateFile(durableStateFileFor(this.dir, workflowId));
|
|
245
|
-
}
|
|
386
|
+
private removeWorkflowFile(workflowId: string): void { this.removeStateFile(durableStateFileFor(this.dir, workflowId)); }
|
|
246
387
|
|
|
247
388
|
private removeStateFile(filePath: string): void {
|
|
248
389
|
this.fileBackends.delete(filePath);
|
|
@@ -251,163 +392,28 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
|
|
|
251
392
|
}
|
|
252
393
|
}
|
|
253
394
|
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
} catch {
|
|
260
|
-
return { version: FILE_FORMAT_VERSION, workflows: [] };
|
|
261
|
-
}
|
|
395
|
+
function workflowIdFromStateFile(dir: string, filePath: string): string | undefined {
|
|
396
|
+
const prefix = `${dir}/workflow-`;
|
|
397
|
+
if (!filePath.startsWith(prefix) || !filePath.endsWith(".json")) return undefined;
|
|
398
|
+
try { return decodeURIComponent(filePath.slice(prefix.length, -".json".length)); }
|
|
399
|
+
catch { return undefined; }
|
|
262
400
|
}
|
|
263
401
|
|
|
264
|
-
function writeState(filePath: string, state: FileDurableState): void {
|
|
265
|
-
const dir = dirname(filePath);
|
|
266
|
-
ensureSecureDir(dir);
|
|
267
|
-
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
268
|
-
writeFileSync(tmp, JSON.stringify(state), { encoding: "utf-8", mode: 0o600 });
|
|
269
|
-
chmodBestEffort(tmp, 0o600);
|
|
270
|
-
renameSync(tmp, filePath);
|
|
271
|
-
chmodBestEffort(filePath, 0o600);
|
|
272
|
-
}
|
|
273
402
|
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
ensureSecureDir(dir);
|
|
277
|
-
const lockDir = `${filePath}.lock`;
|
|
278
|
-
const deadline = Date.now() + 5000;
|
|
279
|
-
while (true) {
|
|
280
|
-
try {
|
|
281
|
-
mkdirSync(lockDir, { mode: 0o700 });
|
|
282
|
-
try {
|
|
283
|
-
chmodBestEffort(lockDir, 0o700);
|
|
284
|
-
writeLockOwner(lockDir);
|
|
285
|
-
} catch (ownerErr) {
|
|
286
|
-
rmSync(lockDir, { recursive: true, force: true });
|
|
287
|
-
throw ownerErr;
|
|
288
|
-
}
|
|
289
|
-
break;
|
|
290
|
-
} catch (err) {
|
|
291
|
-
if (errorCode(err) !== "EEXIST") throw err;
|
|
292
|
-
if (tryReclaimStaleLock(lockDir, STALE_LOCK_MS)) continue;
|
|
293
|
-
if (Date.now() > deadline) throw new Error(`Timed out acquiring durable workflow state lock: ${lockDir}`);
|
|
294
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
try {
|
|
298
|
-
return fn();
|
|
299
|
-
} finally {
|
|
300
|
-
rmSync(lockDir, { recursive: true, force: true });
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function tryReclaimStaleLock(lockDir: string, staleMs: number): boolean {
|
|
305
|
-
if (!isStaleLock(lockDir, staleMs)) return false;
|
|
306
|
-
const owner = readLockOwner(lockDir);
|
|
307
|
-
if (owner === undefined || !isLockOwnerAbandoned(owner)) return false;
|
|
308
|
-
const quarantine = `${lockDir}.stale.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
309
|
-
try {
|
|
310
|
-
renameSync(lockDir, quarantine);
|
|
311
|
-
} catch {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const quarantinedOwner = readLockOwner(quarantine);
|
|
315
|
-
if (!sameLockOwner(owner, quarantinedOwner)) {
|
|
316
|
-
restoreQuarantinedLock(lockDir, quarantine);
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
rmSync(quarantine, { recursive: true, force: true });
|
|
321
|
-
} catch {
|
|
322
|
-
// The stale lock has already been moved away from the active lock path.
|
|
323
|
-
}
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function isStaleLock(lockDir: string, staleMs: number): boolean {
|
|
328
|
-
try {
|
|
329
|
-
const stat = statSync(lockDir);
|
|
330
|
-
return Date.now() - stat.mtimeMs > staleMs;
|
|
331
|
-
} catch {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function writeLockOwner(lockDir: string): void {
|
|
337
|
-
const owner: LockOwner = {
|
|
338
|
-
pid: process.pid,
|
|
339
|
-
host: hostname(),
|
|
340
|
-
token: `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
341
|
-
acquiredAt: Date.now(),
|
|
342
|
-
};
|
|
343
|
-
const ownerPath = `${lockDir}/${LOCK_OWNER_FILE}`;
|
|
344
|
-
writeFileSync(ownerPath, JSON.stringify(owner), { encoding: "utf-8", mode: 0o600 });
|
|
345
|
-
chmodBestEffort(ownerPath, 0o600);
|
|
403
|
+
function emptyState(deletedWorkflowIds: readonly string[] = []): FileDurableState {
|
|
404
|
+
return { version: FILE_FORMAT_VERSION, workflows: [], deletedWorkflowIds };
|
|
346
405
|
}
|
|
347
406
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
const parsed = JSON.parse(readFileSync(`${lockDir}/${LOCK_OWNER_FILE}`, "utf-8"));
|
|
351
|
-
if (!isLockOwner(parsed)) return undefined;
|
|
352
|
-
return parsed;
|
|
353
|
-
} catch {
|
|
354
|
-
return undefined;
|
|
355
|
-
}
|
|
407
|
+
function currentState(records: readonly FileDurableRecord[], deleted: ReadonlySet<string>): FileDurableState {
|
|
408
|
+
return { version: FILE_FORMAT_VERSION, workflows: records, deletedWorkflowIds: [...deleted] };
|
|
356
409
|
}
|
|
357
410
|
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return typeof record.pid === "number"
|
|
362
|
-
&& typeof record.host === "string"
|
|
363
|
-
&& typeof record.token === "string"
|
|
364
|
-
&& typeof record.acquiredAt === "number";
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function isLockOwnerAbandoned(owner: LockOwner): boolean {
|
|
368
|
-
if (owner.host !== hostname()) return false;
|
|
369
|
-
if (!Number.isInteger(owner.pid) || owner.pid <= 0) return false;
|
|
370
|
-
try {
|
|
371
|
-
process.kill(owner.pid, 0);
|
|
372
|
-
return false;
|
|
373
|
-
} catch (err) {
|
|
374
|
-
const code = errorCode(err);
|
|
375
|
-
return code === "ESRCH";
|
|
376
|
-
}
|
|
411
|
+
function stateMatchesWorkflowId(state: FileDurableState, workflowId: string): boolean {
|
|
412
|
+
return state.workflows.every((record) => record.handle.workflowId === workflowId)
|
|
413
|
+
&& state.deletedWorkflowIds.every((id) => id === workflowId);
|
|
377
414
|
}
|
|
378
|
-
|
|
379
|
-
function sameLockOwner(a: LockOwner, b: LockOwner | undefined): boolean {
|
|
380
|
-
return b !== undefined && a.pid === b.pid && a.host === b.host && a.token === b.token;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function restoreQuarantinedLock(lockDir: string, quarantine: string): void {
|
|
384
|
-
try {
|
|
385
|
-
if (!existsSync(lockDir)) renameSync(quarantine, lockDir);
|
|
386
|
-
else rmSync(quarantine, { recursive: true, force: true });
|
|
387
|
-
} catch {
|
|
388
|
-
// Best-effort: never delete the active lock path after a failed compare.
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function errorCode(err: unknown): string | undefined {
|
|
393
|
-
return typeof err === "object" && err !== null && "code" in err ? String(err.code) : undefined;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function ensureSecureDir(dir: string): void {
|
|
397
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
398
|
-
chmodBestEffort(dir, 0o700);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function chmodBestEffort(path: string, mode: number): void {
|
|
402
|
-
try {
|
|
403
|
-
chmodSync(path, mode);
|
|
404
|
-
} catch {
|
|
405
|
-
// chmod is unavailable or unsupported on some filesystems/platforms.
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
415
|
function isPrunableTerminalStatus(status: DurableWorkflowStatus, resumable?: boolean): boolean {
|
|
410
|
-
if (status === "
|
|
416
|
+
if (status === "cancelled") return true;
|
|
411
417
|
return (status === "failed" || status === "blocked") && resumable === false;
|
|
412
418
|
}
|
|
413
419
|
|