@bastani/atomic 0.9.18-alpha.4 → 0.9.18-alpha.6
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 +115 -0
- package/README.md +3 -3
- package/dist/builtin/intercom/CHANGELOG.md +41 -0
- package/dist/builtin/intercom/README.md +18 -3
- package/dist/builtin/intercom/broker/broker.ts +670 -127
- package/dist/builtin/intercom/broker/client.ts +149 -35
- package/dist/builtin/intercom/broker/delivered-message-cache.ts +497 -37
- package/dist/builtin/intercom/broker/framing.ts +4 -2
- package/dist/builtin/intercom/broker/paths.ts +10 -0
- package/dist/builtin/intercom/broker/pending-send-registry.ts +1 -2
- package/dist/builtin/intercom/broker/send-handler.ts +311 -52
- package/dist/builtin/intercom/broker/send-signature.ts +4 -2
- package/dist/builtin/intercom/broker/socket-writes.ts +103 -0
- package/dist/builtin/intercom/broker/spawn.ts +9 -4
- package/dist/builtin/intercom/index.bundle.mjs +1419 -213
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/intercom/recoverable-disconnect.ts +52 -0
- package/dist/builtin/intercom/retry-policy.ts +8 -0
- package/dist/builtin/intercom/skills/intercom/SKILL.md +17 -9
- package/dist/builtin/intercom/types.ts +25 -8
- package/dist/builtin/intercom/workflow-stage-path-matching.ts +90 -0
- package/dist/builtin/intercom/workflow-stage-target.ts +57 -0
- package/dist/builtin/mcp/index.bundle.mjs +427 -126
- package/dist/builtin/mcp/package.json +2 -2
- package/dist/builtin/subagents/CHANGELOG.md +19 -0
- package/dist/builtin/subagents/README.md +3 -3
- package/dist/builtin/subagents/agents/code-simplifier.md +2 -2
- package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -2
- package/dist/builtin/subagents/agents/codebase-locator.md +2 -2
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +11 -11
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -2
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -2
- package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -2
- package/dist/builtin/subagents/agents/debugger.md +3 -3
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +3 -3
- package/dist/builtin/subagents/skills/qlty/SKILL.md +29 -101
- package/dist/builtin/subagents/skills/qlty/references/manual-configuration.md +52 -0
- package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
- package/dist/builtin/subagents/src/extension/index.bundle.mjs +49 -132
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +44 -0
- package/dist/builtin/workflows/README.md +7 -7
- package/dist/builtin/workflows/builtin/adversarial-verification.js +2 -2
- package/dist/builtin/workflows/builtin/{chunk-szz1ajz9.js → chunk-42s7sw0b.js} +9 -2
- package/dist/builtin/workflows/builtin/{chunk-2dqb5s2q.js → chunk-79fjkcvh.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-n58a7v26.js → chunk-b0v2xab1.js} +181 -10
- package/dist/builtin/workflows/builtin/{chunk-fghhy2a5.js → chunk-ed4jfn5t.js} +23 -5
- package/dist/builtin/workflows/builtin/{chunk-h3r2vkzc.js → chunk-hqpe2cyx.js} +60 -21
- package/dist/builtin/workflows/builtin/{chunk-hzzn6adg.js → chunk-k3w87x8y.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-82ha8p41.js → chunk-n8h4yyew.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-qwzvgxnq.js → chunk-reqr3sr3.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-c53y8bdh.js → chunk-wgccch41.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-0x6e303p.js → chunk-zbekjqew.js} +20 -7
- package/dist/builtin/workflows/builtin/{chunk-29wrp38a.js → chunk-ze5x3d1r.js} +1 -1
- package/dist/builtin/workflows/builtin/classify-and-act.js +2 -2
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.js +2 -2
- package/dist/builtin/workflows/builtin/generate-and-filter.js +2 -2
- package/dist/builtin/workflows/builtin/goal.js +3 -3
- package/dist/builtin/workflows/builtin/index.js +11 -11
- package/dist/builtin/workflows/builtin/loop-until-done.js +2 -2
- package/dist/builtin/workflows/builtin/open-claude-design.js +2 -2
- package/dist/builtin/workflows/builtin/ralph.js +3 -3
- package/dist/builtin/workflows/builtin/steering-context.js +1 -1
- package/dist/builtin/workflows/builtin/tournament.js +2 -2
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +39 -52
- package/dist/builtin/workflows/skills/prompt-engineer/references/advanced_patterns.md +9 -37
- package/dist/builtin/workflows/skills/prompt-engineer/references/claude_fable_5.md +49 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/claude_fable_5_1.md +57 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/claude_opus_4_8.md +57 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/claude_opus_5.md +47 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/claude_sonnet_5.md +57 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_5_5.md +60 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_5_6.md +58 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_6_astra.md +58 -0
- package/dist/builtin/workflows/skills/prompt-engineer/references/quality_improvement.md +18 -4
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +3194 -737
- package/dist/builtin/workflows/src/index.js +899 -436
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +0 -10
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +4 -0
- package/dist/cli/list-models.js.map +1 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -2
- package/dist/client/index.js.map +1 -1
- package/dist/config.d.ts +0 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -45
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +8 -6
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +1 -0
- 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-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js +3 -4
- package/dist/core/agent-session-models.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-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +15 -7
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-tool-hooks.js +1 -1
- package/dist/core/agent-session-tool-hooks.js.map +1 -1
- package/dist/core/agent-session-tree.d.ts.map +1 -1
- package/dist/core/agent-session-tree.js +8 -0
- package/dist/core/agent-session-tree.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/anthropic-thinking-guard.d.ts.map +1 -1
- package/dist/core/anthropic-thinking-guard.js +71 -5
- package/dist/core/anthropic-thinking-guard.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +4 -4
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/extensions/api-types.d.ts +8 -2
- package/dist/core/extensions/api-types.d.ts.map +1 -1
- package/dist/core/extensions/api-types.js.map +1 -1
- package/dist/core/extensions/context-types.d.ts +12 -0
- 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/provider-types.d.ts +2 -2
- package/dist/core/extensions/provider-types.d.ts.map +1 -1
- package/dist/core/extensions/provider-types.js.map +1 -1
- package/dist/core/extensions/runner-shortcuts.d.ts.map +1 -1
- package/dist/core/extensions/runner-shortcuts.js +0 -1
- package/dist/core/extensions/runner-shortcuts.js.map +1 -1
- package/dist/core/fast-model-routing-transport.d.ts +12 -0
- package/dist/core/fast-model-routing-transport.d.ts.map +1 -0
- package/dist/core/{codex-fast-mode-transport.js → fast-model-routing-transport.js} +29 -29
- package/dist/core/fast-model-routing-transport.js.map +1 -0
- package/dist/core/fast-model-routing.d.ts +57 -0
- package/dist/core/fast-model-routing.d.ts.map +1 -0
- package/dist/core/fast-model-routing.js +201 -0
- package/dist/core/fast-model-routing.js.map +1 -0
- package/dist/core/fast-model-variants.d.ts +98 -0
- package/dist/core/fast-model-variants.d.ts.map +1 -0
- package/dist/core/fast-model-variants.js +148 -0
- package/dist/core/fast-model-variants.js.map +1 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +4 -2
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/keybindings.d.ts +0 -10
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +0 -8
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts +24 -4
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +6 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver-initial.js +1 -1
- package/dist/core/model-resolver-initial.js.map +1 -1
- package/dist/core/model-runtime-streaming.d.ts +14 -1
- package/dist/core/model-runtime-streaming.d.ts.map +1 -1
- package/dist/core/model-runtime-streaming.js +25 -3
- package/dist/core/model-runtime-streaming.js.map +1 -1
- package/dist/core/model-runtime.d.ts +39 -2
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +91 -7
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/provider-composer-internal.d.ts +1 -1
- package/dist/core/provider-composer-internal.d.ts.map +1 -1
- package/dist/core/provider-composer-internal.js +8 -2
- package/dist/core/provider-composer-internal.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +21 -18
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-archive.d.ts.map +1 -1
- package/dist/core/session-manager-archive.js +25 -2
- package/dist/core/session-manager-archive.js.map +1 -1
- package/dist/core/settings-manager-core.d.ts +0 -2
- package/dist/core/settings-manager-core.d.ts.map +1 -1
- package/dist/core/settings-manager-core.js +2 -9
- package/dist/core/settings-manager-core.js.map +1 -1
- package/dist/core/settings-manager-internals.d.ts +0 -1
- package/dist/core/settings-manager-internals.d.ts.map +1 -1
- package/dist/core/settings-manager-internals.js.map +1 -1
- package/dist/core/settings-manager-ui-accessors.d.ts +0 -8
- package/dist/core/settings-manager-ui-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-ui-accessors.js +0 -53
- package/dist/core/settings-manager-ui-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 +0 -5
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +4 -2
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.js +7 -7
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +10 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +153 -8
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +9 -8
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +4 -3
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/hashline.d.ts +2 -2
- package/dist/core/tools/hashline.d.ts.map +1 -1
- package/dist/core/tools/hashline.js +40 -10
- package/dist/core/tools/hashline.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +2 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +12 -11
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +20 -19
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +22 -21
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/workflow-stage-admission.d.ts +6 -0
- package/dist/core/workflow-stage-admission.d.ts.map +1 -1
- package/dist/core/workflow-stage-admission.js +14 -0
- package/dist/core/workflow-stage-admission.js.map +1 -1
- package/dist/index-extensions.d.ts +1 -1
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/atomic-working-status.d.ts +4 -0
- package/dist/modes/interactive/components/atomic-working-status.d.ts.map +1 -1
- package/dist/modes/interactive/components/atomic-working-status.js +26 -1
- package/dist/modes/interactive/components/atomic-working-status.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +7 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +43 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +0 -6
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -2
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +12 -25
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -5
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +16 -34
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.js +10 -5
- package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-submenus.d.ts +1 -1
- package/dist/modes/interactive/components/settings-selector-submenus.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-submenus.js +10 -7
- package/dist/modes/interactive/components/settings-selector-submenus.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts +1 -2
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +6 -13
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/transcript-follow-indicator.js +3 -3
- package/dist/modes/interactive/components/transcript-follow-indicator.js.map +1 -1
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/components/trust-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +8 -8
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js +5 -16
- package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/dist/modes/interactive/interactive-bash-compact.js +2 -9
- package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.js +3 -0
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-editor-actions.js +2 -2
- package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-custom-ui.js +1 -0
- package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-dialogs.js +7 -0
- package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-runtime.js +20 -10
- package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -5
- package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
- package/dist/modes/interactive/interactive-initial-session-binding.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-initial-session-binding.js +3 -0
- package/dist/modes/interactive/interactive-initial-session-binding.js.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.js +0 -8
- package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +5 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +15 -9
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -3
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -3
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +9 -12
- 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-model-routing.js +10 -13
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +29 -0
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +2 -32
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.js +1 -5
- package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +1 -5
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-slash-commands.js +6 -13
- 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 +19 -13
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-tui.js +27 -3
- package/dist/modes/interactive/interactive-tui.js.map +1 -1
- 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/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +4 -1
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +13 -6
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +33 -18
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +2 -0
- package/docs/custom-provider.md +4 -1
- package/docs/docs.json +14 -2
- package/docs/extensions.md +11 -4
- package/docs/intercom.md +33 -8
- package/docs/keybindings.md +8 -6
- package/docs/models/evals.md +394 -0
- package/docs/models/model-selection.md +60 -25
- package/docs/models/pareto-efficiency.md +33 -15
- package/docs/models.md +80 -1
- package/docs/packages.md +2 -2
- package/docs/providers.md +44 -20
- package/docs/quickstart.md +8 -6
- package/docs/rpc.md +3 -3
- package/docs/sdk.md +2 -16
- package/docs/settings.md +19 -22
- package/docs/skills.md +4 -2
- package/docs/subagents.md +9 -3
- package/docs/terminal-setup.md +15 -0
- package/docs/tools/edit.md +373 -0
- package/docs/tools.md +3 -7
- package/docs/tui.md +2 -1
- package/docs/usage.md +3 -5
- package/docs/workflows/api-reference.md +1395 -0
- package/docs/workflows/authoring.md +678 -0
- package/docs/workflows/builtins.md +245 -0
- package/docs/workflows/operations.md +811 -0
- package/docs/workflows/reliable-design.md +2099 -0
- package/docs/workflows/verification.md +62 -0
- package/docs/workflows.md +15 -5183
- package/npm-shrinkwrap.json +584 -69
- package/package.json +7 -7
- package/dist/client/remote-session.d.ts +0 -64
- package/dist/client/remote-session.d.ts.map +0 -1
- package/dist/client/remote-session.js +0 -351
- package/dist/client/remote-session.js.map +0 -1
- package/dist/client/transcript.d.ts +0 -12
- package/dist/client/transcript.d.ts.map +0 -1
- package/dist/client/transcript.js +0 -98
- package/dist/client/transcript.js.map +0 -1
- package/dist/core/codex-fast-mode-transport.d.ts +0 -12
- package/dist/core/codex-fast-mode-transport.d.ts.map +0 -1
- package/dist/core/codex-fast-mode-transport.js.map +0 -1
- package/dist/core/codex-fast-mode.d.ts +0 -53
- package/dist/core/codex-fast-mode.d.ts.map +0 -1
- package/dist/core/codex-fast-mode.js +0 -249
- package/dist/core/codex-fast-mode.js.map +0 -1
- package/dist/modes/interactive/components/fast-mode-selector.d.ts +0 -27
- package/dist/modes/interactive/components/fast-mode-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/fast-mode-selector.js +0 -107
- package/dist/modes/interactive/components/fast-mode-selector.js.map +0 -1
- package/dist/server/create-harness.d.ts +0 -53
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -360
- package/dist/server/create-harness.js.map +0 -1
- package/docs/models/artificial-analysis-index.md +0 -83
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
// only position from which the stderr cap covers the other modules' own initialization.
|
|
3
3
|
import "./bounded-stderr-install.js";
|
|
4
4
|
import net from "net";
|
|
5
|
-
import { writeFileSync, unlinkSync, mkdirSync, readFileSync } from "fs";
|
|
5
|
+
import { chmodSync, writeFileSync, unlinkSync, mkdirSync, readFileSync } from "fs";
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { createMessageReader } from "./framing.js";
|
|
8
|
+
import { writeMessageIfOpen, writeMessageWithOutcome } from "./socket-writes.js";
|
|
9
|
+
import {
|
|
10
|
+
getBrokerDeliveredMessagesPath,
|
|
11
|
+
getBrokerPidPath,
|
|
12
|
+
getBrokerSocketPath,
|
|
13
|
+
getIntercomDirPath,
|
|
14
|
+
} from "./paths.js";
|
|
9
15
|
import type {
|
|
10
16
|
SessionInfo,
|
|
11
17
|
Message,
|
|
@@ -13,15 +19,23 @@ import type {
|
|
|
13
19
|
SupervisorRegistration,
|
|
14
20
|
WorkflowStageRosterAnnouncement,
|
|
15
21
|
WorkflowStageRosterEntry,
|
|
22
|
+
WorkflowPossibleStageAnnouncement,
|
|
23
|
+
WorkflowFutureStageRosterEntry,
|
|
16
24
|
} from "../types.js";
|
|
17
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
formatWorkflowStageTarget,
|
|
27
|
+
parseWorkflowStageTarget,
|
|
28
|
+
withWorkflowStageTargetFinalSegment,
|
|
29
|
+
} from "../workflow-stage-target.js";
|
|
30
|
+
import { DELIVERED_MESSAGE_MAX_ENTRIES, DeliveredMessageCache } from "./delivered-message-cache.js";
|
|
18
31
|
import { isMessage } from "./client-message-validation.js";
|
|
19
32
|
import { buildMessageSendSignature } from "./send-signature.js";
|
|
20
33
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
deliveryTargetIdentity,
|
|
35
|
+
handleBrokerSend,
|
|
36
|
+
type BrokerConnectedSession,
|
|
37
|
+
type PendingStageRoute,
|
|
38
|
+
senderGroupIdentity,
|
|
25
39
|
} from "./send-handler.js";
|
|
26
40
|
import { SupervisorChannelCache } from "./supervisor-channel.js";
|
|
27
41
|
import { hasGroup, normalizeGroup, normalizeGroups } from "../group.js";
|
|
@@ -34,6 +48,8 @@ import {
|
|
|
34
48
|
setSessionGroups,
|
|
35
49
|
} from "./group-membership.js";
|
|
36
50
|
import { PendingQuestionIndex } from "./pending-question-index.js";
|
|
51
|
+
import { matchStagePathSegments } from "../workflow-stage-path-matching.js";
|
|
52
|
+
import { DELIVERED_MESSAGE_TTL_MS } from "../retry-policy.js";
|
|
37
53
|
|
|
38
54
|
const INTERCOM_DIR = getIntercomDirPath();
|
|
39
55
|
const SOCKET_PATH = getBrokerSocketPath();
|
|
@@ -51,6 +67,7 @@ interface WorkflowRosterRegistration {
|
|
|
51
67
|
readonly ownerSessionId: string;
|
|
52
68
|
readonly group: string;
|
|
53
69
|
readonly stages: WorkflowStageRosterAnnouncement[];
|
|
70
|
+
readonly possibleStages?: readonly WorkflowPossibleStageAnnouncement[];
|
|
54
71
|
}
|
|
55
72
|
|
|
56
73
|
|
|
@@ -59,8 +76,8 @@ interface PendingStageAcknowledgment {
|
|
|
59
76
|
readonly senderSocket: net.Socket;
|
|
60
77
|
readonly messageId: string;
|
|
61
78
|
readonly attemptId?: string;
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
readonly runId: string;
|
|
80
|
+
readonly target: string;
|
|
64
81
|
readonly senderSessionId: string;
|
|
65
82
|
readonly sender: SessionInfo;
|
|
66
83
|
readonly message: Message;
|
|
@@ -82,6 +99,8 @@ interface PendingStageNotificationAcknowledgment {
|
|
|
82
99
|
interface LiveWorkflowStageRouteRegistration {
|
|
83
100
|
readonly sessionId: string;
|
|
84
101
|
readonly capability: string;
|
|
102
|
+
/** Run whose route registration authorized this alias; capabilities are per run. */
|
|
103
|
+
readonly runId: string;
|
|
85
104
|
}
|
|
86
105
|
|
|
87
106
|
interface LiveWorkflowStageRouteActivation {
|
|
@@ -136,22 +155,61 @@ function invocationOwnsGroup(invocationGroup: string, candidateGroup: string): b
|
|
|
136
155
|
return candidate === owner || candidate.startsWith(`${owner}/`);
|
|
137
156
|
}
|
|
138
157
|
|
|
139
|
-
function isWorkflowStageRosterAnnouncements(
|
|
158
|
+
function isWorkflowStageRosterAnnouncements(
|
|
159
|
+
value: unknown,
|
|
160
|
+
runId: string,
|
|
161
|
+
invocationGroup: string,
|
|
162
|
+
): value is WorkflowStageRosterAnnouncement[] {
|
|
163
|
+
const groupRootRunId = invocationGroup.startsWith("workflow:")
|
|
164
|
+
? invocationGroup.slice("workflow:".length)
|
|
165
|
+
: undefined;
|
|
166
|
+
return (
|
|
167
|
+
Array.isArray(value) &&
|
|
168
|
+
value.every((stage) => {
|
|
169
|
+
if (typeof stage !== "object" || stage === null) return false;
|
|
170
|
+
const announcement = stage as WorkflowStageRosterAnnouncement;
|
|
171
|
+
const parsed = parseWorkflowStageTarget(announcement.target);
|
|
172
|
+
return (
|
|
173
|
+
typeof announcement.stageId === "string" &&
|
|
174
|
+
typeof announcement.stageName === "string" &&
|
|
175
|
+
parsed?.kind === "path" &&
|
|
176
|
+
parsed.segments.at(-1) === announcement.stageId &&
|
|
177
|
+
// Depth-faithful targets are anchored at the invocation root even when the
|
|
178
|
+
// announcing run is a nested descendant whose run id appears in no segment
|
|
179
|
+
// position (boundary-name segments). The runId conditions keep the flat
|
|
180
|
+
// shortcut form valid for hosts that have not adopted the clarified form.
|
|
181
|
+
((groupRootRunId !== undefined && groupRootRunId === parsed.rootRunId) ||
|
|
182
|
+
runId === parsed.rootRunId ||
|
|
183
|
+
parsed.segments.slice(0, -1).includes(runId)) &&
|
|
184
|
+
(announcement.lifecycle === "pending" || announcement.lifecycle === "running") &&
|
|
185
|
+
typeof announcement.routeEligible === "boolean" &&
|
|
186
|
+
typeof announcement.group === "string"
|
|
187
|
+
);
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function isWorkflowPossibleStageAnnouncements(
|
|
192
|
+
value: unknown,
|
|
193
|
+
runId: string,
|
|
194
|
+
invocationGroup: string,
|
|
195
|
+
): value is WorkflowPossibleStageAnnouncement[] {
|
|
196
|
+
const groupRootRunId = invocationGroup.startsWith("workflow:")
|
|
197
|
+
? invocationGroup.slice("workflow:".length)
|
|
198
|
+
: undefined;
|
|
140
199
|
return (
|
|
141
200
|
Array.isArray(value) &&
|
|
142
|
-
value.every(
|
|
143
|
-
(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(
|
|
149
|
-
|
|
150
|
-
(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
201
|
+
value.every((row) => {
|
|
202
|
+
if (typeof row !== "object" || row === null) return false;
|
|
203
|
+
const announcement = row as WorkflowPossibleStageAnnouncement;
|
|
204
|
+
const parsed = parseWorkflowStageTarget(announcement.target);
|
|
205
|
+
return (
|
|
206
|
+
parsed !== undefined &&
|
|
207
|
+
((groupRootRunId !== undefined && groupRootRunId === parsed.rootRunId) || runId === parsed.rootRunId) &&
|
|
208
|
+
typeof announcement.queuedCount === "number" &&
|
|
209
|
+
Number.isInteger(announcement.queuedCount) &&
|
|
210
|
+
announcement.queuedCount >= 0
|
|
211
|
+
);
|
|
212
|
+
})
|
|
155
213
|
);
|
|
156
214
|
}
|
|
157
215
|
|
|
@@ -160,7 +218,11 @@ class IntercomBroker {
|
|
|
160
218
|
private sessions = new Map<string, ConnectedSession>();
|
|
161
219
|
private server: net.Server;
|
|
162
220
|
private shutdownTimer: NodeJS.Timeout | null = null;
|
|
163
|
-
private deliveredMessages = new DeliveredMessageCache(
|
|
221
|
+
private deliveredMessages = new DeliveredMessageCache(
|
|
222
|
+
DELIVERED_MESSAGE_TTL_MS,
|
|
223
|
+
DELIVERED_MESSAGE_MAX_ENTRIES,
|
|
224
|
+
getBrokerDeliveredMessagesPath(),
|
|
225
|
+
);
|
|
164
226
|
private supervisorChannel = new SupervisorChannelCache();
|
|
165
227
|
private pendingQuestions = new PendingQuestionIndex();
|
|
166
228
|
private pendingStageRoutes = new Map<string, PendingStageRouteRegistration>();
|
|
@@ -171,7 +233,8 @@ class IntercomBroker {
|
|
|
171
233
|
private liveWorkflowStageRouteActivations = new Map<string, LiveWorkflowStageRouteActivation>();
|
|
172
234
|
|
|
173
235
|
constructor() {
|
|
174
|
-
mkdirSync(INTERCOM_DIR, { recursive: true });
|
|
236
|
+
mkdirSync(INTERCOM_DIR, { recursive: true, mode: 0o700 });
|
|
237
|
+
if (process.platform !== "win32") chmodSync(INTERCOM_DIR, 0o700);
|
|
175
238
|
if (process.platform !== "win32") {
|
|
176
239
|
try {
|
|
177
240
|
unlinkSync(SOCKET_PATH);
|
|
@@ -206,13 +269,38 @@ class IntercomBroker {
|
|
|
206
269
|
|
|
207
270
|
socket.on("data", reader);
|
|
208
271
|
|
|
272
|
+
// Retire the session as soon as its socket stops being able to accept a
|
|
273
|
+
// frame, not only on 'close'. A peer that half-closes -- or one whose
|
|
274
|
+
// writable side the broker itself ended after refusing a registration --
|
|
275
|
+
// can keep its read side open indefinitely, so 'close' may never arrive
|
|
276
|
+
// while the session is still in the fan-out map and every broadcast, ack
|
|
277
|
+
// and delivery targets a socket whose writable side is already gone.
|
|
278
|
+
//
|
|
279
|
+
// 'end' is a safe retirement point: Node runs its automatic end() after the
|
|
280
|
+
// user listeners, so the socket is still writable here and no final frame
|
|
281
|
+
// is lost. disconnectSession is idempotent, so 'end', 'error' and 'close'
|
|
282
|
+
// may each fire without duplicating the departure broadcast.
|
|
283
|
+
const retire = () => {
|
|
284
|
+
const departing = sessionId;
|
|
285
|
+
if (departing === null) return;
|
|
286
|
+
sessionId = null;
|
|
287
|
+
this.disconnectSession(departing);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
socket.on("end", () => {
|
|
291
|
+
retire();
|
|
292
|
+
this.scheduleShutdownCheck();
|
|
293
|
+
});
|
|
294
|
+
|
|
209
295
|
socket.on("close", () => {
|
|
210
|
-
|
|
296
|
+
retire();
|
|
211
297
|
this.scheduleShutdownCheck();
|
|
212
298
|
});
|
|
213
299
|
|
|
214
300
|
socket.on("error", (error) => {
|
|
215
301
|
console.error("Socket error:", error);
|
|
302
|
+
retire();
|
|
303
|
+
this.scheduleShutdownCheck();
|
|
216
304
|
});
|
|
217
305
|
}
|
|
218
306
|
|
|
@@ -227,21 +315,87 @@ class IntercomBroker {
|
|
|
227
315
|
}
|
|
228
316
|
}, 5000);
|
|
229
317
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* End a connection the broker itself is refusing, and retire its session first.
|
|
321
|
+
*
|
|
322
|
+
* `socket.end()` only sends FIN. A peer opened with `allowHalfOpen` — or one
|
|
323
|
+
* that simply has not read yet — never has to close in response, so without
|
|
324
|
+
* this the refused session would stay in the fan-out map indefinitely: still
|
|
325
|
+
* advertised by `list`, still acked as `delivered`, and still written to by
|
|
326
|
+
* every broadcast even though its writable side is gone.
|
|
327
|
+
*/
|
|
328
|
+
private endRefusedConnection(
|
|
329
|
+
socket: net.Socket,
|
|
330
|
+
currentId: string | null,
|
|
331
|
+
setId: (id: string | null) => void,
|
|
332
|
+
): void {
|
|
333
|
+
if (currentId !== null) {
|
|
334
|
+
this.disconnectSession(currentId);
|
|
335
|
+
setId(null);
|
|
336
|
+
}
|
|
337
|
+
socket.end();
|
|
338
|
+
this.scheduleShutdownCheck();
|
|
339
|
+
}
|
|
340
|
+
private pendingStageOwnerForTarget(
|
|
341
|
+
target: string,
|
|
342
|
+
): { readonly runId: string; readonly registration: PendingStageRouteRegistration } | undefined {
|
|
343
|
+
const parsed = parseWorkflowStageTarget(target);
|
|
344
|
+
if (parsed === undefined) return undefined;
|
|
345
|
+
const invocationGroup = normalizeGroup(`workflow:${parsed.rootRunId}`);
|
|
346
|
+
// Slice 3 (D3/D6): pattern and `**` targets are anchored at the invocation root
|
|
347
|
+
// registration — glob segments name no nested run, and the sticky entry lives in
|
|
348
|
+
// the root run's durable bucket.
|
|
349
|
+
if (parsed.kind !== "path") {
|
|
350
|
+
const rootRegistration = this.pendingStageRoutes.get(parsed.rootRunId);
|
|
351
|
+
return rootRegistration !== undefined && normalizeGroup(rootRegistration.group) === invocationGroup
|
|
352
|
+
? { runId: parsed.rootRunId, registration: rootRegistration }
|
|
353
|
+
: undefined;
|
|
354
|
+
}
|
|
355
|
+
let ownerRunId = parsed.rootRunId;
|
|
356
|
+
let registration = this.pendingStageRoutes.get(ownerRunId);
|
|
357
|
+
for (const segment of parsed.segments.slice(0, -1)) {
|
|
358
|
+
const candidate = this.pendingStageRoutes.get(segment);
|
|
359
|
+
if (candidate !== undefined && normalizeGroup(candidate.group) === invocationGroup) {
|
|
360
|
+
ownerRunId = segment;
|
|
361
|
+
registration = candidate;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return registration === undefined ? undefined : { runId: ownerRunId, registration };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private resolveLegacyWorkflowStageTarget = (runId: string, stageKey: string): string | undefined => {
|
|
368
|
+
const route = this.pendingStageRoutes.get(runId);
|
|
369
|
+
const roster = this.workflowRosters.get(runId);
|
|
370
|
+
if (route === undefined || roster === undefined || !route.group.startsWith("workflow:")) return undefined;
|
|
371
|
+
const matches = roster.stages.filter((stage) => stage.stageId === stageKey || stage.stageName === stageKey);
|
|
372
|
+
return matches.length === 1 ? matches[0]!.target : undefined;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
private resolveLiveWorkflowStage = (target: string): ConnectedSession | undefined => {
|
|
376
|
+
const registration = this.liveWorkflowStageRoutes.get(target);
|
|
377
|
+
if (registration === undefined) return undefined;
|
|
378
|
+
// The alias is owned by the run that registered it: route capabilities are per run,
|
|
379
|
+
// and for boundary-form targets the depth-first owner resolution selects the root
|
|
380
|
+
// registration, whose capability legitimately differs from the nested stage's own.
|
|
381
|
+
// Validate staleness against the alias's own run registration and require the alias
|
|
382
|
+
// to stay anchored inside the target's invocation.
|
|
383
|
+
const ownRegistration = this.pendingStageRoutes.get(registration.runId);
|
|
384
|
+
const parsed = parseWorkflowStageTarget(target);
|
|
385
|
+
if (
|
|
386
|
+
ownRegistration === undefined ||
|
|
387
|
+
ownRegistration.capability !== registration.capability ||
|
|
388
|
+
parsed === undefined ||
|
|
389
|
+
normalizeGroup(ownRegistration.group) !== normalizeGroup(`workflow:${parsed.rootRunId}`)
|
|
390
|
+
) {
|
|
237
391
|
this.liveWorkflowStageRoutes.delete(target);
|
|
238
392
|
return undefined;
|
|
239
393
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
394
|
+
const session = this.sessions.get(registration.sessionId);
|
|
395
|
+
if (session !== undefined) return session;
|
|
396
|
+
this.liveWorkflowStageRoutes.delete(target);
|
|
397
|
+
return undefined;
|
|
398
|
+
};
|
|
245
399
|
|
|
246
400
|
private canInspectSelectedGroup(requester: ConnectedSession, selectedGroup: string): boolean {
|
|
247
401
|
const selected = normalizeGroup(selectedGroup);
|
|
@@ -270,14 +424,16 @@ class IntercomBroker {
|
|
|
270
424
|
target: ConnectedSession,
|
|
271
425
|
logicalTarget: string,
|
|
272
426
|
): boolean => {
|
|
273
|
-
const
|
|
274
|
-
const owner = parsed === undefined ? undefined : this.pendingStageRoutes.get(parsed.runId);
|
|
427
|
+
const owner = this.pendingStageOwnerForTarget(logicalTarget)?.registration;
|
|
275
428
|
const live = this.liveWorkflowStageRoutes.get(logicalTarget);
|
|
276
429
|
if (owner === undefined || live?.sessionId !== target.info.id) return false;
|
|
277
430
|
const targetGroup = target.registrationGroup ?? target.info.group ?? "default";
|
|
278
431
|
return this.canControlWorkflowInvocation(sender, owner.group) && invocationOwnsGroup(owner.group, targetGroup);
|
|
279
432
|
};
|
|
280
433
|
|
|
434
|
+
// `send`/`ask` always resolve this namespace as a stage target. `list({ group })`
|
|
435
|
+
// remains the only operation that interprets the same path as an owned subgroup.
|
|
436
|
+
|
|
281
437
|
private workflowStagesVisibleTo(requester: ConnectedSession, selectedGroup?: string): WorkflowStageRosterEntry[] {
|
|
282
438
|
const requesterGroups = sessionGroups(requester.info);
|
|
283
439
|
const selected = selectedGroup === undefined ? undefined : normalizeGroup(selectedGroup);
|
|
@@ -311,12 +467,42 @@ class IntercomBroker {
|
|
|
311
467
|
return entries;
|
|
312
468
|
}
|
|
313
469
|
|
|
470
|
+
/**
|
|
471
|
+
* D7 (slice 4): possible-future rows published with the run's persisted scan. Visible
|
|
472
|
+
* only from inside the invocation — a membership in `workflow:<rootRunId>` or in an
|
|
473
|
+
* owned subgroup (`workflow:<rootRunId>/…`). The read-only peek filter keeps the
|
|
474
|
+
* invocation-group rows out of every other group's view.
|
|
475
|
+
*/
|
|
476
|
+
private workflowFutureStagesVisibleTo(
|
|
477
|
+
requester: ConnectedSession,
|
|
478
|
+
selectedGroup?: string,
|
|
479
|
+
): WorkflowFutureStageRosterEntry[] {
|
|
480
|
+
const requesterGroups = sessionGroups(requester.info);
|
|
481
|
+
const selected = selectedGroup === undefined ? undefined : normalizeGroup(selectedGroup);
|
|
482
|
+
const entries: WorkflowFutureStageRosterEntry[] = [];
|
|
483
|
+
for (const [runId, roster] of this.workflowRosters) {
|
|
484
|
+
if (roster.possibleStages === undefined || roster.possibleStages.length === 0) continue;
|
|
485
|
+
if (![...requesterGroups].some((group) => invocationOwnsGroup(roster.group, group))) continue;
|
|
486
|
+
if (selected !== undefined && selected !== roster.group) continue;
|
|
487
|
+
for (const row of roster.possibleStages) {
|
|
488
|
+
entries.push({
|
|
489
|
+
kind: "workflow-future-stage",
|
|
490
|
+
runId,
|
|
491
|
+
target: row.target,
|
|
492
|
+
queuedCount: row.queuedCount,
|
|
493
|
+
group: roster.group,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return entries;
|
|
498
|
+
}
|
|
499
|
+
|
|
314
500
|
private acknowledgeLiveWorkflowStageRoute(requestId: string): void {
|
|
315
501
|
const activation = this.liveWorkflowStageRouteActivations.get(requestId);
|
|
316
502
|
if (activation === undefined || activation.pendingRequestIds.size > 0) return;
|
|
317
503
|
this.liveWorkflowStageRouteActivations.delete(requestId);
|
|
318
504
|
const stage = this.sessions.get(activation.sessionId);
|
|
319
|
-
if (stage !== undefined)
|
|
505
|
+
if (stage !== undefined) writeMessageIfOpen(stage.socket, { type: "live_workflow_stage_route_registered", requestId });
|
|
320
506
|
}
|
|
321
507
|
|
|
322
508
|
private releasePendingStageAcknowledgment(requestId: string): void {
|
|
@@ -333,10 +519,29 @@ class IntercomBroker {
|
|
|
333
519
|
stageKeys: readonly string[],
|
|
334
520
|
capability: string,
|
|
335
521
|
): boolean {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
522
|
+
const uniqueStageKeys = [...new Set(stageKeys)];
|
|
523
|
+
const owner = this.pendingStageRoutes.get(runId);
|
|
524
|
+
if (owner === undefined || !owner.group.startsWith("workflow:")) return false;
|
|
525
|
+
const rootRunId = owner.group.slice("workflow:".length);
|
|
526
|
+
// D8 clarification: advertised targets are depth-faithful, so the live aliases must be
|
|
527
|
+
// depth-faithful too. The roster publishes the id-form target per stage; the name form
|
|
528
|
+
// swaps its final segment. Without a roster entry, fall back to the flat run-id prefix
|
|
529
|
+
// (still an accepted resolver input).
|
|
530
|
+
const roster = this.workflowRosters.get(runId);
|
|
531
|
+
const targets = uniqueStageKeys.map((stageKey) => {
|
|
532
|
+
const entry = roster?.stages.find((stage) => stage.stageId === stageKey || stage.stageName === stageKey);
|
|
533
|
+
const entryTarget = entry === undefined ? undefined : parseWorkflowStageTarget(entry.target);
|
|
534
|
+
if (entry !== undefined && entryTarget?.kind === "path") {
|
|
535
|
+
if (stageKey !== entry.stageName) return entry.target;
|
|
536
|
+
return (
|
|
537
|
+
withWorkflowStageTargetFinalSegment(entry.target, entry.stageName) ?? entry.target
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
const prefix = runId === rootRunId ? [] : [runId];
|
|
541
|
+
return formatWorkflowStageTarget(rootRunId, ...prefix, stageKey);
|
|
542
|
+
});
|
|
543
|
+
for (const target of targets) {
|
|
544
|
+
const existing = this.liveWorkflowStageRoutes.get(target);
|
|
340
545
|
if (
|
|
341
546
|
existing !== undefined &&
|
|
342
547
|
existing.sessionId !== currentId &&
|
|
@@ -346,11 +551,32 @@ class IntercomBroker {
|
|
|
346
551
|
}
|
|
347
552
|
}
|
|
348
553
|
for (const target of targets) {
|
|
349
|
-
this.liveWorkflowStageRoutes.set(target, { sessionId: currentId, capability });
|
|
554
|
+
this.liveWorkflowStageRoutes.set(target, { sessionId: currentId, capability, runId });
|
|
350
555
|
}
|
|
351
556
|
const pendingRequestIds = new Set(
|
|
352
557
|
[...this.pendingStageAcknowledgments]
|
|
353
|
-
|
|
558
|
+
.filter(([, pending]) => {
|
|
559
|
+
// Match in-flight pendings of the whole invocation, not just this run's own:
|
|
560
|
+
// boundary-form pendings settle on the root registration while the going-live
|
|
561
|
+
// stage may register under its nested child run.
|
|
562
|
+
const parsedPending = parseWorkflowStageTarget(pending.target);
|
|
563
|
+
if (parsedPending === undefined || parsedPending.rootRunId !== rootRunId) return false;
|
|
564
|
+
if (parsedPending.kind !== "path") {
|
|
565
|
+
// Slice 3: sticky pattern pendings hold the going-live barrier when the
|
|
566
|
+
// stage's advertised depth-faithful target matches the pattern.
|
|
567
|
+
return uniqueStageKeys.some((stageKey) => {
|
|
568
|
+
const entry = roster?.stages.find(
|
|
569
|
+
(candidate) => candidate.stageId === stageKey || candidate.stageName === stageKey,
|
|
570
|
+
);
|
|
571
|
+
const parsedEntry = entry === undefined ? undefined : parseWorkflowStageTarget(entry.target);
|
|
572
|
+
if (parsedEntry === undefined) {
|
|
573
|
+
return matchStagePathSegments(parsedPending.segments, parsedPending.segments);
|
|
574
|
+
}
|
|
575
|
+
return matchStagePathSegments(parsedPending.segments, parsedEntry.segments);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return uniqueStageKeys.includes(parsedPending.segments.at(-1) ?? "");
|
|
579
|
+
})
|
|
354
580
|
.map(([pendingRequestId]) => pendingRequestId),
|
|
355
581
|
);
|
|
356
582
|
this.liveWorkflowStageRouteActivations.set(requestId, { sessionId: currentId, pendingRequestIds });
|
|
@@ -358,16 +584,17 @@ class IntercomBroker {
|
|
|
358
584
|
return true;
|
|
359
585
|
}
|
|
360
586
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
587
|
+
private routePendingStage = (route: PendingStageRoute): boolean => {
|
|
588
|
+
const ownerMatch = this.pendingStageOwnerForTarget(route.target);
|
|
589
|
+
if (ownerMatch === undefined) return false;
|
|
590
|
+
const { runId, registration: ownerRegistration } = ownerMatch;
|
|
591
|
+
const owner = this.sessions.get(ownerRegistration.sessionId);
|
|
592
|
+
if (owner === undefined) {
|
|
593
|
+
this.pendingStageRoutes.delete(runId);
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
369
596
|
if (route.liveTargetId === undefined && !this.canControlWorkflowInvocation(route.from, ownerRegistration.group)) {
|
|
370
|
-
|
|
597
|
+
writeMessageIfOpen(route.socket, {
|
|
371
598
|
type: "delivery_failed",
|
|
372
599
|
messageId: route.message.id,
|
|
373
600
|
...(route.attemptId ? { attemptId: route.attemptId } : {}),
|
|
@@ -381,7 +608,7 @@ class IntercomBroker {
|
|
|
381
608
|
if (pending === undefined) return;
|
|
382
609
|
this.pendingStageAcknowledgments.delete(requestId);
|
|
383
610
|
this.releasePendingStageAcknowledgment(requestId);
|
|
384
|
-
|
|
611
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
385
612
|
type: "delivery_failed",
|
|
386
613
|
messageId: pending.messageId,
|
|
387
614
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -393,8 +620,8 @@ class IntercomBroker {
|
|
|
393
620
|
senderSocket: route.socket,
|
|
394
621
|
messageId: route.message.id,
|
|
395
622
|
...(route.attemptId ? { attemptId: route.attemptId } : {}),
|
|
396
|
-
|
|
397
|
-
|
|
623
|
+
runId,
|
|
624
|
+
target: route.target,
|
|
398
625
|
senderSessionId: route.from.info.id,
|
|
399
626
|
sender: { ...route.from.info },
|
|
400
627
|
message: route.message,
|
|
@@ -402,19 +629,31 @@ class IntercomBroker {
|
|
|
402
629
|
...(route.signature === undefined ? {} : { signature: route.signature }),
|
|
403
630
|
timeout,
|
|
404
631
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
632
|
+
if (
|
|
633
|
+
!writeMessageIfOpen(owner.socket, {
|
|
634
|
+
type: "pending_stage_message",
|
|
635
|
+
requestId,
|
|
636
|
+
from: route.from.info,
|
|
409
637
|
...(route.from.registrationName === undefined ? {} : { senderRegistrationName: route.from.registrationName }),
|
|
410
638
|
...(route.from.registrationReturnAddress === undefined
|
|
411
639
|
? {}
|
|
412
640
|
: { senderReturnAddress: route.from.registrationReturnAddress }),
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
641
|
+
runId,
|
|
642
|
+
target: route.target,
|
|
643
|
+
message: route.message,
|
|
416
644
|
...(route.liveTargetId === undefined ? {} : { live: true }),
|
|
417
|
-
|
|
645
|
+
})
|
|
646
|
+
) {
|
|
647
|
+
// The owner's socket stopped accepting frames between resolving the route
|
|
648
|
+
// and this write. Unwind the acknowledgment just armed and report the
|
|
649
|
+
// route as not handled, so the caller answers with an honest
|
|
650
|
+
// delivery_failed now instead of stalling the sender for the full
|
|
651
|
+
// acknowledgment timeout on a message nobody ever received.
|
|
652
|
+
clearTimeout(timeout);
|
|
653
|
+
this.pendingStageAcknowledgments.delete(requestId);
|
|
654
|
+
this.releasePendingStageAcknowledgment(requestId);
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
418
657
|
return true;
|
|
419
658
|
};
|
|
420
659
|
|
|
@@ -423,8 +662,11 @@ class IntercomBroker {
|
|
|
423
662
|
requestId: string,
|
|
424
663
|
outcome: "queued" | "delivered" | "forward" | "refused",
|
|
425
664
|
position: number | undefined,
|
|
665
|
+
forwardTarget: string | undefined,
|
|
426
666
|
reason: string | undefined,
|
|
427
667
|
reasonCode: "message_id_conflict" | undefined,
|
|
668
|
+
notInKnownSet?: true,
|
|
669
|
+
forwardTargets?: readonly string[],
|
|
428
670
|
): void {
|
|
429
671
|
const pending = this.pendingStageAcknowledgments.get(requestId);
|
|
430
672
|
if (pending === undefined || pending.ownerSessionId !== currentId) return;
|
|
@@ -432,11 +674,11 @@ class IntercomBroker {
|
|
|
432
674
|
this.pendingStageAcknowledgments.delete(requestId);
|
|
433
675
|
this.releasePendingStageAcknowledgment(requestId);
|
|
434
676
|
if (outcome === "forward") {
|
|
435
|
-
this.forwardValidatedLiveStageMessage(pending);
|
|
677
|
+
this.forwardValidatedLiveStageMessage(pending, forwardTarget);
|
|
436
678
|
return;
|
|
437
679
|
}
|
|
438
680
|
if (outcome === "delivered") {
|
|
439
|
-
|
|
681
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
440
682
|
type: "delivered",
|
|
441
683
|
messageId: pending.messageId,
|
|
442
684
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -444,17 +686,34 @@ class IntercomBroker {
|
|
|
444
686
|
return;
|
|
445
687
|
}
|
|
446
688
|
if (outcome === "queued" && typeof position === "number" && position > 0) {
|
|
447
|
-
|
|
689
|
+
// Slice 3 (D6/D9): a sticky pattern entry answers `queued` once while any
|
|
690
|
+
// matching live stages receive the message now, as ordinary inbound
|
|
691
|
+
// messages with the original sender identity. The owner records a live
|
|
692
|
+
// delivery only for the targets this write actually reached, so a stage
|
|
693
|
+
// that dropped between the owner's answer and this write is not marked
|
|
694
|
+
// delivered and a retried send re-forwards to it.
|
|
695
|
+
const deliveredTargets = this.deliverStickyBroadcast(pending, forwardTargets);
|
|
696
|
+
const ownerSocket = this.sessions.get(currentId)?.socket;
|
|
697
|
+
if (deliveredTargets.length > 0 && ownerSocket !== undefined) {
|
|
698
|
+
writeMessageIfOpen(ownerSocket, {
|
|
699
|
+
type: "sticky_live_delivered",
|
|
700
|
+
runId: pending.runId,
|
|
701
|
+
messageId: pending.messageId,
|
|
702
|
+
target: pending.target,
|
|
703
|
+
deliveredTargets,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
448
707
|
type: "queued",
|
|
449
708
|
messageId: pending.messageId,
|
|
450
709
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
451
|
-
|
|
452
|
-
stageKey: pending.stageKey,
|
|
710
|
+
target: pending.target,
|
|
453
711
|
position,
|
|
712
|
+
...(notInKnownSet === true ? { notInKnownSet: true } : {}),
|
|
454
713
|
});
|
|
455
714
|
return;
|
|
456
715
|
}
|
|
457
|
-
|
|
716
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
458
717
|
type: "delivery_failed",
|
|
459
718
|
messageId: pending.messageId,
|
|
460
719
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -463,16 +722,27 @@ class IntercomBroker {
|
|
|
463
722
|
});
|
|
464
723
|
}
|
|
465
724
|
|
|
466
|
-
private forwardValidatedLiveStageMessage(pending: PendingStageAcknowledgment): void {
|
|
725
|
+
private forwardValidatedLiveStageMessage(pending: PendingStageAcknowledgment, forwardTarget?: string): void {
|
|
467
726
|
const from = this.sessions.get(pending.senderSessionId);
|
|
468
|
-
|
|
727
|
+
// The route owner chooses the live alias for boundary-form targets, so bind its
|
|
728
|
+
// forward answer to the sender's own invocation: a target under any other rootRunId
|
|
729
|
+
// could deliver a sender's message into a different invocation, bypassing the
|
|
730
|
+
// sender's group authorization on the ordinary send path.
|
|
731
|
+
const destination = forwardTarget ?? pending.target;
|
|
732
|
+
const parsedDestination = parseWorkflowStageTarget(destination);
|
|
733
|
+
const parsedPending = parseWorkflowStageTarget(pending.target);
|
|
734
|
+
const crossInvocation =
|
|
735
|
+
parsedDestination === undefined ||
|
|
736
|
+
parsedPending === undefined ||
|
|
737
|
+
parsedDestination.rootRunId !== parsedPending.rootRunId;
|
|
738
|
+
const target = crossInvocation ? undefined : this.resolveLiveWorkflowStage(destination);
|
|
469
739
|
if (
|
|
470
740
|
from === undefined ||
|
|
471
741
|
target === undefined ||
|
|
472
|
-
target.info.id !== pending.liveTargetId ||
|
|
742
|
+
(pending.liveTargetId !== undefined && target.info.id !== pending.liveTargetId) ||
|
|
473
743
|
pending.signature === undefined
|
|
474
744
|
) {
|
|
475
|
-
|
|
745
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
476
746
|
type: "delivery_failed",
|
|
477
747
|
messageId: pending.messageId,
|
|
478
748
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -480,28 +750,144 @@ class IntercomBroker {
|
|
|
480
750
|
});
|
|
481
751
|
return;
|
|
482
752
|
}
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
753
|
+
const targetIdentity = deliveryTargetIdentity(target);
|
|
754
|
+
const deliveredMatch = this.deliveredMessages.lookupForTarget(
|
|
755
|
+
pending.messageId,
|
|
756
|
+
pending.signature,
|
|
757
|
+
targetIdentity,
|
|
758
|
+
);
|
|
759
|
+
if (deliveredMatch !== "miss") {
|
|
760
|
+
if (deliveredMatch === "match") {
|
|
761
|
+
if (pending.message.expectsReply === true) {
|
|
762
|
+
const acceptedQuestionTarget = this.deliveredMessages.lookupQuestionTarget(
|
|
763
|
+
pending.messageId,
|
|
764
|
+
pending.signature,
|
|
765
|
+
senderGroupIdentity(from),
|
|
766
|
+
);
|
|
767
|
+
if (acceptedQuestionTarget === undefined) {
|
|
768
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
769
|
+
type: "delivery_failed",
|
|
770
|
+
messageId: pending.messageId,
|
|
771
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
772
|
+
reason: "Accepted Intercom question route could not be securely rebound",
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
|
|
777
|
+
}
|
|
778
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
779
|
+
type: "delivered",
|
|
780
|
+
messageId: pending.messageId,
|
|
781
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
782
|
+
});
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
486
786
|
type: "delivery_failed",
|
|
487
787
|
messageId: pending.messageId,
|
|
488
788
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
489
|
-
reason:
|
|
490
|
-
|
|
789
|
+
reason:
|
|
790
|
+
deliveredMatch === "conflict"
|
|
791
|
+
? `Intercom message ID '${pending.messageId}' was already delivered with a different target or payload`
|
|
792
|
+
: deliveredMatch === "uncertain"
|
|
793
|
+
? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
|
|
794
|
+
: "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
|
|
795
|
+
...(deliveredMatch === "conflict" ? { reasonCode: "message_id_conflict" as const } : {}),
|
|
491
796
|
});
|
|
492
797
|
return;
|
|
493
798
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
799
|
+
const reservation =
|
|
800
|
+
pending.message.expectsReply === true
|
|
801
|
+
? this.deliveredMessages.reserveQuestion(
|
|
802
|
+
pending.messageId,
|
|
803
|
+
pending.signature,
|
|
804
|
+
{
|
|
805
|
+
targetSessionId: target.info.id,
|
|
806
|
+
senderGroupIdentity: senderGroupIdentity(from),
|
|
807
|
+
},
|
|
808
|
+
Date.now(),
|
|
809
|
+
targetIdentity,
|
|
810
|
+
)
|
|
811
|
+
: this.deliveredMessages.reserve(pending.messageId, pending.signature, Date.now(), targetIdentity);
|
|
812
|
+
if (reservation === "match") {
|
|
497
813
|
if (pending.message.expectsReply === true) {
|
|
814
|
+
const acceptedQuestionTarget = this.deliveredMessages.lookupQuestionTarget(
|
|
815
|
+
pending.messageId,
|
|
816
|
+
pending.signature,
|
|
817
|
+
senderGroupIdentity(from),
|
|
818
|
+
);
|
|
819
|
+
if (acceptedQuestionTarget === undefined) {
|
|
820
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
821
|
+
type: "delivery_failed",
|
|
822
|
+
messageId: pending.messageId,
|
|
823
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
824
|
+
reason: "Accepted Intercom question route could not be securely rebound",
|
|
825
|
+
});
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
498
828
|
this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
|
|
499
829
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
830
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
831
|
+
type: "delivered",
|
|
832
|
+
messageId: pending.messageId,
|
|
833
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
834
|
+
});
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (reservation !== "recorded") {
|
|
838
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
839
|
+
type: "delivery_failed",
|
|
840
|
+
messageId: pending.messageId,
|
|
841
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
842
|
+
reason:
|
|
843
|
+
reservation === "capacity"
|
|
844
|
+
? "Intercom accepted-delivery capacity is full; refusing delivery without evicting live retry authority"
|
|
845
|
+
: reservation === "uncertain"
|
|
846
|
+
? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
|
|
847
|
+
: "Intercom accepted-delivery authority changed before forwarding; refusing possible duplicate delivery",
|
|
848
|
+
...(reservation === "conflict" ? { reasonCode: "message_id_conflict" as const } : {}),
|
|
849
|
+
});
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (!writeMessageIfOpen(target.socket, { type: "message", from: pending.sender, message: pending.message })) {
|
|
853
|
+
this.deliveredMessages.forget(pending.messageId, pending.signature);
|
|
854
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
855
|
+
type: "delivery_failed",
|
|
856
|
+
messageId: pending.messageId,
|
|
857
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
858
|
+
reason: "Session not found",
|
|
859
|
+
});
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
let accepted: ReturnType<DeliveredMessageCache["accept"]>;
|
|
863
|
+
try {
|
|
864
|
+
accepted = this.deliveredMessages.accept(pending.messageId, pending.signature, targetIdentity);
|
|
865
|
+
} catch {
|
|
866
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
867
|
+
type: "delivery_failed",
|
|
868
|
+
messageId: pending.messageId,
|
|
869
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
870
|
+
reason: "Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
|
|
871
|
+
});
|
|
872
|
+
return;
|
|
503
873
|
}
|
|
504
|
-
|
|
874
|
+
if (accepted !== "accepted") {
|
|
875
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
876
|
+
type: "delivery_failed",
|
|
877
|
+
messageId: pending.messageId,
|
|
878
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
879
|
+
reason: "Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
|
|
880
|
+
...(accepted === "conflict" ? { reasonCode: "message_id_conflict" as const } : {}),
|
|
881
|
+
});
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (pending.message.expectsReply === true) {
|
|
885
|
+
this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
|
|
886
|
+
}
|
|
887
|
+
if (pending.message.replyTo !== undefined) {
|
|
888
|
+
this.pendingQuestions.clearReply(from.info.id, target.info.id, pending.message.replyTo);
|
|
889
|
+
}
|
|
890
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
505
891
|
type: "delivered",
|
|
506
892
|
messageId: pending.messageId,
|
|
507
893
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -509,6 +895,38 @@ class IntercomBroker {
|
|
|
509
895
|
}
|
|
510
896
|
|
|
511
897
|
|
|
898
|
+
/**
|
|
899
|
+
* Slice 3 (D6/D9): deliver a sticky broadcast to the live stages the host validated,
|
|
900
|
+
* each as an ordinary inbound message frame carrying the original sender identity.
|
|
901
|
+
*/
|
|
902
|
+
private deliverStickyBroadcast(
|
|
903
|
+
pending: PendingStageAcknowledgment,
|
|
904
|
+
forwardTargets: readonly string[] | undefined,
|
|
905
|
+
): string[] {
|
|
906
|
+
if (forwardTargets === undefined || forwardTargets.length === 0) return [];
|
|
907
|
+
const parsedPending = parseWorkflowStageTarget(pending.target);
|
|
908
|
+
if (parsedPending === undefined) return [];
|
|
909
|
+
const invocationGroup = normalizeGroup(`workflow:${parsedPending.rootRunId}`);
|
|
910
|
+
const delivered: string[] = [];
|
|
911
|
+
for (const target of forwardTargets) {
|
|
912
|
+
const targetSession = this.resolveLiveWorkflowStage(target);
|
|
913
|
+
if (targetSession === undefined) continue;
|
|
914
|
+
const targetGroup = targetSession.registrationGroup ?? targetSession.info.group ?? "default";
|
|
915
|
+
if (!invocationOwnsGroup(invocationGroup, normalizeGroup(targetGroup))) continue;
|
|
916
|
+
// The write itself is the writability check, so a stage that dropped between
|
|
917
|
+
// the owner's answer and this loop is not reported as delivered and a retried
|
|
918
|
+
// send re-forwards to it.
|
|
919
|
+
if (!writeMessageIfOpen(targetSession.socket, { type: "message", from: pending.sender, message: pending.message }))
|
|
920
|
+
continue;
|
|
921
|
+
delivered.push(target);
|
|
922
|
+
}
|
|
923
|
+
// Deliberately NOT recorded in the delivered-message cache (round-1 review): the
|
|
924
|
+
// sticky ledger in the workflow host is the durable dedupe authority, so a retry
|
|
925
|
+
// re-routes to the host, dedupes there, and the sender's ack stays `queued`
|
|
926
|
+
// instead of flipping to `delivered` between attempts.
|
|
927
|
+
return delivered;
|
|
928
|
+
}
|
|
929
|
+
|
|
512
930
|
private failPendingStageNotification(
|
|
513
931
|
socket: net.Socket,
|
|
514
932
|
messageId: string,
|
|
@@ -516,7 +934,7 @@ class IntercomBroker {
|
|
|
516
934
|
reason: string,
|
|
517
935
|
reasonCode?: "message_id_conflict",
|
|
518
936
|
): void {
|
|
519
|
-
|
|
937
|
+
writeMessageIfOpen(socket, {
|
|
520
938
|
type: "delivery_failed",
|
|
521
939
|
messageId,
|
|
522
940
|
...(attemptId === undefined ? {} : { attemptId }),
|
|
@@ -571,16 +989,20 @@ class IntercomBroker {
|
|
|
571
989
|
const signature = buildMessageSendSignature(logicalTarget, message, clientMessage.capability);
|
|
572
990
|
const deliveredMatch = this.deliveredMessages.lookup(message.id, signature);
|
|
573
991
|
if (deliveredMatch === "match") {
|
|
574
|
-
|
|
992
|
+
writeMessageIfOpen(socket, { type: "delivered", messageId: message.id, ...(attemptId === undefined ? {} : { attemptId }) });
|
|
575
993
|
return;
|
|
576
994
|
}
|
|
577
|
-
if (deliveredMatch
|
|
995
|
+
if (deliveredMatch !== "miss") {
|
|
578
996
|
this.failPendingStageNotification(
|
|
579
997
|
socket,
|
|
580
998
|
message.id,
|
|
581
999
|
attemptId,
|
|
582
|
-
|
|
583
|
-
|
|
1000
|
+
deliveredMatch === "conflict"
|
|
1001
|
+
? `Intercom message ID '${message.id}' was already delivered with a different target or payload`
|
|
1002
|
+
: deliveredMatch === "uncertain"
|
|
1003
|
+
? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
|
|
1004
|
+
: "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
|
|
1005
|
+
deliveredMatch === "conflict" ? "message_id_conflict" : undefined,
|
|
584
1006
|
);
|
|
585
1007
|
return;
|
|
586
1008
|
}
|
|
@@ -616,10 +1038,48 @@ class IntercomBroker {
|
|
|
616
1038
|
}
|
|
617
1039
|
|
|
618
1040
|
const requestId = randomUUID();
|
|
1041
|
+
let reservation: ReturnType<DeliveredMessageCache["reserve"]>;
|
|
1042
|
+
try {
|
|
1043
|
+
reservation = this.deliveredMessages.reserve(message.id, signature);
|
|
1044
|
+
} catch {
|
|
1045
|
+
this.failPendingStageNotification(
|
|
1046
|
+
socket,
|
|
1047
|
+
message.id,
|
|
1048
|
+
attemptId,
|
|
1049
|
+
"Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
|
|
1050
|
+
);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (reservation === "match") {
|
|
1054
|
+
writeMessageIfOpen(socket, {
|
|
1055
|
+
type: "delivered",
|
|
1056
|
+
messageId: message.id,
|
|
1057
|
+
...(attemptId === undefined ? {} : { attemptId }),
|
|
1058
|
+
});
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (reservation !== "recorded") {
|
|
1062
|
+
this.failPendingStageNotification(
|
|
1063
|
+
socket,
|
|
1064
|
+
message.id,
|
|
1065
|
+
attemptId,
|
|
1066
|
+
reservation === "capacity"
|
|
1067
|
+
? "Intercom accepted-delivery capacity is full; refusing delivery without evicting live retry authority"
|
|
1068
|
+
: reservation === "conflict"
|
|
1069
|
+
? `Intercom message ID '${message.id}' was already delivered with a different target or payload`
|
|
1070
|
+
: reservation === "uncertain"
|
|
1071
|
+
? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
|
|
1072
|
+
: "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
|
|
1073
|
+
reservation === "conflict" ? "message_id_conflict" : undefined,
|
|
1074
|
+
);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
619
1078
|
const timeout = setTimeout(() => {
|
|
620
1079
|
const pending = this.pendingStageNotificationAcknowledgments.get(requestId);
|
|
621
1080
|
if (pending === undefined) return;
|
|
622
1081
|
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
1082
|
+
this.deliveredMessages.forget(pending.messageId, pending.signature);
|
|
623
1083
|
this.failPendingStageNotification(
|
|
624
1084
|
pending.senderSocket,
|
|
625
1085
|
pending.messageId,
|
|
@@ -636,7 +1096,22 @@ class IntercomBroker {
|
|
|
636
1096
|
signature,
|
|
637
1097
|
timeout,
|
|
638
1098
|
});
|
|
639
|
-
|
|
1099
|
+
if (
|
|
1100
|
+
!writeMessageIfOpen(target.socket, {
|
|
1101
|
+
type: "pending_stage_notification",
|
|
1102
|
+
requestId,
|
|
1103
|
+
from: owner.info,
|
|
1104
|
+
message,
|
|
1105
|
+
})
|
|
1106
|
+
) {
|
|
1107
|
+
// The recipient's socket stopped accepting frames between resolving it and
|
|
1108
|
+
// this write. Release the acknowledgment and fail now, so the owner retries a
|
|
1109
|
+
// message that was never handed over rather than waiting out the ack timeout.
|
|
1110
|
+
clearTimeout(timeout);
|
|
1111
|
+
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
1112
|
+
this.failPendingStageNotification(socket, message.id, attemptId, "Session not found");
|
|
1113
|
+
this.deliveredMessages.forget(message.id, signature);
|
|
1114
|
+
}
|
|
640
1115
|
}
|
|
641
1116
|
|
|
642
1117
|
private settlePendingStageNotification(
|
|
@@ -649,6 +1124,7 @@ class IntercomBroker {
|
|
|
649
1124
|
clearTimeout(pending.timeout);
|
|
650
1125
|
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
651
1126
|
if (!delivered) {
|
|
1127
|
+
this.deliveredMessages.forget(pending.messageId, pending.signature);
|
|
652
1128
|
this.failPendingStageNotification(
|
|
653
1129
|
pending.senderSocket,
|
|
654
1130
|
pending.messageId,
|
|
@@ -657,8 +1133,29 @@ class IntercomBroker {
|
|
|
657
1133
|
);
|
|
658
1134
|
return;
|
|
659
1135
|
}
|
|
660
|
-
|
|
661
|
-
|
|
1136
|
+
let accepted: ReturnType<DeliveredMessageCache["accept"]>;
|
|
1137
|
+
try {
|
|
1138
|
+
accepted = this.deliveredMessages.accept(pending.messageId, pending.signature);
|
|
1139
|
+
} catch {
|
|
1140
|
+
this.failPendingStageNotification(
|
|
1141
|
+
pending.senderSocket,
|
|
1142
|
+
pending.messageId,
|
|
1143
|
+
pending.attemptId,
|
|
1144
|
+
"Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
|
|
1145
|
+
);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (accepted !== "accepted") {
|
|
1149
|
+
this.failPendingStageNotification(
|
|
1150
|
+
pending.senderSocket,
|
|
1151
|
+
pending.messageId,
|
|
1152
|
+
pending.attemptId,
|
|
1153
|
+
"Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
|
|
1154
|
+
accepted === "conflict" ? "message_id_conflict" : undefined,
|
|
1155
|
+
);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
662
1159
|
type: "delivered",
|
|
663
1160
|
messageId: pending.messageId,
|
|
664
1161
|
...(pending.attemptId === undefined ? {} : { attemptId: pending.attemptId }),
|
|
@@ -706,8 +1203,8 @@ class IntercomBroker {
|
|
|
706
1203
|
? this.supervisorChannel.claim(clientMessage.supervisor.capability, childName)
|
|
707
1204
|
: undefined;
|
|
708
1205
|
if (!claimedSupervisorId || !this.sessions.has(claimedSupervisorId)) {
|
|
709
|
-
|
|
710
|
-
|
|
1206
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid supervisor authorization" });
|
|
1207
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
711
1208
|
return;
|
|
712
1209
|
}
|
|
713
1210
|
supervisorId = claimedSupervisorId;
|
|
@@ -743,7 +1240,7 @@ class IntercomBroker {
|
|
|
743
1240
|
this.shutdownTimer = null;
|
|
744
1241
|
}
|
|
745
1242
|
|
|
746
|
-
|
|
1243
|
+
writeMessageIfOpen(socket, supervisorId
|
|
747
1244
|
? { type: "registered", sessionId: id, supervisorSessionId: supervisorId }
|
|
748
1245
|
: { type: "registered", sessionId: id });
|
|
749
1246
|
this.broadcastToMemberships({ type: "session_joined", session: info }, sessionGroups(info), id);
|
|
@@ -773,11 +1270,12 @@ class IntercomBroker {
|
|
|
773
1270
|
? sessionsInGroup(this.sessions, clientMessage.group)
|
|
774
1271
|
: []
|
|
775
1272
|
: sessionsVisibleTo(this.sessions, requester.info);
|
|
776
|
-
|
|
1273
|
+
writeMessageIfOpen(socket, {
|
|
777
1274
|
type: "sessions",
|
|
778
1275
|
requestId: clientMessage.requestId,
|
|
779
1276
|
sessions,
|
|
780
1277
|
workflowStages: this.workflowStagesVisibleTo(requester, clientMessage.group),
|
|
1278
|
+
workflowFutureStages: this.workflowFutureStagesVisibleTo(requester, clientMessage.group),
|
|
781
1279
|
});
|
|
782
1280
|
break;
|
|
783
1281
|
}
|
|
@@ -786,7 +1284,7 @@ class IntercomBroker {
|
|
|
786
1284
|
if (typeof clientMessage.requestId !== "string") throw new Error("Invalid list_groups message");
|
|
787
1285
|
const requester = currentId ? this.sessions.get(currentId) : undefined;
|
|
788
1286
|
if (requester === undefined) throw new Error("Session not found");
|
|
789
|
-
|
|
1287
|
+
writeMessageIfOpen(socket, {
|
|
790
1288
|
type: "groups",
|
|
791
1289
|
requestId: clientMessage.requestId,
|
|
792
1290
|
groups: knownGroupSummaries(this.sessions, requester.info),
|
|
@@ -808,7 +1306,7 @@ class IntercomBroker {
|
|
|
808
1306
|
childName,
|
|
809
1307
|
typeof clientMessage.capability === "string" ? clientMessage.capability : undefined,
|
|
810
1308
|
);
|
|
811
|
-
|
|
1309
|
+
writeMessageIfOpen(socket, {
|
|
812
1310
|
type: "supervisor_authorized",
|
|
813
1311
|
requestId: clientMessage.requestId,
|
|
814
1312
|
capability,
|
|
@@ -838,28 +1336,52 @@ class IntercomBroker {
|
|
|
838
1336
|
(activeExisting !== undefined &&
|
|
839
1337
|
(activeExisting.capability !== clientMessage.capability || activeExisting.group !== ownerGroup))
|
|
840
1338
|
) {
|
|
841
|
-
|
|
842
|
-
|
|
1339
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
|
|
1340
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
843
1341
|
return;
|
|
844
1342
|
}
|
|
845
1343
|
if (
|
|
846
1344
|
clientMessage.stages !== undefined &&
|
|
847
|
-
(!isWorkflowStageRosterAnnouncements(
|
|
1345
|
+
(!isWorkflowStageRosterAnnouncements(
|
|
1346
|
+
clientMessage.stages,
|
|
1347
|
+
clientMessage.runId,
|
|
1348
|
+
normalizeGroup(clientMessage.group),
|
|
1349
|
+
) ||
|
|
848
1350
|
!clientMessage.stages.every((stage) => invocationOwnsGroup(ownerGroup, stage.group)))
|
|
849
1351
|
) {
|
|
850
|
-
|
|
851
|
-
|
|
1352
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow-stage roster" });
|
|
1353
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
if (
|
|
1357
|
+
clientMessage.possibleStages !== undefined &&
|
|
1358
|
+
!isWorkflowPossibleStageAnnouncements(
|
|
1359
|
+
clientMessage.possibleStages,
|
|
1360
|
+
clientMessage.runId,
|
|
1361
|
+
normalizeGroup(clientMessage.group),
|
|
1362
|
+
)
|
|
1363
|
+
) {
|
|
1364
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow possible-stage roster" });
|
|
1365
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
852
1366
|
return;
|
|
853
1367
|
}
|
|
1368
|
+
// D7 (slice 4): presence replaces, absence keeps — a terminal root publishes `[]`
|
|
1369
|
+
// so the broker drops every future row, while nested runs' re-announcements (which
|
|
1370
|
+
// never carry the field) preserve the root's rows.
|
|
1371
|
+
const possibleStages =
|
|
1372
|
+
clientMessage.possibleStages !== undefined
|
|
1373
|
+
? clientMessage.possibleStages
|
|
1374
|
+
: this.workflowRosters.get(clientMessage.runId)?.possibleStages;
|
|
854
1375
|
if (activeExisting !== undefined && activeExisting.sessionId !== currentId) {
|
|
855
1376
|
// A stage replays the process-shared owner announcement before registering its live aliases.
|
|
856
1377
|
// It may publish the materialized roster, but the original workflow owner must continue to
|
|
857
1378
|
// handle pending delivery and own roster cleanup.
|
|
858
|
-
if (clientMessage.stages !== undefined) {
|
|
1379
|
+
if (clientMessage.stages !== undefined || possibleStages !== undefined) {
|
|
859
1380
|
this.workflowRosters.set(clientMessage.runId, {
|
|
860
1381
|
ownerSessionId: activeExisting.sessionId,
|
|
861
1382
|
group: ownerGroup,
|
|
862
|
-
stages: clientMessage.stages,
|
|
1383
|
+
stages: clientMessage.stages ?? [],
|
|
1384
|
+
...(possibleStages === undefined ? {} : { possibleStages }),
|
|
863
1385
|
});
|
|
864
1386
|
}
|
|
865
1387
|
break;
|
|
@@ -869,11 +1391,12 @@ class IntercomBroker {
|
|
|
869
1391
|
group: ownerGroup,
|
|
870
1392
|
capability: clientMessage.capability,
|
|
871
1393
|
});
|
|
872
|
-
if (clientMessage.stages !== undefined) {
|
|
1394
|
+
if (clientMessage.stages !== undefined || possibleStages !== undefined) {
|
|
873
1395
|
this.workflowRosters.set(clientMessage.runId, {
|
|
874
1396
|
ownerSessionId: currentId,
|
|
875
1397
|
group: ownerGroup,
|
|
876
|
-
stages: clientMessage.stages,
|
|
1398
|
+
stages: clientMessage.stages ?? [],
|
|
1399
|
+
...(possibleStages === undefined ? {} : { possibleStages }),
|
|
877
1400
|
});
|
|
878
1401
|
}
|
|
879
1402
|
break;
|
|
@@ -888,15 +1411,22 @@ class IntercomBroker {
|
|
|
888
1411
|
!Array.isArray(clientMessage.stageKeys) ||
|
|
889
1412
|
clientMessage.stageKeys.length < 1 ||
|
|
890
1413
|
clientMessage.stageKeys.length > 2 ||
|
|
891
|
-
!clientMessage.stageKeys.every(
|
|
892
|
-
(stageKey) =>
|
|
893
|
-
typeof stageKey === "string" &&
|
|
894
|
-
stageKey.length > 0 &&
|
|
895
|
-
parsePendingStageTarget(`${clientMessage.runId}:${stageKey}`) !== undefined,
|
|
896
|
-
)
|
|
1414
|
+
!clientMessage.stageKeys.every((stageKey) => typeof stageKey === "string" && stageKey.length > 0)
|
|
897
1415
|
) {
|
|
898
1416
|
throw new Error("Invalid live workflow-stage route registration");
|
|
899
1417
|
}
|
|
1418
|
+
if (!clientMessage.stageKeys.every((stageKey) => !stageKey.includes("/") && !stageKey.includes("*"))) {
|
|
1419
|
+
// A stage name containing "/" or "*" cannot be a canonical path segment, but throwing
|
|
1420
|
+
// here reaches the framing reader's onError and destroys the stage session's whole
|
|
1421
|
+
// broker connection. Refuse orderly like every neighbouring rejection; host clients
|
|
1422
|
+
// filter such name keys and register the stage-id key instead.
|
|
1423
|
+
writeMessageIfOpen(socket, {
|
|
1424
|
+
type: "registration_failed",
|
|
1425
|
+
reason: "Live workflow-stage route keys must be single path segments",
|
|
1426
|
+
});
|
|
1427
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
900
1430
|
const ownerRegistration = this.pendingStageRoutes.get(clientMessage.runId);
|
|
901
1431
|
const registeringSession = this.sessions.get(currentId);
|
|
902
1432
|
if (
|
|
@@ -915,11 +1445,11 @@ class IntercomBroker {
|
|
|
915
1445
|
clientMessage.capability,
|
|
916
1446
|
)
|
|
917
1447
|
) {
|
|
918
|
-
|
|
1448
|
+
writeMessageIfOpen(socket, {
|
|
919
1449
|
type: "registration_failed",
|
|
920
1450
|
reason: "Live workflow-stage route is owned by another active session",
|
|
921
1451
|
});
|
|
922
|
-
|
|
1452
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
923
1453
|
return;
|
|
924
1454
|
}
|
|
925
1455
|
break;
|
|
@@ -933,8 +1463,14 @@ class IntercomBroker {
|
|
|
933
1463
|
clientMessage.outcome !== "forward" &&
|
|
934
1464
|
clientMessage.outcome !== "refused") ||
|
|
935
1465
|
(clientMessage.position !== undefined && typeof clientMessage.position !== "number") ||
|
|
1466
|
+
(clientMessage.target !== undefined && typeof clientMessage.target !== "string") ||
|
|
1467
|
+
(clientMessage.outcome === "forward" && typeof clientMessage.target !== "string") ||
|
|
936
1468
|
(clientMessage.reason !== undefined && typeof clientMessage.reason !== "string") ||
|
|
937
|
-
(clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict")
|
|
1469
|
+
(clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict") ||
|
|
1470
|
+
(clientMessage.notInKnownSet !== undefined && clientMessage.notInKnownSet !== true) ||
|
|
1471
|
+
(clientMessage.forwardTargets !== undefined &&
|
|
1472
|
+
(!Array.isArray(clientMessage.forwardTargets) ||
|
|
1473
|
+
!clientMessage.forwardTargets.every((entry) => typeof entry === "string")))
|
|
938
1474
|
) {
|
|
939
1475
|
throw new Error("Invalid pending-stage message result");
|
|
940
1476
|
}
|
|
@@ -943,8 +1479,11 @@ class IntercomBroker {
|
|
|
943
1479
|
clientMessage.requestId,
|
|
944
1480
|
clientMessage.outcome,
|
|
945
1481
|
clientMessage.position,
|
|
1482
|
+
clientMessage.target,
|
|
946
1483
|
clientMessage.reason,
|
|
947
1484
|
clientMessage.reasonCode,
|
|
1485
|
+
clientMessage.notInKnownSet === true ? true : undefined,
|
|
1486
|
+
Array.isArray(clientMessage.forwardTargets) ? [...clientMessage.forwardTargets] : undefined,
|
|
948
1487
|
);
|
|
949
1488
|
break;
|
|
950
1489
|
}
|
|
@@ -957,12 +1496,14 @@ class IntercomBroker {
|
|
|
957
1496
|
currentId,
|
|
958
1497
|
this.sessions,
|
|
959
1498
|
this.deliveredMessages,
|
|
960
|
-
|
|
1499
|
+
writeMessageIfOpen,
|
|
961
1500
|
this.supervisorChannel,
|
|
962
1501
|
this.pendingQuestions,
|
|
963
1502
|
this.routePendingStage,
|
|
964
1503
|
this.resolveLiveWorkflowStage,
|
|
965
1504
|
this.canControlLiveWorkflowStage,
|
|
1505
|
+
this.resolveLegacyWorkflowStageTarget,
|
|
1506
|
+
writeMessageWithOutcome,
|
|
966
1507
|
);
|
|
967
1508
|
break;
|
|
968
1509
|
}
|
|
@@ -988,7 +1529,7 @@ class IntercomBroker {
|
|
|
988
1529
|
clientMessage,
|
|
989
1530
|
currentId,
|
|
990
1531
|
this.sessions,
|
|
991
|
-
(target, message) =>
|
|
1532
|
+
(target, message) => writeMessageIfOpen(target, message),
|
|
992
1533
|
);
|
|
993
1534
|
break;
|
|
994
1535
|
}
|
|
@@ -1006,7 +1547,7 @@ class IntercomBroker {
|
|
|
1006
1547
|
for (const question of this.pendingQuestions.takeForTarget(sessionId)) {
|
|
1007
1548
|
const asker = this.sessions.get(question.senderSessionId);
|
|
1008
1549
|
if (!asker) continue;
|
|
1009
|
-
|
|
1550
|
+
writeMessageIfOpen(asker.socket, {
|
|
1010
1551
|
type: "peer_disconnected",
|
|
1011
1552
|
replyTo: question.messageId,
|
|
1012
1553
|
peerSessionId: departed.info.id,
|
|
@@ -1024,7 +1565,7 @@ class IntercomBroker {
|
|
|
1024
1565
|
clearTimeout(pending.timeout);
|
|
1025
1566
|
this.pendingStageAcknowledgments.delete(requestId);
|
|
1026
1567
|
this.releasePendingStageAcknowledgment(requestId);
|
|
1027
|
-
|
|
1568
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
1028
1569
|
type: "delivery_failed",
|
|
1029
1570
|
messageId: pending.messageId,
|
|
1030
1571
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -1035,6 +1576,7 @@ class IntercomBroker {
|
|
|
1035
1576
|
if (pending.ownerSessionId !== sessionId && pending.recipientSessionId !== sessionId) continue;
|
|
1036
1577
|
clearTimeout(pending.timeout);
|
|
1037
1578
|
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
1579
|
+
this.deliveredMessages.forget(pending.messageId, pending.signature);
|
|
1038
1580
|
if (pending.ownerSessionId === sessionId) continue;
|
|
1039
1581
|
this.failPendingStageNotification(
|
|
1040
1582
|
pending.senderSocket,
|
|
@@ -1056,7 +1598,7 @@ class IntercomBroker {
|
|
|
1056
1598
|
private broadcastToMemberships(msg: BrokerMessage, groups: ReadonlySet<string>, exclude?: string): void {
|
|
1057
1599
|
for (const [id, session] of this.sessions) {
|
|
1058
1600
|
if (id !== exclude && [...groups].some((group) => hasGroup(sessionGroups(session.info), group))) {
|
|
1059
|
-
|
|
1601
|
+
writeMessageIfOpen(session.socket, msg);
|
|
1060
1602
|
}
|
|
1061
1603
|
}
|
|
1062
1604
|
}
|
|
@@ -1093,6 +1635,7 @@ class IntercomBroker {
|
|
|
1093
1635
|
this.liveWorkflowStageRouteActivations.clear();
|
|
1094
1636
|
for (const pending of this.pendingStageNotificationAcknowledgments.values()) clearTimeout(pending.timeout);
|
|
1095
1637
|
this.pendingStageNotificationAcknowledgments.clear();
|
|
1638
|
+
this.deliveredMessages.close();
|
|
1096
1639
|
const ownsRuntime = this.readPidFile() === process.pid;
|
|
1097
1640
|
this.unlinkRuntimeFilesIfOwned();
|
|
1098
1641
|
// Node unlinks a Unix socket path on server.close() even after a successor
|