@bastani/atomic 0.9.18-alpha.5 → 0.9.18-alpha.7
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 +88 -0
- package/README.md +3 -3
- package/dist/builtin/intercom/CHANGELOG.md +39 -0
- package/dist/builtin/intercom/README.md +22 -2
- package/dist/builtin/intercom/broker/broker.ts +676 -128
- package/dist/builtin/intercom/broker/client.ts +151 -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 +1410 -212
- 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 +27 -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 +46 -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 +13 -5
- 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/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/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 +15 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +3 -0
- 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.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 +18 -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/edit.d.ts +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +128 -3
- package/dist/core/tools/edit.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/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +8 -8
- 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 +6 -11
- 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 +3 -19
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.js +1 -1
- package/dist/modes/interactive/components/settings-selector-items.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 +4 -11
- 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/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/custom-provider.md +2 -0
- package/docs/development.md +17 -0
- package/docs/docs.json +14 -2
- package/docs/extensions.md +9 -4
- package/docs/intercom.md +41 -7
- package/docs/keybindings.md +8 -6
- package/docs/models/evals.md +394 -0
- package/docs/models/model-selection.md +54 -27
- package/docs/models/pareto-efficiency.md +33 -16
- package/docs/models.md +37 -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/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;
|
|
503
851
|
}
|
|
504
|
-
|
|
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;
|
|
873
|
+
}
|
|
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 }),
|
|
@@ -686,6 +1183,10 @@ class IntercomBroker {
|
|
|
686
1183
|
if (!isSessionRegistration(clientMessage.session)) {
|
|
687
1184
|
throw new Error("Invalid register message");
|
|
688
1185
|
}
|
|
1186
|
+
if (clientMessage.registrationGroup !== undefined &&
|
|
1187
|
+
(typeof clientMessage.registrationGroup !== "string" || clientMessage.registrationGroup.trim().length === 0)) {
|
|
1188
|
+
throw new Error("Invalid registration group");
|
|
1189
|
+
}
|
|
689
1190
|
if (clientMessage.returnAddress !== undefined &&
|
|
690
1191
|
(typeof clientMessage.returnAddress !== "string" || clientMessage.returnAddress.length === 0)) {
|
|
691
1192
|
throw new Error("Invalid return address");
|
|
@@ -706,8 +1207,8 @@ class IntercomBroker {
|
|
|
706
1207
|
? this.supervisorChannel.claim(clientMessage.supervisor.capability, childName)
|
|
707
1208
|
: undefined;
|
|
708
1209
|
if (!claimedSupervisorId || !this.sessions.has(claimedSupervisorId)) {
|
|
709
|
-
|
|
710
|
-
|
|
1210
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid supervisor authorization" });
|
|
1211
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
711
1212
|
return;
|
|
712
1213
|
}
|
|
713
1214
|
supervisorId = claimedSupervisorId;
|
|
@@ -725,7 +1226,8 @@ class IntercomBroker {
|
|
|
725
1226
|
this.sessions.set(id, {
|
|
726
1227
|
socket,
|
|
727
1228
|
info,
|
|
728
|
-
registrationGroup:
|
|
1229
|
+
registrationGroup: typeof clientMessage.registrationGroup === "string"
|
|
1230
|
+
? normalizeGroup(clientMessage.registrationGroup) : legacyGroup,
|
|
729
1231
|
...(typeof info.name === "string" && info.name.trim().length > 0
|
|
730
1232
|
? { registrationName: info.name.trim() }
|
|
731
1233
|
: {}),
|
|
@@ -743,7 +1245,7 @@ class IntercomBroker {
|
|
|
743
1245
|
this.shutdownTimer = null;
|
|
744
1246
|
}
|
|
745
1247
|
|
|
746
|
-
|
|
1248
|
+
writeMessageIfOpen(socket, supervisorId
|
|
747
1249
|
? { type: "registered", sessionId: id, supervisorSessionId: supervisorId }
|
|
748
1250
|
: { type: "registered", sessionId: id });
|
|
749
1251
|
this.broadcastToMemberships({ type: "session_joined", session: info }, sessionGroups(info), id);
|
|
@@ -773,11 +1275,12 @@ class IntercomBroker {
|
|
|
773
1275
|
? sessionsInGroup(this.sessions, clientMessage.group)
|
|
774
1276
|
: []
|
|
775
1277
|
: sessionsVisibleTo(this.sessions, requester.info);
|
|
776
|
-
|
|
1278
|
+
writeMessageIfOpen(socket, {
|
|
777
1279
|
type: "sessions",
|
|
778
1280
|
requestId: clientMessage.requestId,
|
|
779
1281
|
sessions,
|
|
780
1282
|
workflowStages: this.workflowStagesVisibleTo(requester, clientMessage.group),
|
|
1283
|
+
workflowFutureStages: this.workflowFutureStagesVisibleTo(requester, clientMessage.group),
|
|
781
1284
|
});
|
|
782
1285
|
break;
|
|
783
1286
|
}
|
|
@@ -786,7 +1289,7 @@ class IntercomBroker {
|
|
|
786
1289
|
if (typeof clientMessage.requestId !== "string") throw new Error("Invalid list_groups message");
|
|
787
1290
|
const requester = currentId ? this.sessions.get(currentId) : undefined;
|
|
788
1291
|
if (requester === undefined) throw new Error("Session not found");
|
|
789
|
-
|
|
1292
|
+
writeMessageIfOpen(socket, {
|
|
790
1293
|
type: "groups",
|
|
791
1294
|
requestId: clientMessage.requestId,
|
|
792
1295
|
groups: knownGroupSummaries(this.sessions, requester.info),
|
|
@@ -808,7 +1311,7 @@ class IntercomBroker {
|
|
|
808
1311
|
childName,
|
|
809
1312
|
typeof clientMessage.capability === "string" ? clientMessage.capability : undefined,
|
|
810
1313
|
);
|
|
811
|
-
|
|
1314
|
+
writeMessageIfOpen(socket, {
|
|
812
1315
|
type: "supervisor_authorized",
|
|
813
1316
|
requestId: clientMessage.requestId,
|
|
814
1317
|
capability,
|
|
@@ -838,28 +1341,52 @@ class IntercomBroker {
|
|
|
838
1341
|
(activeExisting !== undefined &&
|
|
839
1342
|
(activeExisting.capability !== clientMessage.capability || activeExisting.group !== ownerGroup))
|
|
840
1343
|
) {
|
|
841
|
-
|
|
842
|
-
|
|
1344
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
|
|
1345
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
843
1346
|
return;
|
|
844
1347
|
}
|
|
845
1348
|
if (
|
|
846
1349
|
clientMessage.stages !== undefined &&
|
|
847
|
-
(!isWorkflowStageRosterAnnouncements(
|
|
1350
|
+
(!isWorkflowStageRosterAnnouncements(
|
|
1351
|
+
clientMessage.stages,
|
|
1352
|
+
clientMessage.runId,
|
|
1353
|
+
normalizeGroup(clientMessage.group),
|
|
1354
|
+
) ||
|
|
848
1355
|
!clientMessage.stages.every((stage) => invocationOwnsGroup(ownerGroup, stage.group)))
|
|
849
1356
|
) {
|
|
850
|
-
|
|
851
|
-
|
|
1357
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow-stage roster" });
|
|
1358
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (
|
|
1362
|
+
clientMessage.possibleStages !== undefined &&
|
|
1363
|
+
!isWorkflowPossibleStageAnnouncements(
|
|
1364
|
+
clientMessage.possibleStages,
|
|
1365
|
+
clientMessage.runId,
|
|
1366
|
+
normalizeGroup(clientMessage.group),
|
|
1367
|
+
)
|
|
1368
|
+
) {
|
|
1369
|
+
writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow possible-stage roster" });
|
|
1370
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
852
1371
|
return;
|
|
853
1372
|
}
|
|
1373
|
+
// D7 (slice 4): presence replaces, absence keeps — a terminal root publishes `[]`
|
|
1374
|
+
// so the broker drops every future row, while nested runs' re-announcements (which
|
|
1375
|
+
// never carry the field) preserve the root's rows.
|
|
1376
|
+
const possibleStages =
|
|
1377
|
+
clientMessage.possibleStages !== undefined
|
|
1378
|
+
? clientMessage.possibleStages
|
|
1379
|
+
: this.workflowRosters.get(clientMessage.runId)?.possibleStages;
|
|
854
1380
|
if (activeExisting !== undefined && activeExisting.sessionId !== currentId) {
|
|
855
1381
|
// A stage replays the process-shared owner announcement before registering its live aliases.
|
|
856
1382
|
// It may publish the materialized roster, but the original workflow owner must continue to
|
|
857
1383
|
// handle pending delivery and own roster cleanup.
|
|
858
|
-
if (clientMessage.stages !== undefined) {
|
|
1384
|
+
if (clientMessage.stages !== undefined || possibleStages !== undefined) {
|
|
859
1385
|
this.workflowRosters.set(clientMessage.runId, {
|
|
860
1386
|
ownerSessionId: activeExisting.sessionId,
|
|
861
1387
|
group: ownerGroup,
|
|
862
|
-
stages: clientMessage.stages,
|
|
1388
|
+
stages: clientMessage.stages ?? [],
|
|
1389
|
+
...(possibleStages === undefined ? {} : { possibleStages }),
|
|
863
1390
|
});
|
|
864
1391
|
}
|
|
865
1392
|
break;
|
|
@@ -869,11 +1396,12 @@ class IntercomBroker {
|
|
|
869
1396
|
group: ownerGroup,
|
|
870
1397
|
capability: clientMessage.capability,
|
|
871
1398
|
});
|
|
872
|
-
if (clientMessage.stages !== undefined) {
|
|
1399
|
+
if (clientMessage.stages !== undefined || possibleStages !== undefined) {
|
|
873
1400
|
this.workflowRosters.set(clientMessage.runId, {
|
|
874
1401
|
ownerSessionId: currentId,
|
|
875
1402
|
group: ownerGroup,
|
|
876
|
-
stages: clientMessage.stages,
|
|
1403
|
+
stages: clientMessage.stages ?? [],
|
|
1404
|
+
...(possibleStages === undefined ? {} : { possibleStages }),
|
|
877
1405
|
});
|
|
878
1406
|
}
|
|
879
1407
|
break;
|
|
@@ -888,15 +1416,22 @@ class IntercomBroker {
|
|
|
888
1416
|
!Array.isArray(clientMessage.stageKeys) ||
|
|
889
1417
|
clientMessage.stageKeys.length < 1 ||
|
|
890
1418
|
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
|
-
)
|
|
1419
|
+
!clientMessage.stageKeys.every((stageKey) => typeof stageKey === "string" && stageKey.length > 0)
|
|
897
1420
|
) {
|
|
898
1421
|
throw new Error("Invalid live workflow-stage route registration");
|
|
899
1422
|
}
|
|
1423
|
+
if (!clientMessage.stageKeys.every((stageKey) => !stageKey.includes("/") && !stageKey.includes("*"))) {
|
|
1424
|
+
// A stage name containing "/" or "*" cannot be a canonical path segment, but throwing
|
|
1425
|
+
// here reaches the framing reader's onError and destroys the stage session's whole
|
|
1426
|
+
// broker connection. Refuse orderly like every neighbouring rejection; host clients
|
|
1427
|
+
// filter such name keys and register the stage-id key instead.
|
|
1428
|
+
writeMessageIfOpen(socket, {
|
|
1429
|
+
type: "registration_failed",
|
|
1430
|
+
reason: "Live workflow-stage route keys must be single path segments",
|
|
1431
|
+
});
|
|
1432
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
900
1435
|
const ownerRegistration = this.pendingStageRoutes.get(clientMessage.runId);
|
|
901
1436
|
const registeringSession = this.sessions.get(currentId);
|
|
902
1437
|
if (
|
|
@@ -915,11 +1450,11 @@ class IntercomBroker {
|
|
|
915
1450
|
clientMessage.capability,
|
|
916
1451
|
)
|
|
917
1452
|
) {
|
|
918
|
-
|
|
1453
|
+
writeMessageIfOpen(socket, {
|
|
919
1454
|
type: "registration_failed",
|
|
920
1455
|
reason: "Live workflow-stage route is owned by another active session",
|
|
921
1456
|
});
|
|
922
|
-
|
|
1457
|
+
this.endRefusedConnection(socket, currentId, setId);
|
|
923
1458
|
return;
|
|
924
1459
|
}
|
|
925
1460
|
break;
|
|
@@ -933,8 +1468,14 @@ class IntercomBroker {
|
|
|
933
1468
|
clientMessage.outcome !== "forward" &&
|
|
934
1469
|
clientMessage.outcome !== "refused") ||
|
|
935
1470
|
(clientMessage.position !== undefined && typeof clientMessage.position !== "number") ||
|
|
1471
|
+
(clientMessage.target !== undefined && typeof clientMessage.target !== "string") ||
|
|
1472
|
+
(clientMessage.outcome === "forward" && typeof clientMessage.target !== "string") ||
|
|
936
1473
|
(clientMessage.reason !== undefined && typeof clientMessage.reason !== "string") ||
|
|
937
|
-
(clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict")
|
|
1474
|
+
(clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict") ||
|
|
1475
|
+
(clientMessage.notInKnownSet !== undefined && clientMessage.notInKnownSet !== true) ||
|
|
1476
|
+
(clientMessage.forwardTargets !== undefined &&
|
|
1477
|
+
(!Array.isArray(clientMessage.forwardTargets) ||
|
|
1478
|
+
!clientMessage.forwardTargets.every((entry) => typeof entry === "string")))
|
|
938
1479
|
) {
|
|
939
1480
|
throw new Error("Invalid pending-stage message result");
|
|
940
1481
|
}
|
|
@@ -943,8 +1484,11 @@ class IntercomBroker {
|
|
|
943
1484
|
clientMessage.requestId,
|
|
944
1485
|
clientMessage.outcome,
|
|
945
1486
|
clientMessage.position,
|
|
1487
|
+
clientMessage.target,
|
|
946
1488
|
clientMessage.reason,
|
|
947
1489
|
clientMessage.reasonCode,
|
|
1490
|
+
clientMessage.notInKnownSet === true ? true : undefined,
|
|
1491
|
+
Array.isArray(clientMessage.forwardTargets) ? [...clientMessage.forwardTargets] : undefined,
|
|
948
1492
|
);
|
|
949
1493
|
break;
|
|
950
1494
|
}
|
|
@@ -957,12 +1501,14 @@ class IntercomBroker {
|
|
|
957
1501
|
currentId,
|
|
958
1502
|
this.sessions,
|
|
959
1503
|
this.deliveredMessages,
|
|
960
|
-
|
|
1504
|
+
writeMessageIfOpen,
|
|
961
1505
|
this.supervisorChannel,
|
|
962
1506
|
this.pendingQuestions,
|
|
963
1507
|
this.routePendingStage,
|
|
964
1508
|
this.resolveLiveWorkflowStage,
|
|
965
1509
|
this.canControlLiveWorkflowStage,
|
|
1510
|
+
this.resolveLegacyWorkflowStageTarget,
|
|
1511
|
+
writeMessageWithOutcome,
|
|
966
1512
|
);
|
|
967
1513
|
break;
|
|
968
1514
|
}
|
|
@@ -988,7 +1534,7 @@ class IntercomBroker {
|
|
|
988
1534
|
clientMessage,
|
|
989
1535
|
currentId,
|
|
990
1536
|
this.sessions,
|
|
991
|
-
(target, message) =>
|
|
1537
|
+
(target, message) => writeMessageIfOpen(target, message),
|
|
992
1538
|
);
|
|
993
1539
|
break;
|
|
994
1540
|
}
|
|
@@ -1006,7 +1552,7 @@ class IntercomBroker {
|
|
|
1006
1552
|
for (const question of this.pendingQuestions.takeForTarget(sessionId)) {
|
|
1007
1553
|
const asker = this.sessions.get(question.senderSessionId);
|
|
1008
1554
|
if (!asker) continue;
|
|
1009
|
-
|
|
1555
|
+
writeMessageIfOpen(asker.socket, {
|
|
1010
1556
|
type: "peer_disconnected",
|
|
1011
1557
|
replyTo: question.messageId,
|
|
1012
1558
|
peerSessionId: departed.info.id,
|
|
@@ -1024,7 +1570,7 @@ class IntercomBroker {
|
|
|
1024
1570
|
clearTimeout(pending.timeout);
|
|
1025
1571
|
this.pendingStageAcknowledgments.delete(requestId);
|
|
1026
1572
|
this.releasePendingStageAcknowledgment(requestId);
|
|
1027
|
-
|
|
1573
|
+
writeMessageIfOpen(pending.senderSocket, {
|
|
1028
1574
|
type: "delivery_failed",
|
|
1029
1575
|
messageId: pending.messageId,
|
|
1030
1576
|
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
@@ -1035,6 +1581,7 @@ class IntercomBroker {
|
|
|
1035
1581
|
if (pending.ownerSessionId !== sessionId && pending.recipientSessionId !== sessionId) continue;
|
|
1036
1582
|
clearTimeout(pending.timeout);
|
|
1037
1583
|
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
1584
|
+
this.deliveredMessages.forget(pending.messageId, pending.signature);
|
|
1038
1585
|
if (pending.ownerSessionId === sessionId) continue;
|
|
1039
1586
|
this.failPendingStageNotification(
|
|
1040
1587
|
pending.senderSocket,
|
|
@@ -1056,7 +1603,7 @@ class IntercomBroker {
|
|
|
1056
1603
|
private broadcastToMemberships(msg: BrokerMessage, groups: ReadonlySet<string>, exclude?: string): void {
|
|
1057
1604
|
for (const [id, session] of this.sessions) {
|
|
1058
1605
|
if (id !== exclude && [...groups].some((group) => hasGroup(sessionGroups(session.info), group))) {
|
|
1059
|
-
|
|
1606
|
+
writeMessageIfOpen(session.socket, msg);
|
|
1060
1607
|
}
|
|
1061
1608
|
}
|
|
1062
1609
|
}
|
|
@@ -1093,6 +1640,7 @@ class IntercomBroker {
|
|
|
1093
1640
|
this.liveWorkflowStageRouteActivations.clear();
|
|
1094
1641
|
for (const pending of this.pendingStageNotificationAcknowledgments.values()) clearTimeout(pending.timeout);
|
|
1095
1642
|
this.pendingStageNotificationAcknowledgments.clear();
|
|
1643
|
+
this.deliveredMessages.close();
|
|
1096
1644
|
const ownsRuntime = this.readPidFile() === process.pid;
|
|
1097
1645
|
this.unlinkRuntimeFilesIfOwned();
|
|
1098
1646
|
// Node unlinks a Unix socket path on server.close() even after a successor
|