@bastani/atomic 0.9.5-alpha.9 → 0.9.5
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 +106 -0
- package/README.md +2 -2
- package/dist/builtin/cursor/CHANGELOG.md +12 -0
- package/dist/builtin/cursor/package.json +3 -3
- package/dist/builtin/cursor/src/model-mapper.ts +6 -3
- package/dist/builtin/intercom/CHANGELOG.md +37 -0
- package/dist/builtin/intercom/README.md +4 -2
- package/dist/builtin/intercom/broker/broker.ts +6 -114
- package/dist/builtin/intercom/broker/client.ts +29 -54
- package/dist/builtin/intercom/broker/delivered-message-cache.ts +44 -0
- package/dist/builtin/intercom/broker/pending-send-registry.ts +142 -0
- package/dist/builtin/intercom/broker/send-handler.ts +106 -0
- package/dist/builtin/intercom/broker/send-signature.ts +37 -0
- package/dist/builtin/intercom/foreground-detach-handoff.ts +136 -0
- package/dist/builtin/intercom/index-heavy.ts +49 -53
- package/dist/builtin/intercom/index.ts +278 -262
- package/dist/builtin/intercom/lazy-heavy-proxy.ts +114 -0
- package/dist/builtin/intercom/lazy-subagent-ack.ts +20 -0
- package/dist/builtin/intercom/lazy-tool-execution.ts +39 -0
- package/dist/builtin/intercom/lifecycle-lease.ts +51 -0
- package/dist/builtin/intercom/lifecycle.ts +37 -20
- package/dist/builtin/intercom/package.json +3 -6
- package/dist/builtin/intercom/reply-routing.ts +17 -0
- package/dist/builtin/intercom/subagent-relay.ts +58 -12
- package/dist/builtin/intercom/types.ts +3 -3
- package/dist/builtin/mcp/CHANGELOG.md +27 -0
- package/dist/builtin/mcp/OAUTH.md +1 -0
- package/dist/builtin/mcp/README.md +4 -0
- package/dist/builtin/mcp/apps-cancellation.ts +32 -0
- package/dist/builtin/mcp/call-tool-result.ts +9 -0
- package/dist/builtin/mcp/caller-wait.ts +50 -0
- package/dist/builtin/mcp/command-registration.ts +82 -0
- package/dist/builtin/mcp/direct-tool-executor.ts +279 -0
- package/dist/builtin/mcp/direct-tools.ts +6 -239
- package/dist/builtin/mcp/host-html-template.ts +4 -2
- package/dist/builtin/mcp/index.ts +258 -250
- package/dist/builtin/mcp/init.ts +96 -87
- package/dist/builtin/mcp/mcp-auth-flow.ts +247 -237
- package/dist/builtin/mcp/mcp-callback-server.ts +89 -68
- package/dist/builtin/mcp/mcp-oauth-provider.ts +23 -1
- package/dist/builtin/mcp/metadata-hydration.ts +21 -6
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/mcp/proxy-auth.ts +4 -4
- package/dist/builtin/mcp/proxy-call.ts +111 -40
- package/dist/builtin/mcp/proxy-connect.ts +35 -15
- package/dist/builtin/mcp/proxy-info-modes.ts +49 -19
- package/dist/builtin/mcp/proxy-modes.ts +3 -3
- package/dist/builtin/mcp/session-cleanup-barrier.ts +43 -0
- package/dist/builtin/mcp/state-lease.ts +12 -0
- package/dist/builtin/mcp/ui-server.ts +8 -8
- package/dist/builtin/mcp/ui-session.ts +9 -18
- package/dist/builtin/subagents/CHANGELOG.md +66 -0
- package/dist/builtin/subagents/README.md +30 -29
- 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 +2 -2
- 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 +2 -2
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +5 -5
- package/dist/builtin/subagents/prompts/review-loop.md +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +37 -36
- package/dist/builtin/subagents/src/extension/api-lifecycle.ts +64 -0
- package/dist/builtin/subagents/src/extension/fanout-child.ts +44 -51
- package/dist/builtin/subagents/src/extension/index.ts +287 -307
- package/dist/builtin/subagents/src/extension/prompt-guidance.ts +7 -10
- package/dist/builtin/subagents/src/extension/schemas.ts +22 -5
- package/dist/builtin/subagents/src/extension/tool-description.ts +29 -0
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +13 -2
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
- package/dist/builtin/subagents/src/runs/background/async-event-journal.ts +106 -0
- package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +3 -2
- package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +13 -2
- package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +3 -0
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -1
- package/dist/builtin/subagents/src/runs/background/completion-claims.ts +189 -0
- package/dist/builtin/subagents/src/runs/background/completion-dedupe.ts +44 -7
- package/dist/builtin/subagents/src/runs/background/completion-notification.ts +34 -0
- package/dist/builtin/subagents/src/runs/background/notify.ts +72 -22
- package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +232 -0
- package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +151 -0
- package/dist/builtin/subagents/src/runs/background/result-quarantine.ts +72 -0
- package/dist/builtin/subagents/src/runs/background/result-retry-scheduler.ts +48 -0
- package/dist/builtin/subagents/src/runs/background/result-status.ts +81 -0
- package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +59 -0
- package/dist/builtin/subagents/src/runs/background/result-watcher.ts +172 -238
- package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +73 -14
- package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +17 -13
- package/dist/builtin/subagents/src/runs/background/top-level-async.ts +1 -2
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-dynamic-step.ts +1 -0
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +1 -0
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-step.ts +1 -0
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +7 -9
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-types.ts +3 -13
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +2 -15
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt-finalize.ts +2 -2
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +53 -44
- package/dist/builtin/subagents/src/runs/foreground/execution-detach-reservations.ts +48 -0
- package/dist/builtin/subagents/src/runs/foreground/execution-detach-route.ts +17 -0
- package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +39 -26
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +3 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-chain.ts +3 -51
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +6 -11
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +11 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +5 -112
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +68 -118
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +79 -7
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +2 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +3 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +2 -2
- package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +27 -11
- package/dist/builtin/subagents/src/shared/artifacts.ts +22 -11
- package/dist/builtin/subagents/src/shared/event-jsonl-writer.ts +294 -0
- package/dist/builtin/subagents/src/shared/exclusive-file-publication.ts +44 -0
- package/dist/builtin/subagents/src/shared/jsonl-writer.ts +1 -0
- package/dist/builtin/subagents/src/shared/model-info.ts +2 -2
- package/dist/builtin/subagents/src/shared/settings.ts +20 -10
- package/dist/builtin/subagents/src/shared/types-async.ts +3 -1
- package/dist/builtin/subagents/src/shared/types-config.ts +2 -0
- package/dist/builtin/subagents/src/shared/types-runtime.ts +3 -2
- package/dist/builtin/subagents/src/slash/slash-commands.ts +9 -12
- package/dist/builtin/subagents/src/slash/slash-live-state.ts +63 -32
- package/dist/builtin/subagents/src/tui/render-result.ts +7 -0
- package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -0
- package/dist/builtin/subagents/src/tui/render-widget.ts +128 -89
- package/dist/builtin/web-access/CHANGELOG.md +28 -0
- package/dist/builtin/web-access/README.md +4 -0
- package/dist/builtin/web-access/content-tools.ts +8 -3
- package/dist/builtin/web-access/index-heavy.ts +1 -1
- package/dist/builtin/web-access/index.ts +190 -46
- package/dist/builtin/web-access/lifecycle-lease.ts +38 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/web-access/result-renderers.ts +1 -1
- package/dist/builtin/web-access/summary-review.ts +1 -1
- package/dist/builtin/web-access/web-search-activity.ts +1 -1
- package/dist/builtin/web-access/web-search-return.ts +7 -0
- package/dist/builtin/web-access/web-search-summary.ts +1 -1
- package/dist/builtin/web-access/web-search-tool.ts +4 -2
- package/dist/builtin/workflows/CHANGELOG.md +68 -0
- package/dist/builtin/workflows/README.md +4 -4
- package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +27 -4
- package/dist/builtin/workflows/builtin/goal-runner.ts +47 -26
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +10 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +9 -9
- package/dist/builtin/workflows/builtin/ralph-models.ts +71 -36
- package/dist/builtin/workflows/builtin/ralph-runner.ts +20 -31
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +2 -1
- package/dist/builtin/workflows/src/engine/primitives/task.ts +2 -1
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +17 -22
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +15 -2
- package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +1 -2
- package/dist/builtin/workflows/src/runs/shared/model-fallback-candidates.ts +1 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -2
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +2 -2
- package/dist/builtin/workflows/src/shared/resume-continuation.ts +2 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +1 -23
- package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +54 -9
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +5 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +2 -1
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-message-queue.d.ts.map +1 -1
- package/dist/core/agent-session-message-queue.js +1 -1
- package/dist/core/agent-session-message-queue.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +1 -1
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +1 -1
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/anthropic-thinking-guard.d.ts +5 -5
- package/dist/core/anthropic-thinking-guard.d.ts.map +1 -1
- package/dist/core/anthropic-thinking-guard.js +42 -32
- package/dist/core/anthropic-thinking-guard.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +15 -15
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +14 -9
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +79 -46
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/context-assistant-turns.d.ts +42 -0
- package/dist/core/compaction/context-assistant-turns.d.ts.map +1 -0
- package/dist/core/compaction/context-assistant-turns.js +87 -0
- package/dist/core/compaction/context-assistant-turns.js.map +1 -0
- package/dist/core/compaction/context-compaction-critical.d.ts +1 -1
- package/dist/core/compaction/context-compaction-critical.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-critical.js +2 -2
- package/dist/core/compaction/context-compaction-critical.js.map +1 -1
- package/dist/core/compaction/context-compaction-eviction-alternates.d.ts +18 -0
- package/dist/core/compaction/context-compaction-eviction-alternates.d.ts.map +1 -0
- package/dist/core/compaction/context-compaction-eviction-alternates.js +186 -0
- package/dist/core/compaction/context-compaction-eviction-alternates.js.map +1 -0
- package/dist/core/compaction/context-compaction-eviction.d.ts +10 -2
- package/dist/core/compaction/context-compaction-eviction.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-eviction.js +196 -146
- package/dist/core/compaction/context-compaction-eviction.js.map +1 -1
- package/dist/core/compaction/context-compaction-prompt.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-prompt.js +1 -1
- package/dist/core/compaction/context-compaction-prompt.js.map +1 -1
- package/dist/core/compaction/context-compaction-types.d.ts +2 -0
- package/dist/core/compaction/context-compaction-types.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-types.js.map +1 -1
- package/dist/core/compaction/context-deletion-application.d.ts +5 -4
- package/dist/core/compaction/context-deletion-application.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-application.js +17 -15
- package/dist/core/compaction/context-deletion-application.js.map +1 -1
- package/dist/core/compaction/context-deletion-store.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-store.js +1 -1
- package/dist/core/compaction/context-deletion-store.js.map +1 -1
- package/dist/core/compaction/context-deletion-targets.d.ts +2 -1
- package/dist/core/compaction/context-deletion-targets.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-targets.js +36 -31
- package/dist/core/compaction/context-deletion-targets.js.map +1 -1
- package/dist/core/compaction/context-deletion-tool-helpers.d.ts.map +1 -1
- package/dist/core/compaction/context-deletion-tool-helpers.js +116 -43
- package/dist/core/compaction/context-deletion-tool-helpers.js.map +1 -1
- package/dist/core/compaction/context-transcript-analysis.d.ts +1 -2
- package/dist/core/compaction/context-transcript-analysis.d.ts.map +1 -1
- package/dist/core/compaction/context-transcript-analysis.js +33 -37
- package/dist/core/compaction/context-transcript-analysis.js.map +1 -1
- package/dist/core/copilot-model-synthesis.d.ts.map +1 -1
- package/dist/core/copilot-model-synthesis.js +3 -1
- package/dist/core/copilot-model-synthesis.js.map +1 -1
- package/dist/core/extensions/provider-types.d.ts +2 -7
- package/dist/core/extensions/provider-types.d.ts.map +1 -1
- package/dist/core/extensions/provider-types.js.map +1 -1
- package/dist/core/messages.d.ts +9 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +100 -18
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry-builtins.d.ts.map +1 -1
- package/dist/core/model-registry-builtins.js +5 -0
- package/dist/core/model-registry-builtins.js.map +1 -1
- package/dist/core/model-registry-custom-loader.d.ts.map +1 -1
- package/dist/core/model-registry-custom-loader.js +49 -8
- package/dist/core/model-registry-custom-loader.js.map +1 -1
- package/dist/core/model-registry-dynamic.d.ts.map +1 -1
- package/dist/core/model-registry-dynamic.js +11 -5
- package/dist/core/model-registry-dynamic.js.map +1 -1
- package/dist/core/model-registry-loader.d.ts.map +1 -1
- package/dist/core/model-registry-loader.js +8 -0
- package/dist/core/model-registry-loader.js.map +1 -1
- package/dist/core/model-registry-schemas.d.ts +93 -8
- package/dist/core/model-registry-schemas.d.ts.map +1 -1
- package/dist/core/model-registry-schemas.js +24 -12
- package/dist/core/model-registry-schemas.js.map +1 -1
- package/dist/core/model-registry-types.d.ts +7 -6
- package/dist/core/model-registry-types.d.ts.map +1 -1
- package/dist/core/model-registry-types.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +23 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver-cli.d.ts.map +1 -1
- package/dist/core/model-resolver-cli.js +33 -5
- package/dist/core/model-resolver-cli.js.map +1 -1
- package/dist/core/model-resolver-initial.d.ts +6 -1
- package/dist/core/model-resolver-initial.d.ts.map +1 -1
- package/dist/core/model-resolver-initial.js +12 -7
- package/dist/core/model-resolver-initial.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/resource-loader-context-files.d.ts +1 -0
- package/dist/core/resource-loader-context-files.d.ts.map +1 -1
- package/dist/core/resource-loader-context-files.js +13 -10
- package/dist/core/resource-loader-context-files.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +2 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-entry-normalization.d.ts +7 -0
- package/dist/core/session-entry-normalization.d.ts.map +1 -0
- package/dist/core/session-entry-normalization.js +14 -0
- package/dist/core/session-entry-normalization.js.map +1 -0
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +1 -0
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-history.d.ts +2 -2
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +127 -108
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +2 -2
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +11 -8
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/main-session.d.ts.map +1 -1
- package/dist/main-session.js +1 -0
- package/dist/main-session.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-options.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-options.js +3 -1
- package/dist/modes/interactive/components/settings-selector-options.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +2 -1
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-child-ordering.d.ts +7 -0
- package/dist/modes/interactive/interactive-child-ordering.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-child-ordering.js +26 -0
- package/dist/modes/interactive/interactive-child-ordering.js.map +1 -0
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.js +2 -2
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.js +8 -3
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/dist/modes/interactive/theme/theme-class.d.ts +1 -1
- package/dist/modes/interactive/theme/theme-class.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-class.js +2 -0
- package/dist/modes/interactive/theme/theme-class.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +3 -0
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/changelog.mdx +19 -0
- package/docs/compaction.md +55 -48
- package/docs/custom-provider.md +18 -5
- package/docs/extensions.md +7 -3
- package/docs/models.md +69 -10
- package/docs/providers.md +1 -1
- package/docs/quickstart.md +11 -7
- package/docs/rpc.md +2 -2
- package/docs/sdk.md +1 -1
- package/docs/settings.md +3 -3
- package/docs/subagents.md +36 -5
- package/docs/tools.md +1 -1
- package/docs/usage.md +4 -2
- package/docs/workflows.md +26 -43
- package/examples/extensions/preset.ts +2 -2
- package/examples/extensions/subagent/README.md +2 -2
- package/examples/extensions/subagent/index.ts +1 -2
- package/examples/extensions/subagent/schemas.ts +4 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/npm-shrinkwrap.json +48 -51
- package/package.json +7 -7
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-behavior.ts +0 -75
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-component.ts +0 -202
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-edit.ts +0 -97
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-editor.ts +0 -160
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-frame.ts +0 -72
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-render-modes.ts +0 -161
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-render-selectors.ts +0 -203
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-selectors.ts +0 -234
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-state.ts +0 -103
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify-types.ts +0 -29
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +0 -9
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-clarify.ts +0 -117
|
@@ -4,6 +4,7 @@ import { randomUUID } from "crypto";
|
|
|
4
4
|
import { writeMessage, createMessageReader } from "./framing.js";
|
|
5
5
|
import { getBrokerSocketPath } from "./paths.js";
|
|
6
6
|
import type { SessionInfo, Message, Attachment } from "../types.js";
|
|
7
|
+
import { buildSendSignature, PendingSendRegistry } from "./pending-send-registry.js";
|
|
7
8
|
|
|
8
9
|
const BROKER_SOCKET = getBrokerSocketPath();
|
|
9
10
|
|
|
@@ -107,19 +108,14 @@ function isSessionInfo(value: unknown): value is SessionInfo {
|
|
|
107
108
|
export class IntercomClient extends EventEmitter {
|
|
108
109
|
private socket: net.Socket | null = null;
|
|
109
110
|
private _sessionId: string | null = null;
|
|
110
|
-
private pendingSends = new
|
|
111
|
+
private pendingSends = new PendingSendRegistry();
|
|
111
112
|
private pendingLists = new Map<string, { resolve: (sessions: SessionInfo[]) => void; reject: (e: Error) => void }>();
|
|
112
113
|
private disconnecting = false;
|
|
113
114
|
private disconnectError: Error | null = null;
|
|
114
115
|
|
|
115
116
|
private failPending(error: Error): void {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
this.pendingSends.clear();
|
|
120
|
-
for (const pending of this.pendingLists.values()) {
|
|
121
|
-
pending.reject(error);
|
|
122
|
-
}
|
|
117
|
+
this.pendingSends.rejectAll(error);
|
|
118
|
+
for (const pending of this.pendingLists.values()) pending.reject(error);
|
|
123
119
|
this.pendingLists.clear();
|
|
124
120
|
}
|
|
125
121
|
|
|
@@ -314,31 +310,23 @@ export class IntercomClient extends EventEmitter {
|
|
|
314
310
|
break;
|
|
315
311
|
}
|
|
316
312
|
case "delivered": {
|
|
317
|
-
const { messageId } = brokerMessage;
|
|
318
|
-
if (typeof messageId !== "string") {
|
|
313
|
+
const { messageId, attemptId } = brokerMessage;
|
|
314
|
+
if (typeof messageId !== "string" || (attemptId !== undefined && typeof attemptId !== "string")) {
|
|
319
315
|
throw new Error("Invalid delivered message");
|
|
320
316
|
}
|
|
321
|
-
const
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
this.pendingSends.delete(messageId);
|
|
327
|
-
pending.resolve({ id: messageId, delivered: true });
|
|
317
|
+
const result = { id: messageId, delivered: true } as const;
|
|
318
|
+
if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
|
|
319
|
+
else this.pendingSends.resolve(messageId, attemptId, result);
|
|
328
320
|
break;
|
|
329
321
|
}
|
|
330
322
|
case "delivery_failed": {
|
|
331
|
-
const { messageId, reason } = brokerMessage;
|
|
332
|
-
if (typeof messageId !== "string" || typeof reason !== "string") {
|
|
323
|
+
const { messageId, attemptId, reason } = brokerMessage;
|
|
324
|
+
if (typeof messageId !== "string" || (attemptId !== undefined && typeof attemptId !== "string") || typeof reason !== "string") {
|
|
333
325
|
throw new Error("Invalid delivery_failed message");
|
|
334
326
|
}
|
|
335
|
-
const
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
this.pendingSends.delete(messageId);
|
|
341
|
-
pending.resolve({ id: messageId, delivered: false, reason });
|
|
327
|
+
const result = { id: messageId, delivered: false, reason } as const;
|
|
328
|
+
if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
|
|
329
|
+
else this.pendingSends.resolve(messageId, attemptId, result);
|
|
342
330
|
break;
|
|
343
331
|
}
|
|
344
332
|
case "session_joined": {
|
|
@@ -452,40 +440,27 @@ export class IntercomClient extends EventEmitter {
|
|
|
452
440
|
return Promise.reject(toError(error));
|
|
453
441
|
}
|
|
454
442
|
const messageId = options.messageId ?? randomUUID();
|
|
443
|
+
let acquired;
|
|
444
|
+
try {
|
|
445
|
+
acquired = this.pendingSends.acquire(messageId, buildSendSignature(to, options), 10000);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
return Promise.reject(toError(error));
|
|
448
|
+
}
|
|
449
|
+
if (!acquired.owner) return acquired.attempt.promise;
|
|
450
|
+
|
|
455
451
|
const message: Message = {
|
|
456
452
|
id: messageId,
|
|
457
453
|
timestamp: Date.now(),
|
|
458
454
|
replyTo: options.replyTo,
|
|
459
455
|
expectsReply: options.expectsReply,
|
|
460
|
-
content: {
|
|
461
|
-
text: options.text,
|
|
462
|
-
attachments: options.attachments,
|
|
463
|
-
},
|
|
456
|
+
content: { text: options.text, attachments: options.attachments },
|
|
464
457
|
};
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
clearTimeout(timeout);
|
|
472
|
-
reject(error);
|
|
473
|
-
};
|
|
474
|
-
const timeout = setTimeout(() => {
|
|
475
|
-
if (this.pendingSends.has(messageId)) {
|
|
476
|
-
this.pendingSends.delete(messageId);
|
|
477
|
-
wrappedReject(new Error("Send timeout"));
|
|
478
|
-
}
|
|
479
|
-
}, 10000);
|
|
480
|
-
this.pendingSends.set(messageId, { resolve: wrappedResolve, reject: wrappedReject });
|
|
481
|
-
try {
|
|
482
|
-
writeMessage(socket, { type: "send", to, message });
|
|
483
|
-
} catch (error) {
|
|
484
|
-
clearTimeout(timeout);
|
|
485
|
-
this.pendingSends.delete(messageId);
|
|
486
|
-
reject(toError(error));
|
|
487
|
-
}
|
|
488
|
-
});
|
|
458
|
+
try {
|
|
459
|
+
writeMessage(socket, { type: "send", to, message, attemptId: acquired.attempt.attemptId });
|
|
460
|
+
} catch (error) {
|
|
461
|
+
this.pendingSends.reject(acquired.attempt, toError(error));
|
|
462
|
+
}
|
|
463
|
+
return acquired.attempt.promise;
|
|
489
464
|
}
|
|
490
465
|
updatePresence(updates: { name?: string; status?: string; model?: string }): void {
|
|
491
466
|
if (this.disconnecting) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
2
|
+
const DEFAULT_MAX_ENTRIES = 10_000;
|
|
3
|
+
|
|
4
|
+
export type DeliveredMessageMatch = "miss" | "match" | "conflict";
|
|
5
|
+
|
|
6
|
+
interface DeliveredMessage {
|
|
7
|
+
deliveredAt: number;
|
|
8
|
+
signature: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Bounded successful-delivery cache used to make broker and local retries idempotent. */
|
|
12
|
+
export class DeliveredMessageCache {
|
|
13
|
+
private readonly delivered = new Map<string, DeliveredMessage>();
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly ttlMs = DEFAULT_TTL_MS,
|
|
17
|
+
private readonly maxEntries = DEFAULT_MAX_ENTRIES,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
lookup(messageId: string, signature: string, now = Date.now()): DeliveredMessageMatch {
|
|
21
|
+
this.prune(now);
|
|
22
|
+
const delivered = this.delivered.get(messageId);
|
|
23
|
+
if (!delivered || now - delivered.deliveredAt > this.ttlMs) return "miss";
|
|
24
|
+
return delivered.signature === signature ? "match" : "conflict";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
record(messageId: string, signature: string, now = Date.now()): void {
|
|
28
|
+
this.prune(now);
|
|
29
|
+
this.delivered.delete(messageId);
|
|
30
|
+
this.delivered.set(messageId, { deliveredAt: now, signature });
|
|
31
|
+
while (this.delivered.size > this.maxEntries) {
|
|
32
|
+
const oldest = this.delivered.keys().next().value as string | undefined;
|
|
33
|
+
if (oldest === undefined) break;
|
|
34
|
+
this.delivered.delete(oldest);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private prune(now: number): void {
|
|
39
|
+
for (const [messageId, delivered] of this.delivered) {
|
|
40
|
+
if (now - delivered.deliveredAt <= this.ttlMs) break;
|
|
41
|
+
this.delivered.delete(messageId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import type { LogicalSendOptions } from "./send-signature.js";
|
|
3
|
+
export { buildSendSignature } from "./send-signature.js";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_GENERATION_TTL_MS = 10 * 60 * 1000;
|
|
6
|
+
const DEFAULT_MAX_GENERATIONS = 10_000;
|
|
7
|
+
|
|
8
|
+
export interface SendOptionsLike extends LogicalSendOptions {}
|
|
9
|
+
|
|
10
|
+
export interface SendResultLike {
|
|
11
|
+
id: string;
|
|
12
|
+
delivered: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PendingSendAttempt {
|
|
17
|
+
readonly messageId: string;
|
|
18
|
+
readonly attemptId: string;
|
|
19
|
+
readonly signature: string;
|
|
20
|
+
readonly promise: Promise<SendResultLike>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface OwnedPendingSend extends PendingSendAttempt {
|
|
24
|
+
legacyEligible: boolean;
|
|
25
|
+
generation: number;
|
|
26
|
+
resolve(result: SendResultLike): void;
|
|
27
|
+
reject(error: Error): void;
|
|
28
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PendingSendAcquisition {
|
|
32
|
+
attempt: PendingSendAttempt;
|
|
33
|
+
owner: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface SendGeneration {
|
|
37
|
+
generation: number;
|
|
38
|
+
touchedAt: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export class PendingSendRegistry {
|
|
43
|
+
private readonly attempts = new Map<string, OwnedPendingSend>();
|
|
44
|
+
private readonly generations = new Map<string, SendGeneration>();
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly generationTtlMs = DEFAULT_GENERATION_TTL_MS,
|
|
48
|
+
private readonly maxGenerations = DEFAULT_MAX_GENERATIONS,
|
|
49
|
+
private readonly now: () => number = Date.now,
|
|
50
|
+
) {}
|
|
51
|
+
|
|
52
|
+
private nextGeneration(messageId: string): number {
|
|
53
|
+
const now = this.now();
|
|
54
|
+
for (const [id, record] of this.generations) {
|
|
55
|
+
if (now - record.touchedAt <= this.generationTtlMs) break;
|
|
56
|
+
this.generations.delete(id);
|
|
57
|
+
}
|
|
58
|
+
const previous = this.generations.get(messageId);
|
|
59
|
+
const generation = previous && now - previous.touchedAt <= this.generationTtlMs ? previous.generation + 1 : 1;
|
|
60
|
+
this.generations.delete(messageId);
|
|
61
|
+
this.generations.set(messageId, { generation, touchedAt: now });
|
|
62
|
+
while (this.generations.size > Math.max(1, this.maxGenerations)) {
|
|
63
|
+
const oldest = this.generations.keys().next().value as string | undefined;
|
|
64
|
+
if (oldest === undefined) break;
|
|
65
|
+
this.generations.delete(oldest);
|
|
66
|
+
}
|
|
67
|
+
return generation;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private touchGeneration(attempt: OwnedPendingSend): void {
|
|
71
|
+
this.generations.delete(attempt.messageId);
|
|
72
|
+
this.generations.set(attempt.messageId, { generation: attempt.generation, touchedAt: this.now() });
|
|
73
|
+
while (this.generations.size > Math.max(1, this.maxGenerations)) {
|
|
74
|
+
const oldest = this.generations.keys().next().value as string | undefined;
|
|
75
|
+
if (oldest === undefined) break;
|
|
76
|
+
this.generations.delete(oldest);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
acquire(messageId: string, signature: string, timeoutMs: number): PendingSendAcquisition {
|
|
80
|
+
const existing = this.attempts.get(messageId);
|
|
81
|
+
if (existing) {
|
|
82
|
+
if (existing.signature !== signature) {
|
|
83
|
+
throw new Error(`Intercom message ID '${messageId}' is already pending with a different target or payload`);
|
|
84
|
+
}
|
|
85
|
+
return { attempt: existing, owner: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let resolvePromise!: (result: SendResultLike) => void;
|
|
89
|
+
let rejectPromise!: (error: Error) => void;
|
|
90
|
+
const promise = new Promise<SendResultLike>((resolve, reject) => {
|
|
91
|
+
resolvePromise = resolve;
|
|
92
|
+
rejectPromise = reject;
|
|
93
|
+
});
|
|
94
|
+
const generation = this.nextGeneration(messageId);
|
|
95
|
+
const attempt: OwnedPendingSend = {
|
|
96
|
+
messageId,
|
|
97
|
+
attemptId: randomUUID(),
|
|
98
|
+
signature,
|
|
99
|
+
promise,
|
|
100
|
+
resolve: resolvePromise,
|
|
101
|
+
reject: rejectPromise,
|
|
102
|
+
legacyEligible: generation === 1,
|
|
103
|
+
generation,
|
|
104
|
+
};
|
|
105
|
+
attempt.timer = setTimeout(() => {
|
|
106
|
+
this.reject(attempt, new Error("Send timeout"));
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
this.attempts.set(messageId, attempt);
|
|
109
|
+
return { attempt, owner: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
resolve(messageId: string, attemptId: string, result: SendResultLike): boolean {
|
|
113
|
+
const attempt = this.attempts.get(messageId);
|
|
114
|
+
if (!attempt || attempt.attemptId !== attemptId) return false;
|
|
115
|
+
this.attempts.delete(messageId);
|
|
116
|
+
if (attempt.timer) clearTimeout(attempt.timer);
|
|
117
|
+
this.touchGeneration(attempt);
|
|
118
|
+
attempt.resolve(result);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Resolve a pre-attemptId response only for an ID's first active generation. */
|
|
123
|
+
resolveLegacy(messageId: string, result: SendResultLike): boolean {
|
|
124
|
+
const attempt = this.attempts.get(messageId);
|
|
125
|
+
if (!attempt?.legacyEligible) return false;
|
|
126
|
+
return this.resolve(messageId, attempt.attemptId, result);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
reject(attempt: PendingSendAttempt, error: Error): boolean {
|
|
130
|
+
const current = this.attempts.get(attempt.messageId);
|
|
131
|
+
if (current !== attempt) return false;
|
|
132
|
+
this.attempts.delete(attempt.messageId);
|
|
133
|
+
if (current.timer) clearTimeout(current.timer);
|
|
134
|
+
this.touchGeneration(current);
|
|
135
|
+
current.reject(error);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
rejectAll(error: Error): void {
|
|
140
|
+
for (const attempt of [...this.attempts.values()]) this.reject(attempt, error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type net from "node:net";
|
|
2
|
+
import type { Attachment, BrokerMessage, Message, SessionInfo } from "../types.js";
|
|
3
|
+
import { DeliveredMessageCache } from "./delivered-message-cache.js";
|
|
4
|
+
import { buildMessageSendSignature } from "./send-signature.js";
|
|
5
|
+
|
|
6
|
+
export interface BrokerConnectedSession {
|
|
7
|
+
socket: net.Socket;
|
|
8
|
+
info: SessionInfo;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SendClientMessage extends Record<string, unknown> {
|
|
12
|
+
type: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isAttachment(value: unknown): value is Attachment {
|
|
16
|
+
if (typeof value !== "object" || value === null) return false;
|
|
17
|
+
const attachment = value as Record<string, unknown>;
|
|
18
|
+
if (attachment.type !== "file" && attachment.type !== "snippet" && attachment.type !== "context") return false;
|
|
19
|
+
if (typeof attachment.name !== "string" || typeof attachment.content !== "string") return false;
|
|
20
|
+
return attachment.language === undefined || typeof attachment.language === "string";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isMessage(value: unknown): value is Message {
|
|
24
|
+
if (typeof value !== "object" || value === null) return false;
|
|
25
|
+
const message = value as Record<string, unknown>;
|
|
26
|
+
if (typeof message.id !== "string" || typeof message.timestamp !== "number") return false;
|
|
27
|
+
if (message.replyTo !== undefined && typeof message.replyTo !== "string") return false;
|
|
28
|
+
if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean") return false;
|
|
29
|
+
if (typeof message.content !== "object" || message.content === null) return false;
|
|
30
|
+
const content = message.content as Record<string, unknown>;
|
|
31
|
+
if (typeof content.text !== "string") return false;
|
|
32
|
+
return content.attachments === undefined || (Array.isArray(content.attachments) && content.attachments.every(isAttachment));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function findSessions(sessions: Map<string, BrokerConnectedSession>, nameOrId: string): BrokerConnectedSession[] {
|
|
36
|
+
const byId = sessions.get(nameOrId);
|
|
37
|
+
if (byId) return [byId];
|
|
38
|
+
const lowerName = nameOrId.toLowerCase();
|
|
39
|
+
return Array.from(sessions.values()).filter((session) => session.info.name?.toLowerCase() === lowerName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Validate and route one wire-level send request. */
|
|
43
|
+
export function handleBrokerSend(
|
|
44
|
+
socket: net.Socket,
|
|
45
|
+
clientMessage: SendClientMessage,
|
|
46
|
+
currentId: string | null,
|
|
47
|
+
sessions: Map<string, BrokerConnectedSession>,
|
|
48
|
+
deliveredMessages: DeliveredMessageCache,
|
|
49
|
+
write: (target: net.Socket, message: BrokerMessage) => void,
|
|
50
|
+
): void {
|
|
51
|
+
const message = clientMessage.message;
|
|
52
|
+
const messageId = isMessage(message) ? message.id : "unknown";
|
|
53
|
+
const hasAttemptId = Object.prototype.hasOwnProperty.call(clientMessage, "attemptId");
|
|
54
|
+
if (hasAttemptId && typeof clientMessage.attemptId !== "string") {
|
|
55
|
+
write(socket, {
|
|
56
|
+
type: "delivery_failed",
|
|
57
|
+
messageId,
|
|
58
|
+
reason: "Invalid attemptId format",
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const attemptId = typeof clientMessage.attemptId === "string" ? clientMessage.attemptId : undefined;
|
|
63
|
+
if (typeof clientMessage.to !== "string" || !isMessage(message)) {
|
|
64
|
+
write(socket, { type: "delivery_failed", messageId, attemptId, reason: "Invalid message format" });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const signature = buildMessageSendSignature(clientMessage.to, message);
|
|
69
|
+
const deliveredMatch = deliveredMessages.lookup(message.id, signature);
|
|
70
|
+
if (deliveredMatch === "match") {
|
|
71
|
+
write(socket, { type: "delivered", messageId: message.id, attemptId });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (deliveredMatch === "conflict") {
|
|
75
|
+
write(socket, {
|
|
76
|
+
type: "delivery_failed",
|
|
77
|
+
messageId: message.id,
|
|
78
|
+
attemptId,
|
|
79
|
+
reason: `Intercom message ID '${message.id}' was already delivered with a different target or payload`,
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const targets = findSessions(sessions, clientMessage.to);
|
|
85
|
+
if (targets.length === 1) {
|
|
86
|
+
const fromSession = currentId ? sessions.get(currentId) : undefined;
|
|
87
|
+
if (!fromSession) {
|
|
88
|
+
write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Sender session not found" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
write(targets[0].socket, { type: "message", from: fromSession.info, message });
|
|
92
|
+
deliveredMessages.record(message.id, signature);
|
|
93
|
+
write(socket, { type: "delivered", messageId: message.id, attemptId });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (targets.length > 1) {
|
|
97
|
+
write(socket, {
|
|
98
|
+
type: "delivery_failed",
|
|
99
|
+
messageId: message.id,
|
|
100
|
+
attemptId,
|
|
101
|
+
reason: `Multiple sessions named \"${clientMessage.to}\" are connected. Use the session ID instead.`,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Session not found" });
|
|
106
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Attachment, Message } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface LogicalSendOptions {
|
|
4
|
+
text: string;
|
|
5
|
+
attachments?: Attachment[];
|
|
6
|
+
replyTo?: string;
|
|
7
|
+
expectsReply?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeAttachments(attachments: Attachment[] | undefined): Array<Record<string, string>> | undefined {
|
|
11
|
+
return attachments?.map((attachment) => ({
|
|
12
|
+
type: attachment.type,
|
|
13
|
+
name: attachment.name,
|
|
14
|
+
content: attachment.content,
|
|
15
|
+
...(attachment.language === undefined ? {} : { language: attachment.language }),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Canonical identity for one logical send; transport attempt metadata is deliberately excluded. */
|
|
20
|
+
export function buildSendSignature(to: string, options: LogicalSendOptions): string {
|
|
21
|
+
return JSON.stringify({
|
|
22
|
+
to,
|
|
23
|
+
text: options.text,
|
|
24
|
+
attachments: normalizeAttachments(options.attachments) ?? [],
|
|
25
|
+
replyTo: options.replyTo ?? null,
|
|
26
|
+
expectsReply: options.expectsReply ?? false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildMessageSendSignature(to: string, message: Message): string {
|
|
31
|
+
return buildSendSignature(to, {
|
|
32
|
+
text: message.content.text,
|
|
33
|
+
attachments: message.content.attachments,
|
|
34
|
+
replyTo: message.replyTo,
|
|
35
|
+
expectsReply: message.expectsReply,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@bastani/atomic";
|
|
2
|
+
import type { Message, SessionInfo } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
|
|
5
|
+
export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
|
|
6
|
+
|
|
7
|
+
interface DetachHandshake {
|
|
8
|
+
phase: "probe" | "commit";
|
|
9
|
+
requestId: string;
|
|
10
|
+
messageId: string;
|
|
11
|
+
childIntercomTarget: string;
|
|
12
|
+
senderId: string;
|
|
13
|
+
runtimeGeneration: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ForegroundDeliveryDisposition = "delivered" | "unclaimed" | "abandoned";
|
|
17
|
+
|
|
18
|
+
/** Claims a busy inbound message only when its exact foreground owner acknowledges it. */
|
|
19
|
+
export class ForegroundDetachHandoff {
|
|
20
|
+
private generation = -1;
|
|
21
|
+
private readonly delivered = new Set<string>();
|
|
22
|
+
private readonly pending = new Map<string, Promise<ForegroundDeliveryDisposition>>();
|
|
23
|
+
private generationCancellation = new AbortController();
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly pi: Pick<ExtensionAPI, "events">,
|
|
27
|
+
private readonly ackTimeoutMs = 50,
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
deliver(input: {
|
|
31
|
+
from: SessionInfo;
|
|
32
|
+
message: Message;
|
|
33
|
+
generation: number;
|
|
34
|
+
surface: () => void;
|
|
35
|
+
isCurrent: () => boolean;
|
|
36
|
+
}): Promise<ForegroundDeliveryDisposition> {
|
|
37
|
+
if (!input.isCurrent()) return Promise.resolve("abandoned");
|
|
38
|
+
if (this.generation !== input.generation) this.resetForGeneration(input.generation);
|
|
39
|
+
const deliveryKey = `${input.from.id}\0${input.message.id}`;
|
|
40
|
+
const pendingKey = `${input.generation}\0${deliveryKey}`;
|
|
41
|
+
if (this.delivered.has(deliveryKey)) return Promise.resolve("delivered");
|
|
42
|
+
const existing = this.pending.get(pendingKey);
|
|
43
|
+
if (existing) return existing;
|
|
44
|
+
|
|
45
|
+
const attempt = this.claimAndDeliver(input, deliveryKey).finally(() => {
|
|
46
|
+
if (this.pending.get(pendingKey) === attempt) this.pending.delete(pendingKey);
|
|
47
|
+
});
|
|
48
|
+
this.pending.set(pendingKey, attempt);
|
|
49
|
+
return attempt;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
reset(): void { this.resetForGeneration(-1); }
|
|
53
|
+
|
|
54
|
+
private async claimAndDeliver(
|
|
55
|
+
input: { from: SessionInfo; message: Message; generation: number; surface: () => void; isCurrent: () => boolean },
|
|
56
|
+
deliveryKey: string,
|
|
57
|
+
): Promise<ForegroundDeliveryDisposition> {
|
|
58
|
+
const route: DetachHandshake = {
|
|
59
|
+
phase: "probe",
|
|
60
|
+
requestId: input.message.id,
|
|
61
|
+
messageId: input.message.id,
|
|
62
|
+
childIntercomTarget: input.from.name || input.from.id,
|
|
63
|
+
senderId: input.from.id,
|
|
64
|
+
runtimeGeneration: input.generation,
|
|
65
|
+
};
|
|
66
|
+
const signal = this.generationCancellation.signal;
|
|
67
|
+
const probed = await this.awaitAcknowledgement(route, signal);
|
|
68
|
+
if (probed === "cancelled" || !input.isCurrent() || this.generation !== input.generation) return "abandoned";
|
|
69
|
+
if (probed === "timed-out") return "unclaimed";
|
|
70
|
+
|
|
71
|
+
const committed = await this.awaitAcknowledgement({ ...route, phase: "commit" }, signal);
|
|
72
|
+
if (committed !== "acknowledged" || !input.isCurrent() || this.generation !== input.generation) return "abandoned";
|
|
73
|
+
input.surface();
|
|
74
|
+
this.delivered.add(deliveryKey);
|
|
75
|
+
return "delivered";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private awaitAcknowledgement(
|
|
79
|
+
route: DetachHandshake,
|
|
80
|
+
signal: AbortSignal,
|
|
81
|
+
): Promise<"acknowledged" | "timed-out" | "cancelled"> {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
let settled = false;
|
|
84
|
+
let unsubscribe: (() => void) | undefined;
|
|
85
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
const finish = (disposition: "acknowledged" | "timed-out" | "cancelled") => {
|
|
87
|
+
if (settled) return;
|
|
88
|
+
settled = true;
|
|
89
|
+
if (timeout) clearTimeout(timeout);
|
|
90
|
+
unsubscribe?.();
|
|
91
|
+
signal.removeEventListener("abort", cancel);
|
|
92
|
+
resolve(disposition);
|
|
93
|
+
};
|
|
94
|
+
const cancel = () => finish("cancelled");
|
|
95
|
+
unsubscribe = this.pi.events?.on(INTERCOM_DETACH_RESPONSE_EVENT, (payload) => {
|
|
96
|
+
if (!payload || typeof payload !== "object") return;
|
|
97
|
+
const response = payload as Partial<DetachHandshake> & { accepted?: unknown };
|
|
98
|
+
if (response.accepted === true
|
|
99
|
+
&& response.phase === route.phase
|
|
100
|
+
&& response.requestId === route.requestId
|
|
101
|
+
&& response.messageId === route.messageId
|
|
102
|
+
&& response.childIntercomTarget === route.childIntercomTarget
|
|
103
|
+
&& response.senderId === route.senderId
|
|
104
|
+
&& response.runtimeGeneration === route.runtimeGeneration) finish("acknowledged");
|
|
105
|
+
});
|
|
106
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
107
|
+
timeout = setTimeout(() => finish("timed-out"), this.ackTimeoutMs);
|
|
108
|
+
if (signal.aborted) {
|
|
109
|
+
cancel();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.pi.events?.emit(INTERCOM_DETACH_REQUEST_EVENT, route);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private resetForGeneration(generation: number): void {
|
|
117
|
+
this.generationCancellation.abort();
|
|
118
|
+
this.generationCancellation = new AbortController();
|
|
119
|
+
this.pending.clear();
|
|
120
|
+
this.delivered.clear();
|
|
121
|
+
this.generation = generation;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function handleForegroundInboundDelivery(input: {
|
|
126
|
+
handoff: ForegroundDetachHandoff;
|
|
127
|
+
from: SessionInfo;
|
|
128
|
+
message: Message;
|
|
129
|
+
generation: number;
|
|
130
|
+
surface: () => void;
|
|
131
|
+
isCurrent: () => boolean;
|
|
132
|
+
onUnclaimed: () => void;
|
|
133
|
+
}): Promise<void> {
|
|
134
|
+
const disposition = await input.handoff.deliver(input);
|
|
135
|
+
if (disposition === "unclaimed" && input.isCurrent()) input.onUnclaimed();
|
|
136
|
+
}
|