@selesai/code 0.9.11 → 0.9.13
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 +17 -0
- package/dist/config.d.ts +4 -6
- package/dist/config.js +10 -14
- package/dist/core/agent-session.d.ts +0 -1
- package/dist/core/agent-session.js +3 -49
- package/dist/core/extensions/types.d.ts +0 -2
- package/dist/core/settings-manager.d.ts +0 -2
- package/dist/core/settings-manager.js +0 -3
- package/dist/core/tools/index.d.ts +0 -1
- package/dist/core/tools/index.js +0 -1
- package/dist/defaults/settings.json +2 -3
- package/dist/extensions/model-prompt-injector/index.test.ts +0 -10
- package/dist/extensions/model-prompt-injector/index.ts +0 -3
- package/dist/extensions/pi-intercom/CHANGELOG.md +53 -16
- package/dist/extensions/pi-intercom/README.md +41 -8
- package/dist/extensions/pi-intercom/banner.png +0 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +498 -215
- package/dist/extensions/pi-intercom/broker/client.ts +39 -12
- package/dist/extensions/pi-intercom/broker/extension.test.ts +102 -3
- package/dist/extensions/pi-intercom/broker/paths.test.ts +2 -2
- package/dist/extensions/pi-intercom/broker/protocol.ts +26 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +5 -20
- package/dist/extensions/pi-intercom/config.test.ts +41 -0
- package/dist/extensions/pi-intercom/config.ts +21 -0
- package/dist/extensions/pi-intercom/extension-api.ts +40 -0
- package/dist/extensions/pi-intercom/index.ts +362 -26
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +819 -13
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/project-agent.ts +2 -2
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +8 -110
- package/dist/extensions/pi-intercom/types.ts +32 -5
- package/dist/extensions/pi-intercom/ui/inline-message.ts +6 -0
- package/dist/extensions/pi-subagents/.github/assets/subagent-fleet-inspector.png +0 -0
- package/dist/extensions/pi-subagents/.github/workflows/release.yml +28 -0
- package/dist/extensions/pi-subagents/.github/workflows/test.yml +51 -0
- package/dist/extensions/pi-subagents/.greptile/config.json +33 -0
- package/dist/extensions/pi-subagents/.greptile/files.json +8 -0
- package/dist/extensions/pi-subagents/.greptile/rules.md +16 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +309 -87
- package/dist/extensions/pi-subagents/README.md +15 -17
- package/dist/extensions/pi-subagents/agents/oracle.md +3 -1
- package/dist/extensions/pi-subagents/agents/researcher.md +32 -13
- package/dist/extensions/pi-subagents/agents/reviewer.md +13 -2
- package/dist/extensions/pi-subagents/agents/scout.md +2 -2
- package/dist/extensions/pi-subagents/agents/worker.md +2 -1
- package/dist/extensions/pi-subagents/async-retention-discovery-worker.mjs +180 -0
- package/dist/extensions/pi-subagents/docs/configuration.md +106 -27
- package/dist/extensions/pi-subagents/docs/extension-api.md +85 -21
- package/dist/extensions/pi-subagents/docs/missions.md +41 -4
- package/dist/extensions/pi-subagents/docs/models.md +52 -9
- package/dist/extensions/pi-subagents/docs/observability.md +103 -16
- package/dist/extensions/pi-subagents/docs/tool-reference.md +60 -45
- package/dist/extensions/pi-subagents/docs/watchdog.md +60 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +181 -7
- package/dist/extensions/pi-subagents/index.ts +10 -1
- package/dist/extensions/pi-subagents/install.mjs +4 -4
- package/dist/extensions/pi-subagents/package-lock.json +1483 -1470
- package/dist/extensions/pi-subagents/package.json +6 -3
- package/dist/extensions/pi-subagents/prompts/council.md +60 -0
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +5 -1
- package/dist/extensions/pi-subagents/prompts/review-loop.md +9 -5
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +254 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +18 -17
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +31 -9
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +107 -140
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +9 -12
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +44 -92
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +261 -368
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +6 -2
- package/dist/extensions/pi-subagents/src/agents/agents.ts +388 -85
- package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +14 -0
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +418 -0
- package/dist/extensions/pi-subagents/src/agents/skills.ts +1 -1
- package/dist/extensions/pi-subagents/src/api/agents.ts +7 -0
- package/dist/extensions/pi-subagents/src/api/external-job-provider.ts +195 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +88 -27
- package/dist/extensions/pi-subagents/src/api/project-panes.ts +2 -0
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +20 -11
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +14 -6
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -4
- package/dist/extensions/pi-subagents/src/extension/index.ts +270 -187
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +65 -28
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +120 -11
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +46 -64
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +51 -37
- package/dist/extensions/pi-subagents/src/extension/tool-result.ts +19 -0
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -2
- package/dist/extensions/pi-subagents/src/inspectors/herdr/client.ts +3 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/focus.ts +55 -0
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +230 -46
- package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +26 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +32 -10
- package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +6 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +4 -9
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/policy/authority.ts +2 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +31 -8
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +210 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +63 -33
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +71 -17
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +886 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +14 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +49 -56
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +3 -33
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +22 -12
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +105 -71
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +68 -19
- package/dist/extensions/pi-subagents/src/runs/background/index-segment.ts +59 -0
- package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +443 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +94 -10
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +158 -90
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +141 -53
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +8 -5
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +14 -4
- package/dist/extensions/pi-subagents/src/runs/background/run-id-query.ts +7 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +13 -14
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +46 -25
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +823 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +9 -3
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +594 -216
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -13
- package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +138 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +23 -4
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +82 -4
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +5 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +21 -14
- package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +23 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +153 -58
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -9
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1283 -1657
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +274 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +59 -10
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +52 -2
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/extension-bindings.ts +78 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-job-bridge.ts +460 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-job-runner.ts +389 -0
- package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +10 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +243 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +201 -85
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +106 -39
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +145 -0
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +150 -27
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +46 -4
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +19 -15
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +86 -2
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +9 -9
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +0 -6
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +18 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +51 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +62 -21
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +1 -23
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +14 -2
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +2 -2
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +22 -2
- package/dist/extensions/pi-subagents/src/shared/capacity-resilient-json.ts +102 -0
- package/dist/extensions/pi-subagents/src/shared/completion-owner.ts +14 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +49 -1
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +59 -1
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +12 -5
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +20 -0
- package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +0 -40
- package/dist/extensions/pi-subagents/src/shared/settings.ts +11 -30
- package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +228 -49
- package/dist/extensions/pi-subagents/src/shared/utils.ts +11 -3
- package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +10 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +0 -162
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +2 -5
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +105 -13
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +6 -36
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +829 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +1415 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +78 -7
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +1 -1
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +1 -0
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +59 -51
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +8 -2
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +2 -1
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +4 -4
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +471 -86
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +140 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +1 -1
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +0 -54
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1053 -414
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +194 -4
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +59 -4
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +0 -77
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +107 -6
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +211 -897
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +265 -375
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +77 -7
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +10 -10
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +547 -30
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +2112 -310
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +274 -34
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +20 -3
- package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +9 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +30 -5
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +4 -2
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +1 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +197 -0
- package/dist/extensions/pi-subagents/test/unit/agent-contract.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +204 -27
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +444 -145
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +256 -4
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +9 -7
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +152 -22
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +29 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +205 -23
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/authority-policy.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/capacity-resilient-json.test.ts +155 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +123 -0
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +81 -3
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +109 -7
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -2
- package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/external-job-runner.test.ts +961 -0
- package/dist/extensions/pi-subagents/test/unit/file-system-retry.test.ts +64 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +1110 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +1664 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +81 -1
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +138 -11
- package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +41 -2
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +210 -79
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +95 -0
- package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +596 -0
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +3 -5
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +167 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +319 -18
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +19 -5
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +129 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +231 -2
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +28 -1
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +106 -2
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +204 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +59 -4
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +34 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -6
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +12 -3
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +56 -61
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +416 -1
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +9 -2
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +21 -30
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +86 -11
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +233 -5
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +28 -5
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +323 -20
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +66 -10
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -5
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +174 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +679 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +55 -44
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +399 -84
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +13 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +15 -10
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +11 -7
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +40 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +10 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +38 -3
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +110 -12
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +204 -19
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +25 -0
- package/dist/extensions/pi-subagents/test/unit/thinking-ceiling.test.ts +73 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +90 -50
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +234 -7
- package/dist/extensions/pi-subagents/test/unit/watch-strategy.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +37 -9
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +302 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +167 -7
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +6 -6
- package/dist/extensions/ponytail/index.js +31 -25
- package/dist/extensions/ponytail/ponytail-config.cjs +34 -7
- package/dist/extensions/ponytail/ponytail-instructions.cjs +7 -9
- package/dist/extensions/ponytail/test/extension.test.js +7 -8
- package/dist/extensions/ponytail/test/helpers.test.js +1 -1
- package/dist/skills/brandkit/SKILL.md +799 -0
- package/dist/skills/design-taste-frontend/SKILL.md +1207 -0
- package/dist/skills/design-taste-frontend-v1/SKILL.md +227 -0
- package/dist/skills/full-output-enforcement/SKILL.md +50 -0
- package/dist/skills/gpt-taste/SKILL.md +75 -0
- package/dist/skills/high-end-visual-design/SKILL.md +99 -0
- package/dist/skills/image-to-code/SKILL.md +1229 -0
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +1466 -0
- package/dist/skills/imagegen-frontend-web/SKILL.md +988 -0
- package/dist/skills/industrial-brutalist-ui/SKILL.md +93 -0
- package/dist/skills/minimalist-ui/SKILL.md +86 -0
- package/dist/skills/pi-subagents/SKILL.md +18 -17
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +31 -9
- package/dist/skills/pi-subagents/references/execution-controls.md +95 -143
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +9 -12
- package/dist/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +45 -94
- package/dist/skills/ponytail/SKILL.md +12 -8
- package/dist/skills/ponytail-audit/SKILL.md +0 -1
- package/dist/skills/ponytail-debt/SKILL.md +0 -1
- package/dist/skills/ponytail-gain/SKILL.md +0 -1
- package/dist/skills/ponytail-help/SKILL.md +4 -3
- package/dist/skills/ponytail-review/SKILL.md +0 -1
- package/dist/skills/redesign-existing-projects/SKILL.md +179 -0
- package/dist/skills/stitch-design-taste/SKILL.md +185 -0
- package/dist/skills/unlazy/CHANGELOG.md +100 -0
- package/dist/skills/unlazy/CONTRIBUTING.md +66 -0
- package/dist/skills/unlazy/LICENSE +21 -0
- package/dist/skills/unlazy/README.md +242 -0
- package/dist/skills/unlazy/SECURITY.md +70 -0
- package/dist/skills/unlazy/SKILL.md +100 -0
- package/dist/skills/unlazy/package.json +13 -0
- package/dist/skills/unlazy/references/dispatch.md +82 -0
- package/dist/skills/unlazy/references/gates.md +132 -0
- package/dist/skills/unlazy/references/method.md +49 -0
- package/dist/skills/unlazy/references/orchestration.md +99 -0
- package/dist/skills/unlazy/references/parallel.md +123 -0
- package/dist/skills/unlazy/references/token-economy.md +39 -0
- package/dist/skills/unlazy/research/validation-protocol.md +81 -0
- package/dist/skills/unlazy/scripts/dispatch-check.mjs +108 -0
- package/dist/skills/unlazy/scripts/gate-check.mjs +894 -0
- package/dist/skills/unlazy/scripts/gate-lint.mjs +178 -0
- package/dist/skills/unlazy/scripts/install-hooks.mjs +206 -0
- package/dist/skills/unlazy/scripts/lib/check-supervisor.mjs +46 -0
- package/dist/skills/unlazy/scripts/lib/dispatch.mjs +283 -0
- package/dist/skills/unlazy/scripts/lib/gates.mjs +629 -0
- package/dist/skills/unlazy/scripts/lib/process-tree.mjs +161 -0
- package/dist/skills/unlazy/scripts/lib/regex-worker.mjs +9 -0
- package/dist/skills/unlazy/scripts/stop-hook.mjs +174 -0
- package/dist/skills/unlazy/templates/PLAN.md +65 -0
- package/dist/skills/unlazy/templates/gates-leaf.md +51 -0
- package/dist/skills/unlazy/templates/gates-node.md +50 -0
- package/dist/skills/unlazy/tests/contract-tests.mjs +127 -0
- package/dist/skills/unlazy/tests/dispatch-tests.mjs +481 -0
- package/dist/skills/unlazy/tests/hardening-tests.mjs +903 -0
- package/dist/skills/unlazy/tests/lint-tests.mjs +242 -0
- package/dist/skills/unlazy/tests/run-tests.mjs +554 -0
- package/dist/skills/unlazy/tests/self-check.mjs +165 -0
- package/dist/skills/unlazy/tests/stress-tests.mjs +356 -0
- package/dist/skills/web-design-guidelines/SKILL.md +40 -0
- package/docs/settings.md +0 -5
- package/package.json +1 -1
- package/dist/core/tools/schema-prune.d.ts +0 -16
- package/dist/core/tools/schema-prune.js +0 -39
- package/dist/core/tools/schema-prune.test.d.ts +0 -1
- package/dist/core/tools/schema-prune.test.js +0 -111
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +0 -172
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +0 -55
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +0 -61
- package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +0 -132
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +0 -181
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +0 -1354
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +0 -1581
- package/dist/extensions/pi-subagents/src/shared/env.ts +0 -16
- package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +0 -71
- package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +0 -136
- package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +0 -41
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +0 -466
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +0 -1859
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +0 -733
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +0 -63
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +0 -326
- package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +0 -103
- package/dist/extensions/pi-subagents/test/unit/inline-subagents.test.ts +0 -127
- package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +0 -73
- package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +0 -213
- package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +0 -65
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import net from "net";
|
|
2
|
-
import { writeFileSync, unlinkSync } from "fs";
|
|
2
|
+
import { chmodSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { randomUUID } from "crypto";
|
|
4
|
+
import { createHash, randomUUID } from "crypto";
|
|
5
5
|
import { writeMessage, createMessageReader } from "./framing.ts";
|
|
6
6
|
import { isMessage, isMessageReceipt, isSessionId, isSessionRegistration } from "./protocol.ts";
|
|
7
7
|
import {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getBrokerListenTarget,
|
|
10
10
|
getBrokerPortFilePath,
|
|
11
11
|
getIntercomDirPath,
|
|
12
|
+
INTERCOM_DIR_MODE,
|
|
12
13
|
INTERCOM_PROTOCOL_NAME,
|
|
13
14
|
INTERCOM_PROTOCOL_VERSION,
|
|
14
15
|
INTERCOM_RUNTIME_FILE_MODE,
|
|
@@ -17,8 +18,8 @@ import {
|
|
|
17
18
|
} from "./paths.ts";
|
|
18
19
|
import { getAskTimeoutMs } from "../config.ts";
|
|
19
20
|
import { sameCwd } from "../cwd.ts";
|
|
20
|
-
import { EXTENSION_BUS_FEATURE } from "../types.ts";
|
|
21
|
-
import type { SessionInfo, Message, BrokerMessage, ExtensionCapability, MessageControl } from "../types.ts";
|
|
21
|
+
import { EXACT_SEND_FEATURE, EXTENSION_BUS_FEATURE } from "../types.ts";
|
|
22
|
+
import type { DeliveryState, SessionInfo, Message, BrokerMessage, ExtensionCapability, MessageControl } from "../types.ts";
|
|
22
23
|
import { ExtensionStateManager } from "./extension-state.ts";
|
|
23
24
|
import { assertNoLiveBroker } from "./runtime-claim.ts";
|
|
24
25
|
|
|
@@ -26,6 +27,7 @@ const INTERCOM_DIR = getIntercomDirPath();
|
|
|
26
27
|
const LISTEN_TARGET = getBrokerListenTarget();
|
|
27
28
|
const PID_PATH = join(INTERCOM_DIR, "broker.pid");
|
|
28
29
|
const PORT_PATH = getBrokerPortFilePath(INTERCOM_DIR);
|
|
30
|
+
const PENDING_ASKS_DIR = join(INTERCOM_DIR, "pending-asks");
|
|
29
31
|
const BROKER_STATE_ID = randomUUID();
|
|
30
32
|
const MAX_SESSIONS = 128;
|
|
31
33
|
const MAX_UNREGISTERED_CONNECTIONS = 32;
|
|
@@ -40,6 +42,8 @@ const MESSAGE_RECEIPT_ROUTE_RETENTION_MS = 60 * 60 * 1000;
|
|
|
40
42
|
const DISCONNECTED_SESSION_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
41
43
|
const MAILBOX_MESSAGE_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
42
44
|
const MAX_MAILBOX_MESSAGES = 256;
|
|
45
|
+
const DELIVERY_RECORD_RETENTION_MS = 60 * 60 * 1000;
|
|
46
|
+
const MAX_DELIVERY_RECORDS = 4096;
|
|
43
47
|
|
|
44
48
|
function serializedPayloadSize(payload: unknown): number | null {
|
|
45
49
|
try {
|
|
@@ -53,15 +57,30 @@ function serializedPayloadSize(payload: unknown): number | null {
|
|
|
53
57
|
interface ConnectedSession {
|
|
54
58
|
socket: net.Socket;
|
|
55
59
|
info: SessionInfo;
|
|
60
|
+
key: string;
|
|
61
|
+
scopeId?: string;
|
|
56
62
|
lastPresenceBroadcastAt: number;
|
|
57
63
|
ownerOrder: number;
|
|
58
64
|
extensions?: ExtensionCapability[];
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
interface DeliveryRecord {
|
|
68
|
+
fingerprint: string;
|
|
69
|
+
state: DeliveryState;
|
|
70
|
+
reason?: string;
|
|
71
|
+
code?: string;
|
|
72
|
+
retryable: boolean;
|
|
73
|
+
outcomeKnown: boolean;
|
|
74
|
+
createdAt: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
interface NamespaceOwner {
|
|
78
|
+
namespace: string;
|
|
79
|
+
sessionKey: string;
|
|
62
80
|
sessionId: string;
|
|
63
81
|
socket: net.Socket;
|
|
64
82
|
epoch: string;
|
|
83
|
+
scopeId?: string;
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
interface ConnectionState {
|
|
@@ -73,9 +92,20 @@ interface ConnectionState {
|
|
|
73
92
|
interface AskEdge {
|
|
74
93
|
from: string;
|
|
75
94
|
to: string;
|
|
95
|
+
scopeId?: string;
|
|
76
96
|
createdAt: number;
|
|
77
97
|
}
|
|
78
98
|
|
|
99
|
+
interface PendingAskRecord {
|
|
100
|
+
askId: string;
|
|
101
|
+
messageId: string;
|
|
102
|
+
asker: { sessionId: string; name: string | null };
|
|
103
|
+
target: { sessionId: string; name: string | null };
|
|
104
|
+
question: string;
|
|
105
|
+
createdAt: number;
|
|
106
|
+
expiresAt: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
interface MessageReceiptRoute {
|
|
80
110
|
from: string;
|
|
81
111
|
to: string;
|
|
@@ -84,22 +114,98 @@ interface MessageReceiptRoute {
|
|
|
84
114
|
|
|
85
115
|
interface DisconnectedSession {
|
|
86
116
|
info: SessionInfo;
|
|
117
|
+
key: string;
|
|
118
|
+
scopeId?: string;
|
|
87
119
|
disconnectedAt: number;
|
|
88
120
|
}
|
|
89
121
|
|
|
90
122
|
interface MailboxMessage {
|
|
91
123
|
from: SessionInfo;
|
|
124
|
+
fromKey: string;
|
|
125
|
+
fromScopeId?: string;
|
|
92
126
|
target: SessionInfo;
|
|
127
|
+
targetKey: string;
|
|
128
|
+
targetScopeId?: string;
|
|
93
129
|
message: Message;
|
|
94
130
|
queuedAt: number;
|
|
95
131
|
}
|
|
96
132
|
|
|
133
|
+
function normalizeScopeId(value: unknown): string | undefined {
|
|
134
|
+
if (value === undefined) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
if (typeof value !== "string") {
|
|
138
|
+
throw new Error("Invalid register scopeId");
|
|
139
|
+
}
|
|
140
|
+
const trimmed = value.trim();
|
|
141
|
+
return trimmed ? trimmed : undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function sameScope(a: string | undefined, b: string | undefined): boolean {
|
|
145
|
+
return a === b;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function scopedSessionKey(scopeId: string | undefined, sessionId: string): string {
|
|
149
|
+
return JSON.stringify([scopeId ?? null, sessionId]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function scopedExtensionKey(scopeId: string | undefined, namespace: string): string {
|
|
153
|
+
return JSON.stringify([scopeId ?? null, namespace]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function scopedExtensionStateNamespace(scopeId: string | undefined, namespace: string): string {
|
|
157
|
+
if (!scopeId) {
|
|
158
|
+
return namespace;
|
|
159
|
+
}
|
|
160
|
+
return JSON.stringify(["scope", createHash("sha256").update(scopeId).digest("hex"), namespace]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function scopedPendingAskRecordPath(scopeId: string | undefined, messageId: string): string {
|
|
164
|
+
if (!scopeId) {
|
|
165
|
+
return pendingAskRecordPath(messageId);
|
|
166
|
+
}
|
|
167
|
+
const scopeHash = createHash("sha256").update(scopeId).digest("hex");
|
|
168
|
+
return join(PENDING_ASKS_DIR, `${scopeHash}-${encodeURIComponent(messageId)}.json`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
172
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isPendingAskRecord(value: unknown): value is PendingAskRecord {
|
|
176
|
+
if (!isRecord(value) || !isRecord(value.asker) || !isRecord(value.target)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return typeof value.askId === "string"
|
|
180
|
+
&& typeof value.messageId === "string"
|
|
181
|
+
&& typeof value.asker.sessionId === "string"
|
|
182
|
+
&& (typeof value.asker.name === "string" || value.asker.name === null)
|
|
183
|
+
&& typeof value.target.sessionId === "string"
|
|
184
|
+
&& (typeof value.target.name === "string" || value.target.name === null)
|
|
185
|
+
&& typeof value.question === "string"
|
|
186
|
+
&& Number.isSafeInteger(value.createdAt)
|
|
187
|
+
&& Number.isSafeInteger(value.expiresAt)
|
|
188
|
+
&& value.expiresAt >= value.createdAt;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function pendingAskRecordPath(messageId: string): string {
|
|
192
|
+
return join(PENDING_ASKS_DIR, `${encodeURIComponent(messageId)}.json`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function ensurePendingAskRecordDir(): void {
|
|
196
|
+
mkdirSync(PENDING_ASKS_DIR, { recursive: true, mode: INTERCOM_DIR_MODE });
|
|
197
|
+
if (process.platform !== "win32") {
|
|
198
|
+
chmodSync(PENDING_ASKS_DIR, INTERCOM_DIR_MODE);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
97
202
|
class IntercomBroker {
|
|
98
203
|
private sessions = new Map<string, ConnectedSession>();
|
|
99
204
|
private askEdges = new Map<string, AskEdge>();
|
|
100
205
|
private messageReceiptRoutes = new Map<string, MessageReceiptRoute>();
|
|
101
206
|
private disconnectedSessions = new Map<string, DisconnectedSession>();
|
|
102
207
|
private mailboxMessages: MailboxMessage[] = [];
|
|
208
|
+
private deliveryRecords = new Map<string, DeliveryRecord>();
|
|
103
209
|
private connections = new Set<net.Socket>();
|
|
104
210
|
private unregisteredConnections = new Set<net.Socket>();
|
|
105
211
|
private server: net.Server;
|
|
@@ -112,6 +218,8 @@ class IntercomBroker {
|
|
|
112
218
|
constructor() {
|
|
113
219
|
ensureIntercomRuntimeDir(INTERCOM_DIR);
|
|
114
220
|
assertNoLiveBroker(PID_PATH);
|
|
221
|
+
ensurePendingAskRecordDir();
|
|
222
|
+
this.prunePendingAskRecords();
|
|
115
223
|
this.extensionStateManager = new ExtensionStateManager(INTERCOM_DIR);
|
|
116
224
|
if (typeof LISTEN_TARGET === "string" && process.platform !== "win32") {
|
|
117
225
|
try {
|
|
@@ -157,7 +265,7 @@ class IntercomBroker {
|
|
|
157
265
|
|
|
158
266
|
private handleConnection(socket: net.Socket): void {
|
|
159
267
|
this.connections.add(socket);
|
|
160
|
-
let
|
|
268
|
+
let sessionKey: string | null = null;
|
|
161
269
|
let registrationTimeout: NodeJS.Timeout | null = null;
|
|
162
270
|
const armRegistrationTimeout = () => {
|
|
163
271
|
if (registrationTimeout) {
|
|
@@ -167,7 +275,7 @@ class IntercomBroker {
|
|
|
167
275
|
this.unregisteredConnections.add(socket);
|
|
168
276
|
this.evictOldestUnregisteredConnections(socket);
|
|
169
277
|
registrationTimeout = setTimeout(() => {
|
|
170
|
-
if (!
|
|
278
|
+
if (!sessionKey) {
|
|
171
279
|
socket.destroy();
|
|
172
280
|
}
|
|
173
281
|
}, REGISTRATION_TIMEOUT_MS);
|
|
@@ -193,8 +301,8 @@ class IntercomBroker {
|
|
|
193
301
|
socket.destroy(new Error("Intercom broker rate limit exceeded"));
|
|
194
302
|
return;
|
|
195
303
|
}
|
|
196
|
-
this.handleMessage(socket, msg,
|
|
197
|
-
|
|
304
|
+
this.handleMessage(socket, msg, sessionKey, (id) => {
|
|
305
|
+
sessionKey = id;
|
|
198
306
|
if (id) {
|
|
199
307
|
clearRegistrationTimeout();
|
|
200
308
|
} else {
|
|
@@ -210,13 +318,13 @@ class IntercomBroker {
|
|
|
210
318
|
socket.on("close", () => {
|
|
211
319
|
clearRegistrationTimeout();
|
|
212
320
|
this.connections.delete(socket);
|
|
213
|
-
if (
|
|
214
|
-
const existing = this.sessions.get(
|
|
321
|
+
if (sessionKey) {
|
|
322
|
+
const existing = this.sessions.get(sessionKey);
|
|
215
323
|
if (existing?.socket === socket) {
|
|
216
|
-
this.rememberDisconnectedSession(existing
|
|
217
|
-
this.sessions.delete(
|
|
218
|
-
this.clearMessageReceiptRoutesForSession(
|
|
219
|
-
this.broadcast({ type: "session_left", sessionId },
|
|
324
|
+
this.rememberDisconnectedSession(existing);
|
|
325
|
+
this.sessions.delete(sessionKey);
|
|
326
|
+
this.clearMessageReceiptRoutesForSession(sessionKey);
|
|
327
|
+
this.broadcast({ type: "session_left", sessionId: existing.info.id }, sessionKey, existing.scopeId);
|
|
220
328
|
this.recomputeNamespaceOwners();
|
|
221
329
|
this.scheduleShutdownCheck();
|
|
222
330
|
}
|
|
@@ -273,8 +381,8 @@ class IntercomBroker {
|
|
|
273
381
|
private handleMessage(
|
|
274
382
|
socket: net.Socket,
|
|
275
383
|
msg: unknown,
|
|
276
|
-
|
|
277
|
-
|
|
384
|
+
currentKey: string | null,
|
|
385
|
+
setKey: (key: string | null) => void,
|
|
278
386
|
): void {
|
|
279
387
|
if (typeof msg !== "object" || msg === null || !("type" in msg) || typeof msg.type !== "string") {
|
|
280
388
|
throw new Error("Invalid client message");
|
|
@@ -304,7 +412,7 @@ class IntercomBroker {
|
|
|
304
412
|
throw new Error("Invalid intercom TCP endpoint credentials");
|
|
305
413
|
}
|
|
306
414
|
|
|
307
|
-
if (
|
|
415
|
+
if (currentKey === null && clientMessage.type !== "register") {
|
|
308
416
|
throw new Error(`Received ${clientMessage.type} before register`);
|
|
309
417
|
}
|
|
310
418
|
|
|
@@ -314,7 +422,7 @@ class IntercomBroker {
|
|
|
314
422
|
throw new Error("Invalid register message");
|
|
315
423
|
}
|
|
316
424
|
|
|
317
|
-
if (
|
|
425
|
+
if (currentKey) {
|
|
318
426
|
throw new Error("Received duplicate register message");
|
|
319
427
|
}
|
|
320
428
|
|
|
@@ -325,6 +433,8 @@ class IntercomBroker {
|
|
|
325
433
|
}
|
|
326
434
|
id = clientMessage.sessionId;
|
|
327
435
|
}
|
|
436
|
+
const scopeId = normalizeScopeId(clientMessage.scopeId);
|
|
437
|
+
const key = scopedSessionKey(scopeId, id);
|
|
328
438
|
const session = clientMessage.session;
|
|
329
439
|
const extensions = session.extensions;
|
|
330
440
|
if (extensions !== undefined) {
|
|
@@ -340,20 +450,20 @@ class IntercomBroker {
|
|
|
340
450
|
|
|
341
451
|
this.pruneDisconnectedSessions();
|
|
342
452
|
this.pruneMailboxMessages();
|
|
343
|
-
const previous = this.sessions.get(
|
|
453
|
+
const previous = this.sessions.get(key);
|
|
344
454
|
if (!previous && this.sessions.size >= MAX_SESSIONS) {
|
|
345
455
|
writeMessage(socket, { type: "error", error: "Too many registered intercom sessions" });
|
|
346
456
|
socket.destroy();
|
|
347
457
|
break;
|
|
348
458
|
}
|
|
349
459
|
if (previous) {
|
|
350
|
-
this.
|
|
351
|
-
this.clearMessageReceiptRoutesForSession(id);
|
|
460
|
+
this.clearMessageReceiptRoutesForSession(key);
|
|
352
461
|
previous.socket.end();
|
|
353
462
|
}
|
|
354
|
-
|
|
463
|
+
setKey(key);
|
|
355
464
|
const info: SessionInfo = {
|
|
356
465
|
id,
|
|
466
|
+
endpointEpoch: randomUUID(),
|
|
357
467
|
...(session.name !== undefined ? { name: session.name } : {}),
|
|
358
468
|
...(session.runtimeFallbackAlias !== undefined ? { runtimeFallbackAlias: session.runtimeFallbackAlias } : {}),
|
|
359
469
|
cwd: session.cwd,
|
|
@@ -362,18 +472,21 @@ class IntercomBroker {
|
|
|
362
472
|
startedAt: session.startedAt,
|
|
363
473
|
lastActivity: session.lastActivity,
|
|
364
474
|
...(session.status !== undefined ? { status: session.status } : {}),
|
|
475
|
+
...(session.tmuxPane !== undefined ? { tmuxPane: session.tmuxPane } : {}),
|
|
365
476
|
trustedLocal: typeof LISTEN_TARGET === "string" && process.platform !== "win32",
|
|
366
477
|
};
|
|
367
478
|
|
|
368
479
|
const connectedSession: ConnectedSession = {
|
|
369
480
|
socket,
|
|
370
481
|
info,
|
|
482
|
+
key,
|
|
483
|
+
...(scopeId ? { scopeId } : {}),
|
|
371
484
|
lastPresenceBroadcastAt: Date.now(),
|
|
372
485
|
ownerOrder: previous?.ownerOrder ?? this.nextOwnerOrder++,
|
|
373
486
|
extensions,
|
|
374
487
|
};
|
|
375
|
-
this.sessions.set(
|
|
376
|
-
this.disconnectedSessions.delete(
|
|
488
|
+
this.sessions.set(key, connectedSession);
|
|
489
|
+
this.disconnectedSessions.delete(key);
|
|
377
490
|
|
|
378
491
|
if (this.shutdownTimer) {
|
|
379
492
|
clearTimeout(this.shutdownTimer);
|
|
@@ -386,22 +499,22 @@ class IntercomBroker {
|
|
|
386
499
|
writeMessage(socket, {
|
|
387
500
|
type: "registered",
|
|
388
501
|
sessionId: id,
|
|
389
|
-
features: [EXTENSION_BUS_FEATURE],
|
|
502
|
+
features: [EXTENSION_BUS_FEATURE, EXACT_SEND_FEATURE],
|
|
390
503
|
});
|
|
391
|
-
this.broadcast({ type: "session_joined", session: info },
|
|
504
|
+
this.broadcast({ type: "session_joined", session: info }, key, scopeId);
|
|
392
505
|
|
|
393
506
|
this.recomputeNamespaceOwners();
|
|
394
507
|
this.flushMailboxForSession(connectedSession);
|
|
395
508
|
|
|
396
509
|
if (extensions) {
|
|
397
510
|
for (const ext of extensions) {
|
|
398
|
-
const owner = this.namespaceOwners.get(ext.namespace);
|
|
511
|
+
const owner = this.namespaceOwners.get(scopedExtensionKey(scopeId, ext.namespace));
|
|
399
512
|
writeMessage(socket, {
|
|
400
513
|
type: "extension_owner",
|
|
401
514
|
namespace: ext.namespace,
|
|
402
515
|
...(owner ? { ownerId: owner.sessionId, ownerEpoch: owner.epoch } : {}),
|
|
403
516
|
});
|
|
404
|
-
const state = this.extensionStateManager.loadState(ext.namespace);
|
|
517
|
+
const state = this.extensionStateManager.loadState(scopedExtensionStateNamespace(scopeId, ext.namespace));
|
|
405
518
|
if (state) {
|
|
406
519
|
writeMessage(socket, {
|
|
407
520
|
type: "extension_state",
|
|
@@ -416,27 +529,27 @@ class IntercomBroker {
|
|
|
416
529
|
}
|
|
417
530
|
|
|
418
531
|
case "unregister": {
|
|
419
|
-
if (!
|
|
532
|
+
if (!currentKey) {
|
|
420
533
|
throw new Error("Received unregister before register");
|
|
421
534
|
}
|
|
422
|
-
const existing = this.sessions.get(
|
|
535
|
+
const existing = this.sessions.get(currentKey);
|
|
423
536
|
if (existing?.socket === socket) {
|
|
424
|
-
this.rememberDisconnectedSession(existing
|
|
425
|
-
this.sessions.delete(
|
|
426
|
-
this.clearMessageReceiptRoutesForSession(
|
|
427
|
-
this.broadcast({ type: "session_left", sessionId:
|
|
537
|
+
this.rememberDisconnectedSession(existing);
|
|
538
|
+
this.sessions.delete(currentKey);
|
|
539
|
+
this.clearMessageReceiptRoutesForSession(currentKey);
|
|
540
|
+
this.broadcast({ type: "session_left", sessionId: existing.info.id }, currentKey, existing.scopeId);
|
|
428
541
|
this.recomputeNamespaceOwners();
|
|
429
542
|
this.scheduleShutdownCheck();
|
|
430
543
|
}
|
|
431
|
-
|
|
544
|
+
setKey(null);
|
|
432
545
|
break;
|
|
433
546
|
}
|
|
434
547
|
|
|
435
548
|
case "extension_capabilities_update": {
|
|
436
|
-
if (!
|
|
549
|
+
if (!currentKey) {
|
|
437
550
|
throw new Error("Received extension_capabilities_update before register");
|
|
438
551
|
}
|
|
439
|
-
const session = this.sessions.get(
|
|
552
|
+
const session = this.sessions.get(currentKey);
|
|
440
553
|
if (!session || session.socket !== socket) {
|
|
441
554
|
throw new Error("Extension capability session not found");
|
|
442
555
|
}
|
|
@@ -452,13 +565,13 @@ class IntercomBroker {
|
|
|
452
565
|
session.extensions = extensions;
|
|
453
566
|
this.recomputeNamespaceOwners();
|
|
454
567
|
for (const extension of extensions) {
|
|
455
|
-
const owner = this.namespaceOwners.get(extension.namespace);
|
|
568
|
+
const owner = this.namespaceOwners.get(scopedExtensionKey(session.scopeId, extension.namespace));
|
|
456
569
|
writeMessage(socket, {
|
|
457
570
|
type: "extension_owner",
|
|
458
571
|
namespace: extension.namespace,
|
|
459
572
|
...(owner ? { ownerId: owner.sessionId, ownerEpoch: owner.epoch } : {}),
|
|
460
573
|
});
|
|
461
|
-
const state = this.extensionStateManager.loadState(extension.namespace);
|
|
574
|
+
const state = this.extensionStateManager.loadState(scopedExtensionStateNamespace(session.scopeId, extension.namespace));
|
|
462
575
|
if (state) {
|
|
463
576
|
writeMessage(socket, {
|
|
464
577
|
type: "extension_state",
|
|
@@ -476,24 +589,31 @@ class IntercomBroker {
|
|
|
476
589
|
throw new Error("Invalid list message");
|
|
477
590
|
}
|
|
478
591
|
|
|
479
|
-
const
|
|
592
|
+
const requester = currentKey ? this.sessions.get(currentKey) : undefined;
|
|
593
|
+
if (!requester || requester.socket !== socket) {
|
|
594
|
+
throw new Error("List session not found");
|
|
595
|
+
}
|
|
596
|
+
const sessions = Array.from(this.sessions.values())
|
|
597
|
+
.filter(session => sameScope(session.scopeId, requester.scopeId))
|
|
598
|
+
.map(s => s.info);
|
|
480
599
|
writeMessage(socket, { type: "sessions", requestId: clientMessage.requestId, sessions });
|
|
481
600
|
break;
|
|
482
601
|
}
|
|
483
602
|
|
|
484
603
|
case "send": {
|
|
485
|
-
if (!
|
|
604
|
+
if (!currentKey) {
|
|
486
605
|
throw new Error("Received send before register");
|
|
487
606
|
}
|
|
488
607
|
const message = clientMessage.message;
|
|
489
608
|
const messageId = isMessage(message) ? message.id : "unknown";
|
|
490
609
|
|
|
491
610
|
if (typeof clientMessage.to !== "string" || !isMessage(message)) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
611
|
+
this.writeDeliveryFailure(socket, messageId, "Invalid message format", "E_INVALID_MESSAGE");
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
const fromSession = this.sessions.get(currentKey);
|
|
615
|
+
if (!fromSession || fromSession.socket !== socket) {
|
|
616
|
+
this.writeDeliveryFailure(socket, message.id, "Sender session not found", "E_SENDER_NOT_FOUND");
|
|
497
617
|
break;
|
|
498
618
|
}
|
|
499
619
|
|
|
@@ -502,56 +622,72 @@ class IntercomBroker {
|
|
|
502
622
|
this.pruneMessageReceiptRoutes(brokerReceivedAt);
|
|
503
623
|
const replyEdge = message.replyTo ? this.askEdges.get(message.replyTo) : undefined;
|
|
504
624
|
|
|
505
|
-
const
|
|
625
|
+
const hasTargetId = clientMessage.targetId !== undefined;
|
|
626
|
+
const hasTargetEpoch = clientMessage.targetEpoch !== undefined;
|
|
627
|
+
if (
|
|
628
|
+
hasTargetId !== hasTargetEpoch
|
|
629
|
+
|| (hasTargetId && (typeof clientMessage.targetId !== "string" || clientMessage.targetId.length === 0))
|
|
630
|
+
|| (hasTargetEpoch && (typeof clientMessage.targetEpoch !== "string" || clientMessage.targetEpoch.length === 0))
|
|
631
|
+
) {
|
|
632
|
+
this.writeDeliveryFailure(socket, message.id, "Exact target requires an id and endpoint epoch", "E_INVALID_TARGET");
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
if (hasTargetId && hasTargetEpoch) {
|
|
636
|
+
const targetId = clientMessage.targetId as string;
|
|
637
|
+
const targetEpoch = clientMessage.targetEpoch as string;
|
|
638
|
+
const fingerprint = this.deliveryFingerprint(message, targetId);
|
|
639
|
+
if (this.replayOrReject(socket, currentKey, message.id, fingerprint)) {
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
const exactTarget = this.sessions.get(scopedSessionKey(fromSession.scopeId, targetId));
|
|
643
|
+
if (!exactTarget) {
|
|
644
|
+
this.recordDelivery(currentKey, message.id, fingerprint, "failed", "Session not found", "E_TARGET_NOT_FOUND");
|
|
645
|
+
this.writeDeliveryFailure(socket, message.id, "Session not found", "E_TARGET_NOT_FOUND");
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
if (exactTarget.info.endpointEpoch !== targetEpoch) {
|
|
649
|
+
this.recordDelivery(currentKey, message.id, fingerprint, "failed", "Target endpoint changed before delivery", "E_TARGET_REBOUND", true);
|
|
650
|
+
this.writeDeliveryFailure(socket, message.id, "Target endpoint changed before delivery", "E_TARGET_REBOUND", true);
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
clientMessage.to = targetId;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const targets = this.findSessions(clientMessage.to, fromSession.scopeId);
|
|
506
657
|
if (targets.length === 1) {
|
|
507
658
|
if (message.replyTo && !replyEdge) {
|
|
508
|
-
|
|
509
|
-
type: "delivery_failed",
|
|
510
|
-
messageId: message.id,
|
|
511
|
-
reason: "Reply target does not match a pending ask",
|
|
512
|
-
});
|
|
659
|
+
this.writeDeliveryFailure(socket, message.id, "Reply target does not match a pending ask", "E_REPLY_TARGET");
|
|
513
660
|
break;
|
|
514
661
|
}
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
type: "delivery_failed",
|
|
519
|
-
messageId: message.id,
|
|
520
|
-
reason: "Sender session not found",
|
|
521
|
-
});
|
|
662
|
+
const target = targets[0];
|
|
663
|
+
const fingerprint = this.deliveryFingerprint(message, target.info.id);
|
|
664
|
+
if (this.replayOrReject(socket, currentKey, message.id, fingerprint)) {
|
|
522
665
|
break;
|
|
523
666
|
}
|
|
524
|
-
const target = targets[0];
|
|
525
667
|
if (message.supersedes) {
|
|
526
668
|
const supersededRoute = this.messageReceiptRoutes.get(message.supersedes);
|
|
527
|
-
if (!supersededRoute || supersededRoute.from !==
|
|
528
|
-
|
|
529
|
-
type: "delivery_failed",
|
|
530
|
-
messageId: message.id,
|
|
531
|
-
reason: "Supersede target does not match a previous message from this sender to this receiver",
|
|
532
|
-
});
|
|
669
|
+
if (!supersededRoute || supersededRoute.from !== currentKey || supersededRoute.to !== target.key) {
|
|
670
|
+
this.writeDeliveryFailure(socket, message.id, "Supersede target does not match a previous message from this sender to this receiver", "E_SUPERSEDE_TARGET");
|
|
533
671
|
break;
|
|
534
672
|
}
|
|
535
673
|
}
|
|
536
|
-
if (replyEdge && (replyEdge.to !==
|
|
537
|
-
|
|
538
|
-
type: "delivery_failed",
|
|
539
|
-
messageId: message.id,
|
|
540
|
-
reason: "Reply target does not match the pending ask",
|
|
541
|
-
});
|
|
674
|
+
if (replyEdge && (replyEdge.to !== currentKey || replyEdge.from !== target.key)) {
|
|
675
|
+
this.writeDeliveryFailure(socket, message.id, "Reply target does not match the pending ask", "E_REPLY_TARGET");
|
|
542
676
|
break;
|
|
543
677
|
}
|
|
544
678
|
if (message.expectsReply) {
|
|
545
|
-
const reverseEdge = Array.from(this.askEdges.entries()).find(([edgeMessageId, edge]) => edgeMessageId !== message.replyTo && edge.from === target.
|
|
679
|
+
const reverseEdge = Array.from(this.askEdges.entries()).find(([edgeMessageId, edge]) => edgeMessageId !== message.replyTo && edge.from === target.key && edge.to === currentKey);
|
|
546
680
|
if (reverseEdge) {
|
|
547
|
-
|
|
548
|
-
type: "delivery_failed",
|
|
549
|
-
messageId: message.id,
|
|
550
|
-
reason: "Mutual ask refused: target session is already waiting for a reply from this session.",
|
|
551
|
-
});
|
|
681
|
+
this.writeDeliveryFailure(socket, message.id, "Mutual ask refused: target session is already waiting for a reply from this session.", "E_MUTUAL_ASK");
|
|
552
682
|
break;
|
|
553
683
|
}
|
|
554
|
-
this.
|
|
684
|
+
this.writePendingAskRecord(message, fromSession, target.info, brokerReceivedAt);
|
|
685
|
+
this.askEdges.set(message.id, {
|
|
686
|
+
from: currentKey,
|
|
687
|
+
to: target.key,
|
|
688
|
+
...(fromSession.scopeId ? { scopeId: fromSession.scopeId } : {}),
|
|
689
|
+
createdAt: brokerReceivedAt,
|
|
690
|
+
});
|
|
555
691
|
}
|
|
556
692
|
const deliveredMessage: Message = {
|
|
557
693
|
...message,
|
|
@@ -570,6 +706,7 @@ class IntercomBroker {
|
|
|
570
706
|
from: fromSession.info,
|
|
571
707
|
control,
|
|
572
708
|
});
|
|
709
|
+
this.updateDeliveryRecord(currentKey, message.supersedes, "failed", `Superseded by ${message.id}`, "E_DELIVERY_SUPERSEDED");
|
|
573
710
|
}
|
|
574
711
|
writeMessage(target.socket, {
|
|
575
712
|
type: "message",
|
|
@@ -578,66 +715,44 @@ class IntercomBroker {
|
|
|
578
715
|
});
|
|
579
716
|
if (message.replyTo) {
|
|
580
717
|
this.askEdges.delete(message.replyTo);
|
|
718
|
+
this.removePendingAskRecord(message.replyTo, fromSession.scopeId);
|
|
581
719
|
}
|
|
582
|
-
this.messageReceiptRoutes.set(message.id, { from:
|
|
583
|
-
|
|
720
|
+
this.messageReceiptRoutes.set(message.id, { from: currentKey, to: target.key, createdAt: brokerReceivedAt });
|
|
721
|
+
this.recordDelivery(currentKey, message.id, fingerprint, "socket_delivered");
|
|
722
|
+
this.writeDeliverySuccess(socket, message.id, "socket_delivered");
|
|
584
723
|
break;
|
|
585
724
|
}
|
|
586
725
|
|
|
587
726
|
if (targets.length > 1) {
|
|
588
|
-
|
|
589
|
-
type: "delivery_failed",
|
|
590
|
-
messageId: message.id,
|
|
591
|
-
reason: `Multiple sessions named \"${clientMessage.to}\" are connected. Use the session ID instead.`,
|
|
592
|
-
});
|
|
727
|
+
this.writeDeliveryFailure(socket, message.id, `Multiple sessions named \"${clientMessage.to}\" are connected. Use the session ID instead.`, "E_AMBIGUOUS_TARGET");
|
|
593
728
|
break;
|
|
594
729
|
}
|
|
595
730
|
|
|
596
|
-
const disconnectedTargets = this.findDisconnectedSessions(clientMessage.to);
|
|
731
|
+
const disconnectedTargets = this.findDisconnectedSessions(clientMessage.to, fromSession.scopeId);
|
|
597
732
|
if (disconnectedTargets.length === 1) {
|
|
598
733
|
if (message.replyTo && !replyEdge) {
|
|
599
|
-
|
|
600
|
-
type: "delivery_failed",
|
|
601
|
-
messageId: message.id,
|
|
602
|
-
reason: "Reply target does not match a pending ask",
|
|
603
|
-
});
|
|
734
|
+
this.writeDeliveryFailure(socket, message.id, "Reply target does not match a pending ask", "E_REPLY_TARGET");
|
|
604
735
|
break;
|
|
605
736
|
}
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
messageId: message.id,
|
|
611
|
-
reason: "Sender session not found",
|
|
612
|
-
});
|
|
737
|
+
const disconnectedTarget = disconnectedTargets[0]!;
|
|
738
|
+
const target = disconnectedTarget.info;
|
|
739
|
+
const fingerprint = this.deliveryFingerprint(message, target.id);
|
|
740
|
+
if (this.replayOrReject(socket, currentKey, message.id, fingerprint)) {
|
|
613
741
|
break;
|
|
614
742
|
}
|
|
615
|
-
const target = disconnectedTargets[0]!.info;
|
|
616
743
|
if (message.supersedes) {
|
|
617
|
-
|
|
618
|
-
type: "delivery_failed",
|
|
619
|
-
messageId: message.id,
|
|
620
|
-
reason: "Supersede target is not connected",
|
|
621
|
-
});
|
|
744
|
+
this.writeDeliveryFailure(socket, message.id, "Supersede target is not connected", "E_SUPERSEDE_TARGET");
|
|
622
745
|
break;
|
|
623
746
|
}
|
|
624
|
-
if (replyEdge && (replyEdge.to !==
|
|
625
|
-
|
|
626
|
-
type: "delivery_failed",
|
|
627
|
-
messageId: message.id,
|
|
628
|
-
reason: "Reply target does not match the pending ask",
|
|
629
|
-
});
|
|
747
|
+
if (replyEdge && (replyEdge.to !== currentKey || replyEdge.from !== disconnectedTarget.key)) {
|
|
748
|
+
this.writeDeliveryFailure(socket, message.id, "Reply target does not match the pending ask", "E_REPLY_TARGET");
|
|
630
749
|
break;
|
|
631
750
|
}
|
|
632
751
|
if (message.expectsReply) {
|
|
633
|
-
|
|
634
|
-
type: "delivery_failed",
|
|
635
|
-
messageId: message.id,
|
|
636
|
-
reason: "Target session is not currently connected; blocking asks are not queued",
|
|
637
|
-
});
|
|
752
|
+
this.writeDeliveryFailure(socket, message.id, "Target session is not currently connected; blocking asks are not queued", "E_TARGET_DISCONNECTED");
|
|
638
753
|
break;
|
|
639
754
|
}
|
|
640
|
-
const liveMailboxTarget = this.findUniqueLiveSessionForDisconnectedSession(
|
|
755
|
+
const liveMailboxTarget = this.findUniqueLiveSessionForDisconnectedSession(disconnectedTarget, currentKey);
|
|
641
756
|
if (liveMailboxTarget) {
|
|
642
757
|
const deliveredMessage: Message = {
|
|
643
758
|
...message,
|
|
@@ -649,36 +764,30 @@ class IntercomBroker {
|
|
|
649
764
|
from: fromSession.info,
|
|
650
765
|
message: deliveredMessage,
|
|
651
766
|
});
|
|
652
|
-
this.messageReceiptRoutes.set(message.id, { from:
|
|
767
|
+
this.messageReceiptRoutes.set(message.id, { from: currentKey, to: liveMailboxTarget.key, createdAt: brokerReceivedAt });
|
|
653
768
|
} else {
|
|
654
|
-
this.queueMailboxMessage(fromSession
|
|
769
|
+
this.queueMailboxMessage(fromSession, disconnectedTarget, message, brokerReceivedAt);
|
|
655
770
|
}
|
|
656
771
|
if (message.replyTo) {
|
|
657
772
|
this.askEdges.delete(message.replyTo);
|
|
773
|
+
this.removePendingAskRecord(message.replyTo, fromSession.scopeId);
|
|
658
774
|
}
|
|
659
|
-
|
|
775
|
+
this.recordDelivery(currentKey, message.id, fingerprint, liveMailboxTarget ? "socket_delivered" : "queued");
|
|
776
|
+
this.writeDeliverySuccess(socket, message.id, liveMailboxTarget ? "socket_delivered" : "queued");
|
|
660
777
|
break;
|
|
661
778
|
}
|
|
662
779
|
|
|
663
780
|
if (disconnectedTargets.length > 1) {
|
|
664
|
-
|
|
665
|
-
type: "delivery_failed",
|
|
666
|
-
messageId: message.id,
|
|
667
|
-
reason: `Multiple disconnected sessions named \"${clientMessage.to}\" can receive queued mail. Use the session ID instead.`,
|
|
668
|
-
});
|
|
781
|
+
this.writeDeliveryFailure(socket, message.id, `Multiple disconnected sessions named \"${clientMessage.to}\" can receive queued mail. Use the session ID instead.`, "E_AMBIGUOUS_TARGET");
|
|
669
782
|
break;
|
|
670
783
|
}
|
|
671
784
|
|
|
672
|
-
|
|
673
|
-
type: "delivery_failed",
|
|
674
|
-
messageId: message.id,
|
|
675
|
-
reason: "Session not found",
|
|
676
|
-
});
|
|
785
|
+
this.writeDeliveryFailure(socket, message.id, "Session not found", "E_TARGET_NOT_FOUND");
|
|
677
786
|
break;
|
|
678
787
|
}
|
|
679
788
|
|
|
680
789
|
case "message_receipt": {
|
|
681
|
-
if (!
|
|
790
|
+
if (!currentKey) {
|
|
682
791
|
throw new Error("Received message_receipt before register");
|
|
683
792
|
}
|
|
684
793
|
if (!isMessageReceipt(clientMessage.receipt)) {
|
|
@@ -686,9 +795,9 @@ class IntercomBroker {
|
|
|
686
795
|
}
|
|
687
796
|
this.pruneMessageReceiptRoutes();
|
|
688
797
|
const route = this.messageReceiptRoutes.get(clientMessage.receipt.messageId);
|
|
689
|
-
const receiver = this.sessions.get(
|
|
798
|
+
const receiver = this.sessions.get(currentKey);
|
|
690
799
|
const sender = route ? this.sessions.get(route.from) : undefined;
|
|
691
|
-
if (route?.to ===
|
|
800
|
+
if (route?.to === currentKey && receiver?.socket === socket && sender) {
|
|
692
801
|
writeMessage(sender.socket, {
|
|
693
802
|
type: "message_receipt",
|
|
694
803
|
from: receiver.info,
|
|
@@ -699,7 +808,7 @@ class IntercomBroker {
|
|
|
699
808
|
}
|
|
700
809
|
|
|
701
810
|
case "cancel_message": {
|
|
702
|
-
if (!
|
|
811
|
+
if (!currentKey) {
|
|
703
812
|
throw new Error("Received cancel_message before register");
|
|
704
813
|
}
|
|
705
814
|
if (typeof clientMessage.messageId !== "string") {
|
|
@@ -707,20 +816,22 @@ class IntercomBroker {
|
|
|
707
816
|
}
|
|
708
817
|
this.pruneMessageReceiptRoutes();
|
|
709
818
|
this.pruneMailboxMessages();
|
|
710
|
-
const sender = this.sessions.get(
|
|
711
|
-
const queuedIndex = this.mailboxMessages.findIndex(entry => entry.message.id === clientMessage.messageId && entry.
|
|
819
|
+
const sender = this.sessions.get(currentKey);
|
|
820
|
+
const queuedIndex = this.mailboxMessages.findIndex(entry => entry.message.id === clientMessage.messageId && entry.fromKey === currentKey);
|
|
712
821
|
if (queuedIndex >= 0 && sender?.socket === socket) {
|
|
713
822
|
this.mailboxMessages.splice(queuedIndex, 1);
|
|
823
|
+
this.updateDeliveryRecord(currentKey, clientMessage.messageId, "failed", "Sender cancelled the queued delivery", "E_DELIVERY_CANCELLED");
|
|
714
824
|
const edge = this.askEdges.get(clientMessage.messageId);
|
|
715
|
-
if (edge?.from ===
|
|
825
|
+
if (edge?.from === currentKey) {
|
|
716
826
|
this.askEdges.delete(clientMessage.messageId);
|
|
827
|
+
this.removePendingAskRecord(clientMessage.messageId, sender.scopeId);
|
|
717
828
|
}
|
|
718
829
|
writeMessage(socket, { type: "delivered", messageId: clientMessage.messageId });
|
|
719
830
|
break;
|
|
720
831
|
}
|
|
721
832
|
const route = this.messageReceiptRoutes.get(clientMessage.messageId);
|
|
722
833
|
const receiver = route ? this.sessions.get(route.to) : undefined;
|
|
723
|
-
if (route?.from !==
|
|
834
|
+
if (route?.from !== currentKey || sender?.socket !== socket || !receiver) {
|
|
724
835
|
writeMessage(socket, {
|
|
725
836
|
type: "delivery_failed",
|
|
726
837
|
messageId: clientMessage.messageId,
|
|
@@ -738,33 +849,36 @@ class IntercomBroker {
|
|
|
738
849
|
},
|
|
739
850
|
});
|
|
740
851
|
const edge = this.askEdges.get(clientMessage.messageId);
|
|
741
|
-
if (edge?.from ===
|
|
852
|
+
if (edge?.from === currentKey) {
|
|
742
853
|
this.askEdges.delete(clientMessage.messageId);
|
|
854
|
+
this.removePendingAskRecord(clientMessage.messageId, sender.scopeId);
|
|
743
855
|
}
|
|
856
|
+
this.updateDeliveryRecord(currentKey, clientMessage.messageId, "failed", "Sender cancelled the delivery", "E_DELIVERY_CANCELLED");
|
|
744
857
|
writeMessage(socket, { type: "delivered", messageId: clientMessage.messageId });
|
|
745
858
|
break;
|
|
746
859
|
}
|
|
747
860
|
|
|
748
861
|
case "cancel_ask": {
|
|
749
|
-
if (!
|
|
862
|
+
if (!currentKey) {
|
|
750
863
|
throw new Error("Received cancel_ask before register");
|
|
751
864
|
}
|
|
752
865
|
if (typeof clientMessage.messageId !== "string") {
|
|
753
866
|
throw new Error("Invalid cancel_ask message");
|
|
754
867
|
}
|
|
755
|
-
const session = this.sessions.get(
|
|
868
|
+
const session = this.sessions.get(currentKey);
|
|
756
869
|
const edge = this.askEdges.get(clientMessage.messageId);
|
|
757
|
-
if (session?.socket === socket && edge?.from ===
|
|
870
|
+
if (session?.socket === socket && edge?.from === currentKey) {
|
|
758
871
|
this.askEdges.delete(clientMessage.messageId);
|
|
872
|
+
this.removePendingAskRecord(clientMessage.messageId, session.scopeId);
|
|
759
873
|
}
|
|
760
874
|
break;
|
|
761
875
|
}
|
|
762
876
|
|
|
763
877
|
case "presence": {
|
|
764
|
-
if (!
|
|
878
|
+
if (!currentKey) {
|
|
765
879
|
throw new Error("Received presence before register");
|
|
766
880
|
}
|
|
767
|
-
const session = this.sessions.get(
|
|
881
|
+
const session = this.sessions.get(currentKey);
|
|
768
882
|
if (session?.socket === socket) {
|
|
769
883
|
let changed = false;
|
|
770
884
|
if (clientMessage.name !== undefined) {
|
|
@@ -840,19 +954,19 @@ class IntercomBroker {
|
|
|
840
954
|
session.info.lastActivity = now;
|
|
841
955
|
if (changed || now - session.lastPresenceBroadcastAt >= PRESENCE_HEARTBEAT_MS) {
|
|
842
956
|
session.lastPresenceBroadcastAt = now;
|
|
843
|
-
this.broadcast({ type: "presence_update", session: session.info },
|
|
957
|
+
this.broadcast({ type: "presence_update", session: session.info }, currentKey, session.scopeId);
|
|
844
958
|
}
|
|
845
959
|
}
|
|
846
960
|
break;
|
|
847
961
|
}
|
|
848
962
|
|
|
849
963
|
case "extension_publish": {
|
|
850
|
-
this.handleExtensionPublish(socket,
|
|
964
|
+
this.handleExtensionPublish(socket, currentKey, clientMessage);
|
|
851
965
|
break;
|
|
852
966
|
}
|
|
853
967
|
|
|
854
968
|
case "extension_state_commit": {
|
|
855
|
-
this.handleExtensionStateCommit(socket,
|
|
969
|
+
this.handleExtensionStateCommit(socket, currentKey, clientMessage);
|
|
856
970
|
break;
|
|
857
971
|
}
|
|
858
972
|
|
|
@@ -861,8 +975,13 @@ class IntercomBroker {
|
|
|
861
975
|
}
|
|
862
976
|
}
|
|
863
977
|
|
|
864
|
-
private rememberDisconnectedSession(
|
|
865
|
-
this.disconnectedSessions.set(
|
|
978
|
+
private rememberDisconnectedSession(session: ConnectedSession, now = Date.now()): void {
|
|
979
|
+
this.disconnectedSessions.set(session.key, {
|
|
980
|
+
info: { ...session.info },
|
|
981
|
+
key: session.key,
|
|
982
|
+
...(session.scopeId ? { scopeId: session.scopeId } : {}),
|
|
983
|
+
disconnectedAt: now,
|
|
984
|
+
});
|
|
866
985
|
this.pruneDisconnectedSessions(now);
|
|
867
986
|
}
|
|
868
987
|
|
|
@@ -880,41 +999,132 @@ class IntercomBroker {
|
|
|
880
999
|
if (now - entry.queuedAt > MAILBOX_MESSAGE_RETENTION_MS) {
|
|
881
1000
|
if (entry.message.expectsReply) {
|
|
882
1001
|
this.askEdges.delete(entry.message.id);
|
|
1002
|
+
this.removePendingAskRecord(entry.message.id, entry.fromScopeId);
|
|
883
1003
|
}
|
|
884
1004
|
this.messageReceiptRoutes.delete(entry.message.id);
|
|
1005
|
+
this.updateDeliveryRecord(entry.fromKey, entry.message.id, "failed", "Mailbox delivery expired", "E_DELIVERY_EXPIRED");
|
|
885
1006
|
this.mailboxMessages.splice(index, 1);
|
|
886
1007
|
}
|
|
887
1008
|
}
|
|
888
1009
|
}
|
|
889
1010
|
|
|
890
|
-
private queueMailboxMessage(from:
|
|
1011
|
+
private queueMailboxMessage(from: ConnectedSession, target: DisconnectedSession, message: Message, brokerReceivedAt: number): void {
|
|
891
1012
|
this.pruneMailboxMessages(brokerReceivedAt);
|
|
892
1013
|
while (this.mailboxMessages.length >= MAX_MAILBOX_MESSAGES) {
|
|
893
1014
|
const evicted = this.mailboxMessages.shift();
|
|
894
1015
|
if (!evicted) break;
|
|
895
1016
|
if (evicted.message.expectsReply) {
|
|
896
1017
|
this.askEdges.delete(evicted.message.id);
|
|
1018
|
+
this.removePendingAskRecord(evicted.message.id, evicted.fromScopeId);
|
|
897
1019
|
}
|
|
898
1020
|
this.messageReceiptRoutes.delete(evicted.message.id);
|
|
1021
|
+
this.updateDeliveryRecord(evicted.fromKey, evicted.message.id, "failed", "Mailbox capacity evicted the delivery", "E_DELIVERY_EVICTED");
|
|
899
1022
|
}
|
|
900
1023
|
this.mailboxMessages.push({
|
|
901
|
-
from: { ...from },
|
|
902
|
-
|
|
1024
|
+
from: { ...from.info },
|
|
1025
|
+
fromKey: from.key,
|
|
1026
|
+
...(from.scopeId ? { fromScopeId: from.scopeId } : {}),
|
|
1027
|
+
target: { ...target.info },
|
|
1028
|
+
targetKey: target.key,
|
|
1029
|
+
...(target.scopeId ? { targetScopeId: target.scopeId } : {}),
|
|
903
1030
|
message: { ...message, brokerReceivedAt },
|
|
904
1031
|
queuedAt: brokerReceivedAt,
|
|
905
1032
|
});
|
|
906
1033
|
}
|
|
907
1034
|
|
|
1035
|
+
private writeDeliverySuccess(socket: net.Socket, messageId: string, delivery: "socket_delivered" | "queued"): void {
|
|
1036
|
+
writeMessage(socket, { type: "delivered", messageId, delivery, retryable: false, outcomeKnown: true });
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
private writeDeliveryFailure(socket: net.Socket, messageId: string, reason: string, code: string, retryable = false): void {
|
|
1040
|
+
writeMessage(socket, { type: "delivery_failed", messageId, reason, delivery: "failed", code, retryable, outcomeKnown: true });
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
private deliveryFingerprint(message: Message, targetId: string): string {
|
|
1044
|
+
return JSON.stringify({
|
|
1045
|
+
targetId,
|
|
1046
|
+
text: message.content.text,
|
|
1047
|
+
attachments: message.content.attachments,
|
|
1048
|
+
replyTo: message.replyTo,
|
|
1049
|
+
expectsReply: message.expectsReply,
|
|
1050
|
+
supersedes: message.supersedes,
|
|
1051
|
+
retryOf: message.retryOf,
|
|
1052
|
+
provenance: message.provenance,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
private deliveryRecordKey(fromSessionId: string, messageId: string): string {
|
|
1057
|
+
return JSON.stringify([fromSessionId, messageId]);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
private replayOrReject(socket: net.Socket, fromSessionId: string, messageId: string, fingerprint: string): boolean {
|
|
1061
|
+
this.pruneDeliveryRecords();
|
|
1062
|
+
const record = this.deliveryRecords.get(this.deliveryRecordKey(fromSessionId, messageId));
|
|
1063
|
+
if (!record) return false;
|
|
1064
|
+
if (record.fingerprint !== fingerprint) {
|
|
1065
|
+
this.writeDeliveryFailure(socket, messageId, "Message id was reused with different authored content", "E_MESSAGE_ID_REUSE");
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
if (record.code === "E_TARGET_REBOUND" && record.retryable) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
if (record.state === "socket_delivered" || record.state === "queued") {
|
|
1072
|
+
this.writeDeliverySuccess(socket, messageId, record.state);
|
|
1073
|
+
} else {
|
|
1074
|
+
this.writeDeliveryFailure(socket, messageId, record.reason ?? "Previous delivery failed", record.code ?? "E_DELIVERY_FAILED", record.retryable);
|
|
1075
|
+
}
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
private recordDelivery(fromSessionId: string, messageId: string, fingerprint: string, state: DeliveryState, reason?: string, code?: string, retryable = false): void {
|
|
1080
|
+
this.pruneDeliveryRecords();
|
|
1081
|
+
while (this.deliveryRecords.size >= MAX_DELIVERY_RECORDS) {
|
|
1082
|
+
const oldest = this.deliveryRecords.keys().next().value;
|
|
1083
|
+
if (oldest === undefined) break;
|
|
1084
|
+
this.deliveryRecords.delete(oldest);
|
|
1085
|
+
}
|
|
1086
|
+
this.deliveryRecords.set(this.deliveryRecordKey(fromSessionId, messageId), {
|
|
1087
|
+
fingerprint,
|
|
1088
|
+
state,
|
|
1089
|
+
...(reason ? { reason } : {}),
|
|
1090
|
+
...(code ? { code } : {}),
|
|
1091
|
+
retryable,
|
|
1092
|
+
outcomeKnown: true,
|
|
1093
|
+
createdAt: Date.now(),
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
private pruneDeliveryRecords(now = Date.now()): void {
|
|
1098
|
+
for (const [key, record] of this.deliveryRecords) {
|
|
1099
|
+
if (now - record.createdAt > DELIVERY_RECORD_RETENTION_MS) this.deliveryRecords.delete(key);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
private updateDeliveryRecord(fromSessionId: string, messageId: string, state: DeliveryState, reason?: string, code?: string): void {
|
|
1104
|
+
const record = this.deliveryRecords.get(this.deliveryRecordKey(fromSessionId, messageId));
|
|
1105
|
+
if (!record) return;
|
|
1106
|
+
record.state = state;
|
|
1107
|
+
record.reason = reason;
|
|
1108
|
+
record.code = code;
|
|
1109
|
+
record.retryable = false;
|
|
1110
|
+
record.outcomeKnown = true;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
908
1113
|
private flushMailboxForSession(session: ConnectedSession, now = Date.now()): void {
|
|
909
1114
|
this.pruneMailboxMessages(now);
|
|
910
1115
|
const sessionName = session.info.name?.toLowerCase();
|
|
911
|
-
const uniqueMailboxIdentity = this.findLiveSessionsSharingMailboxIdentity(session
|
|
1116
|
+
const uniqueMailboxIdentity = this.findLiveSessionsSharingMailboxIdentity(session).length === 1;
|
|
912
1117
|
|
|
913
1118
|
for (let index = 0; index < this.mailboxMessages.length;) {
|
|
914
1119
|
const entry = this.mailboxMessages[index]!;
|
|
915
|
-
|
|
1120
|
+
if (!sameScope(entry.targetScopeId, session.scopeId)) {
|
|
1121
|
+
index += 1;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const matchesId = entry.targetKey === session.key;
|
|
916
1125
|
const matchesSenderIdentity = Boolean(
|
|
917
1126
|
sessionName
|
|
1127
|
+
&& sameScope(entry.fromScopeId, session.scopeId)
|
|
918
1128
|
&& entry.from.name?.toLowerCase() === sessionName
|
|
919
1129
|
&& sameCwd(entry.from.cwd, session.info.cwd),
|
|
920
1130
|
);
|
|
@@ -932,8 +1142,8 @@ class IntercomBroker {
|
|
|
932
1142
|
|
|
933
1143
|
this.mailboxMessages.splice(index, 1);
|
|
934
1144
|
const edge = this.askEdges.get(entry.message.id);
|
|
935
|
-
if (edge?.to === entry.
|
|
936
|
-
edge.to = session.
|
|
1145
|
+
if (edge?.to === entry.targetKey) {
|
|
1146
|
+
edge.to = session.key;
|
|
937
1147
|
}
|
|
938
1148
|
const deliveredMessage: Message = {
|
|
939
1149
|
...entry.message,
|
|
@@ -945,25 +1155,75 @@ class IntercomBroker {
|
|
|
945
1155
|
message: deliveredMessage,
|
|
946
1156
|
});
|
|
947
1157
|
this.messageReceiptRoutes.set(entry.message.id, {
|
|
948
|
-
from: entry.
|
|
949
|
-
to: session.
|
|
1158
|
+
from: entry.fromKey,
|
|
1159
|
+
to: session.key,
|
|
950
1160
|
createdAt: entry.message.brokerReceivedAt ?? entry.queuedAt,
|
|
951
1161
|
});
|
|
1162
|
+
this.updateDeliveryRecord(entry.fromKey, entry.message.id, "socket_delivered");
|
|
952
1163
|
}
|
|
953
1164
|
}
|
|
954
1165
|
|
|
955
1166
|
private pruneAskEdges(now = Date.now()): void {
|
|
1167
|
+
this.prunePendingAskRecords(now);
|
|
956
1168
|
for (const [messageId, edge] of this.askEdges) {
|
|
957
1169
|
if (now - edge.createdAt > this.askTimeoutMs) {
|
|
958
1170
|
this.askEdges.delete(messageId);
|
|
1171
|
+
this.removePendingAskRecord(messageId, edge.scopeId);
|
|
959
1172
|
}
|
|
960
1173
|
}
|
|
961
1174
|
}
|
|
962
1175
|
|
|
963
|
-
private clearAskEdgesForSession(
|
|
1176
|
+
private clearAskEdgesForSession(sessionKey: string): void {
|
|
964
1177
|
for (const [messageId, edge] of this.askEdges) {
|
|
965
|
-
if (edge.from ===
|
|
1178
|
+
if (edge.from === sessionKey || edge.to === sessionKey) {
|
|
966
1179
|
this.askEdges.delete(messageId);
|
|
1180
|
+
this.removePendingAskRecord(messageId, edge.scopeId);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
private writePendingAskRecord(message: Message, from: ConnectedSession, target: SessionInfo, createdAt: number): void {
|
|
1186
|
+
ensurePendingAskRecordDir();
|
|
1187
|
+
const record: PendingAskRecord = {
|
|
1188
|
+
askId: message.id,
|
|
1189
|
+
messageId: message.id,
|
|
1190
|
+
asker: { sessionId: from.info.id, name: from.info.name ?? null },
|
|
1191
|
+
target: { sessionId: target.id, name: target.name ?? null },
|
|
1192
|
+
question: message.content.text,
|
|
1193
|
+
createdAt,
|
|
1194
|
+
expiresAt: createdAt + this.askTimeoutMs,
|
|
1195
|
+
};
|
|
1196
|
+
const filePath = scopedPendingAskRecordPath(from.scopeId, message.id);
|
|
1197
|
+
writeFileSync(filePath, `${JSON.stringify(record, null, 2)}\n`, { mode: INTERCOM_RUNTIME_FILE_MODE });
|
|
1198
|
+
restrictIntercomRuntimeFile(filePath);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
private removePendingAskRecord(messageId: string, scopeId?: string): void {
|
|
1202
|
+
try {
|
|
1203
|
+
unlinkSync(scopedPendingAskRecordPath(scopeId, messageId));
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
if (!isRecord(error) || error.code !== "ENOENT") {
|
|
1206
|
+
throw error;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
private prunePendingAskRecords(now = Date.now()): void {
|
|
1212
|
+
ensurePendingAskRecordDir();
|
|
1213
|
+
for (const entry of readdirSync(PENDING_ASKS_DIR, { withFileTypes: true })) {
|
|
1214
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const filePath = join(PENDING_ASKS_DIR, entry.name);
|
|
1218
|
+
let parsed: unknown;
|
|
1219
|
+
try {
|
|
1220
|
+
parsed = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
1221
|
+
} catch {
|
|
1222
|
+
unlinkSync(filePath);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (!isPendingAskRecord(parsed) || now > parsed.expiresAt) {
|
|
1226
|
+
unlinkSync(filePath);
|
|
967
1227
|
}
|
|
968
1228
|
}
|
|
969
1229
|
}
|
|
@@ -976,52 +1236,52 @@ class IntercomBroker {
|
|
|
976
1236
|
}
|
|
977
1237
|
}
|
|
978
1238
|
|
|
979
|
-
private clearMessageReceiptRoutesForSession(
|
|
1239
|
+
private clearMessageReceiptRoutesForSession(sessionKey: string): void {
|
|
980
1240
|
for (const [messageId, route] of this.messageReceiptRoutes) {
|
|
981
|
-
if (route.from ===
|
|
1241
|
+
if (route.from === sessionKey || route.to === sessionKey) {
|
|
982
1242
|
this.messageReceiptRoutes.delete(messageId);
|
|
983
1243
|
}
|
|
984
1244
|
}
|
|
985
1245
|
}
|
|
986
1246
|
|
|
987
|
-
private findSessions(nameOrId: string): ConnectedSession[] {
|
|
988
|
-
const byId = this.sessions.get(nameOrId);
|
|
1247
|
+
private findSessions(nameOrId: string, scopeId: string | undefined): ConnectedSession[] {
|
|
1248
|
+
const byId = this.sessions.get(scopedSessionKey(scopeId, nameOrId));
|
|
989
1249
|
if (byId) {
|
|
990
1250
|
return [byId];
|
|
991
1251
|
}
|
|
992
1252
|
|
|
993
1253
|
const lowerName = nameOrId.toLowerCase();
|
|
994
|
-
const byName = Array.from(this.sessions.values()).filter(session => session.info.name?.toLowerCase() === lowerName);
|
|
1254
|
+
const byName = Array.from(this.sessions.values()).filter(session => sameScope(session.scopeId, scopeId) && session.info.name?.toLowerCase() === lowerName);
|
|
995
1255
|
if (byName.length > 0) {
|
|
996
1256
|
return byName;
|
|
997
1257
|
}
|
|
998
1258
|
|
|
999
1259
|
return Array.from(this.sessions.entries())
|
|
1000
|
-
.filter(([
|
|
1260
|
+
.filter(([, session]) => sameScope(session.scopeId, scopeId) && session.info.id.startsWith(nameOrId))
|
|
1001
1261
|
.map(([, session]) => session);
|
|
1002
1262
|
}
|
|
1003
1263
|
|
|
1004
|
-
private findDisconnectedSessions(nameOrId: string): DisconnectedSession[] {
|
|
1264
|
+
private findDisconnectedSessions(nameOrId: string, scopeId: string | undefined): DisconnectedSession[] {
|
|
1005
1265
|
this.pruneDisconnectedSessions();
|
|
1006
|
-
const byId = this.disconnectedSessions.get(nameOrId);
|
|
1266
|
+
const byId = this.disconnectedSessions.get(scopedSessionKey(scopeId, nameOrId));
|
|
1007
1267
|
if (byId) {
|
|
1008
1268
|
return [byId];
|
|
1009
1269
|
}
|
|
1010
1270
|
|
|
1011
1271
|
const lowerName = nameOrId.toLowerCase();
|
|
1012
|
-
const byName = Array.from(this.disconnectedSessions.values()).filter(session => session.info.name?.toLowerCase() === lowerName);
|
|
1272
|
+
const byName = Array.from(this.disconnectedSessions.values()).filter(session => sameScope(session.scopeId, scopeId) && session.info.name?.toLowerCase() === lowerName);
|
|
1013
1273
|
if (byName.length > 0) {
|
|
1014
1274
|
return byName;
|
|
1015
1275
|
}
|
|
1016
1276
|
|
|
1017
1277
|
return Array.from(this.disconnectedSessions.entries())
|
|
1018
|
-
.filter(([
|
|
1278
|
+
.filter(([, session]) => sameScope(session.scopeId, scopeId) && session.info.id.startsWith(nameOrId))
|
|
1019
1279
|
.map(([, session]) => session);
|
|
1020
1280
|
}
|
|
1021
1281
|
|
|
1022
|
-
private findUniqueLiveSessionForDisconnectedSession(
|
|
1023
|
-
const matches = this.findLiveSessionsSharingMailboxIdentity(
|
|
1024
|
-
.filter((session) => session.
|
|
1282
|
+
private findUniqueLiveSessionForDisconnectedSession(disconnected: DisconnectedSession, senderKey?: string): ConnectedSession | null {
|
|
1283
|
+
const matches = this.findLiveSessionsSharingMailboxIdentity(disconnected)
|
|
1284
|
+
.filter((session) => session.key !== senderKey);
|
|
1025
1285
|
return matches.length === 1 ? matches[0]! : null;
|
|
1026
1286
|
}
|
|
1027
1287
|
|
|
@@ -1036,21 +1296,22 @@ class IntercomBroker {
|
|
|
1036
1296
|
* directory differently (trailing slash, "."/"..", or a symlink such as macOS
|
|
1037
1297
|
* /tmp vs /private/tmp) still matches.
|
|
1038
1298
|
*/
|
|
1039
|
-
private findLiveSessionsSharingMailboxIdentity(
|
|
1040
|
-
const lowerName = info.name?.toLowerCase();
|
|
1041
|
-
if (!lowerName || info.runtimeFallbackAlias) {
|
|
1299
|
+
private findLiveSessionsSharingMailboxIdentity(sessionInfo: ConnectedSession | DisconnectedSession): ConnectedSession[] {
|
|
1300
|
+
const lowerName = sessionInfo.info.name?.toLowerCase();
|
|
1301
|
+
if (!lowerName || sessionInfo.info.runtimeFallbackAlias) {
|
|
1042
1302
|
return [];
|
|
1043
1303
|
}
|
|
1044
1304
|
return Array.from(this.sessions.values()).filter(session =>
|
|
1045
|
-
|
|
1305
|
+
sameScope(session.scopeId, sessionInfo.scopeId)
|
|
1306
|
+
&& !session.info.runtimeFallbackAlias
|
|
1046
1307
|
&& session.info.name?.toLowerCase() === lowerName
|
|
1047
|
-
&& sameCwd(session.info.cwd, info.cwd)
|
|
1308
|
+
&& sameCwd(session.info.cwd, sessionInfo.info.cwd)
|
|
1048
1309
|
);
|
|
1049
1310
|
}
|
|
1050
1311
|
|
|
1051
|
-
private broadcast(msg: BrokerMessage, exclude?: string): void {
|
|
1312
|
+
private broadcast(msg: BrokerMessage, exclude?: string, scopeId?: string): void {
|
|
1052
1313
|
for (const [id, session] of this.sessions) {
|
|
1053
|
-
if (id !== exclude) {
|
|
1314
|
+
if (id !== exclude && sameScope(session.scopeId, scopeId)) {
|
|
1054
1315
|
writeMessage(session.socket, msg);
|
|
1055
1316
|
}
|
|
1056
1317
|
}
|
|
@@ -1082,31 +1343,42 @@ class IntercomBroker {
|
|
|
1082
1343
|
}
|
|
1083
1344
|
|
|
1084
1345
|
private recomputeNamespaceOwners(): void {
|
|
1085
|
-
const namespaces = new
|
|
1346
|
+
const namespaces = new Map<string, { namespace: string; scopeId?: string }>();
|
|
1347
|
+
for (const [key, owner] of this.namespaceOwners) {
|
|
1348
|
+
namespaces.set(key, {
|
|
1349
|
+
namespace: owner.namespace,
|
|
1350
|
+
...(owner.scopeId ? { scopeId: owner.scopeId } : {}),
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1086
1353
|
for (const session of this.sessions.values()) {
|
|
1087
1354
|
for (const extension of session.extensions ?? []) {
|
|
1088
|
-
namespaces.
|
|
1355
|
+
namespaces.set(scopedExtensionKey(session.scopeId, extension.namespace), {
|
|
1356
|
+
namespace: extension.namespace,
|
|
1357
|
+
...(session.scopeId ? { scopeId: session.scopeId } : {}),
|
|
1358
|
+
});
|
|
1089
1359
|
}
|
|
1090
1360
|
}
|
|
1091
1361
|
|
|
1092
1362
|
// For each namespace, elect owner by (startedAt, sessionId).
|
|
1093
|
-
for (const
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1363
|
+
for (const [namespaceKey, scopedNamespace] of namespaces) {
|
|
1364
|
+
const { namespace, scopeId } = scopedNamespace;
|
|
1365
|
+
const candidates: Array<{ sessionKey: string; session: ConnectedSession }> = [];
|
|
1366
|
+
for (const [sessionKey, session] of this.sessions) {
|
|
1096
1367
|
if (session.extensions) {
|
|
1097
1368
|
const hasNamespace = session.extensions.some(
|
|
1098
|
-
(ext) => ext.namespace === namespace && ext.ownerEligible
|
|
1369
|
+
(ext) => sameScope(session.scopeId, scopeId) && ext.namespace === namespace && ext.ownerEligible
|
|
1099
1370
|
);
|
|
1100
1371
|
if (hasNamespace) {
|
|
1101
|
-
candidates.push({
|
|
1372
|
+
candidates.push({ sessionKey, session });
|
|
1102
1373
|
}
|
|
1103
1374
|
}
|
|
1104
1375
|
}
|
|
1105
1376
|
|
|
1106
1377
|
if (candidates.length === 0) {
|
|
1107
|
-
if (this.namespaceOwners.delete(
|
|
1378
|
+
if (this.namespaceOwners.delete(namespaceKey)) {
|
|
1108
1379
|
for (const session of this.sessions.values()) {
|
|
1109
|
-
const isCapable = session.
|
|
1380
|
+
const isCapable = sameScope(session.scopeId, scopeId)
|
|
1381
|
+
&& session.extensions?.some((extension) => extension.namespace === namespace);
|
|
1110
1382
|
if (isCapable) {
|
|
1111
1383
|
writeMessage(session.socket, { type: "extension_owner", namespace });
|
|
1112
1384
|
}
|
|
@@ -1122,31 +1394,35 @@ class IntercomBroker {
|
|
|
1122
1394
|
if (a.session.ownerOrder !== b.session.ownerOrder) {
|
|
1123
1395
|
return a.session.ownerOrder - b.session.ownerOrder;
|
|
1124
1396
|
}
|
|
1125
|
-
return a.
|
|
1397
|
+
return a.session.info.id.localeCompare(b.session.info.id);
|
|
1126
1398
|
});
|
|
1127
1399
|
|
|
1128
1400
|
const winner = candidates[0];
|
|
1129
|
-
const existing = this.namespaceOwners.get(
|
|
1401
|
+
const existing = this.namespaceOwners.get(namespaceKey);
|
|
1130
1402
|
|
|
1131
|
-
const ownerChanged = !existing || existing.
|
|
1403
|
+
const ownerChanged = !existing || existing.sessionKey !== winner.sessionKey;
|
|
1132
1404
|
const socketChanged = existing && existing.socket !== winner.session.socket;
|
|
1133
1405
|
|
|
1134
1406
|
if (ownerChanged || socketChanged) {
|
|
1135
1407
|
const epoch = randomUUID();
|
|
1136
|
-
this.namespaceOwners.set(
|
|
1137
|
-
|
|
1408
|
+
this.namespaceOwners.set(namespaceKey, {
|
|
1409
|
+
namespace,
|
|
1410
|
+
sessionKey: winner.sessionKey,
|
|
1411
|
+
sessionId: winner.session.info.id,
|
|
1138
1412
|
socket: winner.session.socket,
|
|
1139
1413
|
epoch,
|
|
1414
|
+
...(scopeId ? { scopeId } : {}),
|
|
1140
1415
|
});
|
|
1141
1416
|
|
|
1142
1417
|
for (const session of this.sessions.values()) {
|
|
1143
1418
|
if (session.extensions?.length) {
|
|
1144
|
-
const isCapable = session.
|
|
1419
|
+
const isCapable = sameScope(session.scopeId, scopeId)
|
|
1420
|
+
&& session.extensions.some((ext) => ext.namespace === namespace);
|
|
1145
1421
|
if (isCapable) {
|
|
1146
1422
|
writeMessage(session.socket, {
|
|
1147
1423
|
type: "extension_owner",
|
|
1148
1424
|
namespace,
|
|
1149
|
-
ownerId: winner.
|
|
1425
|
+
ownerId: winner.session.info.id,
|
|
1150
1426
|
ownerEpoch: epoch,
|
|
1151
1427
|
});
|
|
1152
1428
|
}
|
|
@@ -1158,14 +1434,14 @@ class IntercomBroker {
|
|
|
1158
1434
|
|
|
1159
1435
|
private handleExtensionPublish(
|
|
1160
1436
|
socket: net.Socket,
|
|
1161
|
-
|
|
1437
|
+
currentKey: string | null,
|
|
1162
1438
|
msg: Record<string, unknown>
|
|
1163
1439
|
): void {
|
|
1164
|
-
if (!
|
|
1440
|
+
if (!currentKey) {
|
|
1165
1441
|
throw new Error("Received extension_publish before register");
|
|
1166
1442
|
}
|
|
1167
1443
|
|
|
1168
|
-
const session = this.sessions.get(
|
|
1444
|
+
const session = this.sessions.get(currentKey);
|
|
1169
1445
|
if (!session || session.socket !== socket) {
|
|
1170
1446
|
writeMessage(socket, { type: "error", error: "Session not found" });
|
|
1171
1447
|
return;
|
|
@@ -1205,7 +1481,7 @@ class IntercomBroker {
|
|
|
1205
1481
|
return;
|
|
1206
1482
|
}
|
|
1207
1483
|
|
|
1208
|
-
const owner = this.namespaceOwners.get(namespace);
|
|
1484
|
+
const owner = this.namespaceOwners.get(scopedExtensionKey(session.scopeId, namespace));
|
|
1209
1485
|
if ((audience === "owner" || ownerOnly) && !owner) {
|
|
1210
1486
|
writeMessage(socket, { type: "error", error: "No owner for this namespace" });
|
|
1211
1487
|
return;
|
|
@@ -1217,7 +1493,7 @@ class IntercomBroker {
|
|
|
1217
1493
|
writeMessage(socket, { type: "error", error: "ownerEpoch required for owner-only messages" });
|
|
1218
1494
|
return;
|
|
1219
1495
|
}
|
|
1220
|
-
if (
|
|
1496
|
+
if (currentKey !== owner.sessionKey || socket !== owner.socket || ownerEpoch !== owner.epoch) {
|
|
1221
1497
|
writeMessage(socket, { type: "error", error: "Owner validation failed" });
|
|
1222
1498
|
return;
|
|
1223
1499
|
}
|
|
@@ -1225,6 +1501,9 @@ class IntercomBroker {
|
|
|
1225
1501
|
|
|
1226
1502
|
// Route message to appropriate audience
|
|
1227
1503
|
for (const [recipientId, recipientSession] of this.sessions) {
|
|
1504
|
+
if (!sameScope(recipientSession.scopeId, session.scopeId)) {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1228
1507
|
if (!recipientSession.extensions?.length) {
|
|
1229
1508
|
continue;
|
|
1230
1509
|
}
|
|
@@ -1237,14 +1516,14 @@ class IntercomBroker {
|
|
|
1237
1516
|
const shouldReceive =
|
|
1238
1517
|
audience === "capable" ||
|
|
1239
1518
|
(audience === "owner" && owner !== undefined &&
|
|
1240
|
-
recipientId === owner.
|
|
1519
|
+
recipientId === owner.sessionKey &&
|
|
1241
1520
|
recipientSession.socket === owner.socket);
|
|
1242
1521
|
|
|
1243
1522
|
if (shouldReceive) {
|
|
1244
1523
|
writeMessage(recipientSession.socket, {
|
|
1245
1524
|
type: "extension_message",
|
|
1246
1525
|
namespace,
|
|
1247
|
-
fromSessionId:
|
|
1526
|
+
fromSessionId: session.info.id,
|
|
1248
1527
|
...(owner ? { ownerId: owner.sessionId, ownerEpoch: owner.epoch } : {}),
|
|
1249
1528
|
payload,
|
|
1250
1529
|
});
|
|
@@ -1254,14 +1533,14 @@ class IntercomBroker {
|
|
|
1254
1533
|
|
|
1255
1534
|
private handleExtensionStateCommit(
|
|
1256
1535
|
socket: net.Socket,
|
|
1257
|
-
|
|
1536
|
+
currentKey: string | null,
|
|
1258
1537
|
msg: Record<string, unknown>
|
|
1259
1538
|
): void {
|
|
1260
|
-
if (!
|
|
1539
|
+
if (!currentKey) {
|
|
1261
1540
|
throw new Error("Received extension_state_commit before register");
|
|
1262
1541
|
}
|
|
1263
1542
|
|
|
1264
|
-
const session = this.sessions.get(
|
|
1543
|
+
const session = this.sessions.get(currentKey);
|
|
1265
1544
|
if (!session || session.socket !== socket) {
|
|
1266
1545
|
writeMessage(socket, {
|
|
1267
1546
|
type: "extension_state_result",
|
|
@@ -1299,13 +1578,14 @@ class IntercomBroker {
|
|
|
1299
1578
|
});
|
|
1300
1579
|
return;
|
|
1301
1580
|
}
|
|
1581
|
+
const stateNamespace = scopedExtensionStateNamespace(session.scopeId, namespace);
|
|
1302
1582
|
|
|
1303
1583
|
if (typeof ownerEpoch !== "string") {
|
|
1304
1584
|
writeMessage(socket, {
|
|
1305
1585
|
type: "extension_state_result",
|
|
1306
1586
|
namespace,
|
|
1307
1587
|
committed: false,
|
|
1308
|
-
revision: this.extensionStateManager.getCurrentRevision(
|
|
1588
|
+
revision: this.extensionStateManager.getCurrentRevision(stateNamespace),
|
|
1309
1589
|
reason: "Invalid ownerEpoch",
|
|
1310
1590
|
});
|
|
1311
1591
|
return;
|
|
@@ -1316,7 +1596,7 @@ class IntercomBroker {
|
|
|
1316
1596
|
type: "extension_state_result",
|
|
1317
1597
|
namespace,
|
|
1318
1598
|
committed: false,
|
|
1319
|
-
revision: this.extensionStateManager.getCurrentRevision(
|
|
1599
|
+
revision: this.extensionStateManager.getCurrentRevision(stateNamespace),
|
|
1320
1600
|
reason: "Invalid expectedRevision",
|
|
1321
1601
|
});
|
|
1322
1602
|
return;
|
|
@@ -1328,7 +1608,7 @@ class IntercomBroker {
|
|
|
1328
1608
|
type: "extension_state_result",
|
|
1329
1609
|
namespace,
|
|
1330
1610
|
committed: false,
|
|
1331
|
-
revision: this.extensionStateManager.getCurrentRevision(
|
|
1611
|
+
revision: this.extensionStateManager.getCurrentRevision(stateNamespace),
|
|
1332
1612
|
reason: "Invalid extension state or payload exceeds 64 KiB limit",
|
|
1333
1613
|
});
|
|
1334
1614
|
return;
|
|
@@ -1341,37 +1621,37 @@ class IntercomBroker {
|
|
|
1341
1621
|
type: "extension_state_result",
|
|
1342
1622
|
namespace,
|
|
1343
1623
|
committed: false,
|
|
1344
|
-
revision: this.extensionStateManager.getCurrentRevision(
|
|
1624
|
+
revision: this.extensionStateManager.getCurrentRevision(stateNamespace),
|
|
1345
1625
|
reason: "Sender does not have capability for this namespace",
|
|
1346
1626
|
});
|
|
1347
1627
|
return;
|
|
1348
1628
|
}
|
|
1349
1629
|
|
|
1350
|
-
const owner = this.namespaceOwners.get(namespace);
|
|
1630
|
+
const owner = this.namespaceOwners.get(scopedExtensionKey(session.scopeId, namespace));
|
|
1351
1631
|
if (!owner) {
|
|
1352
1632
|
writeMessage(socket, {
|
|
1353
1633
|
type: "extension_state_result",
|
|
1354
1634
|
namespace,
|
|
1355
1635
|
committed: false,
|
|
1356
|
-
revision: this.extensionStateManager.getCurrentRevision(
|
|
1636
|
+
revision: this.extensionStateManager.getCurrentRevision(stateNamespace),
|
|
1357
1637
|
reason: "No owner for this namespace",
|
|
1358
1638
|
});
|
|
1359
1639
|
return;
|
|
1360
1640
|
}
|
|
1361
1641
|
|
|
1362
1642
|
// Validate owner, socket, and epoch
|
|
1363
|
-
if (
|
|
1643
|
+
if (currentKey !== owner.sessionKey || socket !== owner.socket || ownerEpoch !== owner.epoch) {
|
|
1364
1644
|
writeMessage(socket, {
|
|
1365
1645
|
type: "extension_state_result",
|
|
1366
1646
|
namespace,
|
|
1367
1647
|
committed: false,
|
|
1368
|
-
revision: this.extensionStateManager.getCurrentRevision(
|
|
1648
|
+
revision: this.extensionStateManager.getCurrentRevision(stateNamespace),
|
|
1369
1649
|
reason: "Owner validation failed",
|
|
1370
1650
|
});
|
|
1371
1651
|
return;
|
|
1372
1652
|
}
|
|
1373
1653
|
|
|
1374
|
-
const result = this.extensionStateManager.commitState(
|
|
1654
|
+
const result = this.extensionStateManager.commitState(stateNamespace, expectedRevision, payload);
|
|
1375
1655
|
|
|
1376
1656
|
// Send result to committer
|
|
1377
1657
|
writeMessage(socket, {
|
|
@@ -1385,6 +1665,9 @@ class IntercomBroker {
|
|
|
1385
1665
|
// If committed, broadcast new state to all capable sessions
|
|
1386
1666
|
if (result.committed) {
|
|
1387
1667
|
for (const recipientSession of this.sessions.values()) {
|
|
1668
|
+
if (!sameScope(recipientSession.scopeId, session.scopeId)) {
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1388
1671
|
if (!recipientSession.extensions?.length) {
|
|
1389
1672
|
continue;
|
|
1390
1673
|
}
|