@gajae-code/coding-agent 0.13.2 → 0.14.0
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 +215 -8
- package/README.md +2 -0
- package/bin/gajaessi.js +4 -0
- package/dist/types/async/job-manager.d.ts +55 -2
- package/dist/types/async/support.d.ts +14 -0
- package/dist/types/capability/mcp.d.ts +6 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/accounts-cli.d.ts +19 -0
- package/dist/types/cli/args.d.ts +3 -0
- package/dist/types/cli/config-cli.d.ts +6 -0
- package/dist/types/cli/crash-cli.d.ts +10 -0
- package/dist/types/cli/customize-doctor.d.ts +145 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/mcp-cli.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/root-flags.d.ts +12 -0
- package/dist/types/cli/setup-cli.d.ts +3 -1
- package/dist/types/cli/skills-cli.d.ts +2 -1
- package/dist/types/cli/update-cli.d.ts +19 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/accounts.d.ts +40 -0
- package/dist/types/commands/crash.d.ts +24 -0
- package/dist/types/commands/customize.d.ts +22 -0
- package/dist/types/commands/daemon.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/commands/skills.d.ts +6 -1
- package/dist/types/commands/update.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
- package/dist/types/config/config-schema-version.d.ts +10 -0
- package/dist/types/config/file-lock.d.ts +18 -0
- package/dist/types/config/model-profile-activation.d.ts +3 -1
- package/dist/types/config/model-registry.d.ts +10 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +73 -9
- package/dist/types/config/settings.d.ts +63 -0
- package/dist/types/config/skill-settings-defaults.d.ts +26 -0
- package/dist/types/config/task-delegation.d.ts +39 -0
- package/dist/types/config/update-channel.d.ts +10 -0
- package/dist/types/coordinator-mcp/server.d.ts +5 -1
- package/dist/types/crash/index-store.d.ts +87 -0
- package/dist/types/crash/nudge.d.ts +50 -0
- package/dist/types/crash/record-loader.d.ts +17 -0
- package/dist/types/crash/report.d.ts +134 -0
- package/dist/types/crash/sanitize.d.ts +25 -0
- package/dist/types/cursor.d.ts +13 -2
- package/dist/types/customization/import.d.ts +37 -0
- package/dist/types/customization/index.d.ts +4 -0
- package/dist/types/customization/inventory.d.ts +25 -0
- package/dist/types/customization/mutations.d.ts +32 -0
- package/dist/types/customization/types.d.ts +115 -0
- package/dist/types/daemon/control-types.d.ts +8 -1
- package/dist/types/daemon/operator-contract.d.ts +4 -4
- package/dist/types/discovery/claude.d.ts +39 -1
- package/dist/types/discovery/codex.d.ts +39 -1
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/mcp-compat.d.ts +30 -0
- package/dist/types/edit/renderer.d.ts +23 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -4
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
- package/dist/types/extensibility/hooks/loader.d.ts +19 -5
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
- package/dist/types/extensibility/skill-management.d.ts +107 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
- package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
- package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
- package/dist/types/hooks/events.d.ts +60 -0
- package/dist/types/hooks/normalize.d.ts +83 -0
- package/dist/types/hooks/skill-state.d.ts +9 -2
- package/dist/types/main.d.ts +2 -1
- package/dist/types/modes/acp/abort-scope.d.ts +13 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -4
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
- package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
- package/dist/types/modes/components/customization/index.d.ts +1 -0
- package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
- package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
- package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -4
- package/dist/types/modes/components/pet-capability.d.ts +7 -21
- package/dist/types/modes/components/settings-selector.d.ts +5 -0
- package/dist/types/modes/components/status-line/gh.d.ts +6 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
- package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/defaults/index.d.ts +118 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
- package/dist/types/runtime-mcp/config.d.ts +9 -0
- package/dist/types/runtime-mcp/manager.d.ts +26 -1
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
- package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
- package/dist/types/runtime-mcp/protocol.d.ts +278 -0
- package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
- package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
- package/dist/types/runtime-mcp/types.d.ts +102 -10
- package/dist/types/sdk/acp/adapter.d.ts +26 -5
- package/dist/types/sdk/broker/broker.d.ts +3 -55
- package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
- package/dist/types/sdk/broker/session-index.d.ts +14 -5
- package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
- package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
- package/dist/types/sdk/bus/index.d.ts +85 -99
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
- package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
- package/dist/types/sdk/bus/notification-service.d.ts +52 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
- package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
- package/dist/types/sdk/bus/session-control.d.ts +25 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
- package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
- package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
- package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
- package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
- package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
- package/dist/types/sdk/cli/rows.d.ts +2 -11
- package/dist/types/sdk/cli/session-cli.d.ts +15 -25
- package/dist/types/sdk/client/client.d.ts +97 -1
- package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +18 -5
- package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
- package/dist/types/sdk/host/host.d.ts +15 -1
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/host/session-runtime.d.ts +127 -7
- package/dist/types/sdk/host/types.d.ts +1 -1
- package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +2 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
- package/dist/types/sdk/lifecycle/client.d.ts +59 -0
- package/dist/types/sdk/lifecycle/index.d.ts +3 -0
- package/dist/types/sdk/lifecycle/service.d.ts +232 -0
- package/dist/types/sdk/mcp/server.d.ts +9 -6
- package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
- package/dist/types/sdk/prompt-status.d.ts +10 -3
- package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
- package/dist/types/sdk/receipt-state.d.ts +17 -0
- package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
- package/dist/types/sdk/router/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +152 -0
- package/dist/types/sdk/session-list.d.ts +22 -0
- package/dist/types/sdk/session-reconnect.d.ts +43 -1
- package/dist/types/sdk/session.d.ts +34 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
- package/dist/types/session/account-inventory.d.ts +86 -0
- package/dist/types/session/agent-session.d.ts +145 -6
- package/dist/types/session/agent-storage.d.ts +10 -0
- package/dist/types/session/coordinator-tool-label.d.ts +28 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/secure-token-file.d.ts +3 -0
- package/dist/types/session/session-manager.d.ts +47 -1
- package/dist/types/session/startup-auth-config.d.ts +44 -0
- package/dist/types/session/terminal-abort.d.ts +378 -0
- package/dist/types/session/yield-queue.d.ts +7 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +88 -0
- package/dist/types/session-import/command.d.ts +35 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/provider-codex.d.ts +22 -0
- package/dist/types/session-import/provider-service.d.ts +38 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +47 -0
- package/dist/types/session-import/types.d.ts +120 -0
- package/dist/types/setup/paseo/check.d.ts +16 -0
- package/dist/types/setup/paseo/install-saga.d.ts +115 -0
- package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
- package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
- package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
- package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
- package/dist/types/setup/paseo/provider-config.d.ts +35 -0
- package/dist/types/setup/paseo/remove.d.ts +14 -0
- package/dist/types/setup/paseo/result-types.d.ts +59 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
- package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
- package/dist/types/skill-state/active-state.d.ts +5 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
- package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
- package/dist/types/slash-commands/types.d.ts +7 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/tools/ask-answer-registry.d.ts +6 -3
- package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
- package/dist/types/tools/bash.d.ts +2 -6
- package/dist/types/tools/browser/attach.d.ts +11 -0
- package/dist/types/tools/browser/launch.d.ts +20 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +10 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/monitor.d.ts +1 -1
- package/dist/types/tools/skill-discovery.d.ts +8 -1
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
- package/dist/types/utils/herdr-pane.d.ts +76 -0
- package/package.json +19 -9
- package/scripts/build-binary.ts +2 -1
- package/scripts/build-sdk-package-smoke.ts +2 -13
- package/scripts/capture-sticky-viewport-showcase.ts +0 -1
- package/scripts/compile-args.ts +2 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
- package/scripts/generate-sdk-operation-inventory.ts +28 -0
- package/scripts/generate-telegram-baseline-manifest.ts +1 -0
- package/scripts/generate-tool-catalog.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
- package/src/async/job-manager.ts +352 -17
- package/src/async/support.ts +25 -0
- package/src/autoresearch/dashboard.ts +28 -13
- package/src/capability/index.ts +3 -1
- package/src/capability/mcp.ts +6 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/accounts-cli.ts +395 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/auth-broker-cli.ts +136 -66
- package/src/cli/auth-gateway-cli.ts +94 -70
- package/src/cli/config-cli.ts +27 -2
- package/src/cli/crash-cli.ts +78 -0
- package/src/cli/customize-doctor.ts +1605 -0
- package/src/cli/daemon-cli.ts +12 -3
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/mcp-cli.ts +128 -60
- package/src/cli/notify-cli.ts +8 -14
- package/src/cli/root-flags.ts +8 -0
- package/src/cli/setup-cli.ts +145 -0
- package/src/cli/skills-cli.ts +38 -2
- package/src/cli/update-cli.ts +167 -12
- package/src/cli.ts +4 -0
- package/src/commands/accounts.ts +71 -0
- package/src/commands/crash.ts +35 -0
- package/src/commands/customize.ts +84 -0
- package/src/commands/daemon.ts +2 -2
- package/src/commands/deep-interview.ts +2 -0
- package/src/commands/launch.ts +1 -0
- package/src/commands/mcp.ts +12 -7
- package/src/commands/ralplan.ts +2 -0
- package/src/commands/sdk.ts +65 -32
- package/src/commands/setup.ts +5 -0
- package/src/commands/skills.ts +14 -4
- package/src/commands/ultragoal.ts +10 -1
- package/src/commands/update.ts +26 -15
- package/src/config/atomic-yaml-patch-worker.ts +57 -0
- package/src/config/atomic-yaml-patch.ts +769 -29
- package/src/config/config-schema-version.ts +10 -0
- package/src/config/file-lock.ts +70 -1
- package/src/config/mcp-schema.json +52 -14
- package/src/config/model-profile-activation.ts +23 -2
- package/src/config/model-profiles.ts +88 -21
- package/src/config/model-registry.ts +147 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +77 -7
- package/src/config/settings.ts +4038 -199
- package/src/config/skill-settings-defaults.ts +41 -3
- package/src/config/task-delegation.ts +87 -0
- package/src/config/update-channel.ts +23 -0
- package/src/coordinator-mcp/server.ts +377 -308
- package/src/crash/index-store.ts +723 -0
- package/src/crash/nudge.ts +89 -0
- package/src/crash/record-loader.ts +149 -0
- package/src/crash/report.ts +551 -0
- package/src/crash/sanitize.ts +129 -0
- package/src/cursor.ts +36 -10
- package/src/customization/import.ts +946 -0
- package/src/customization/index.ts +4 -0
- package/src/customization/inventory.ts +335 -0
- package/src/customization/mutations.ts +141 -0
- package/src/customization/types.ts +223 -0
- package/src/daemon/control-types.ts +8 -1
- package/src/daemon/operator-contract.ts +6 -6
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
- package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/discovery/claude-hooks.ts +38 -0
- package/src/discovery/claude.ts +142 -209
- package/src/discovery/codex-hooks.ts +38 -0
- package/src/discovery/codex.ts +132 -198
- package/src/discovery/helpers.ts +8 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/mcp-compat.ts +234 -0
- package/src/discovery/mcp-json.ts +6 -0
- package/src/edit/renderer.ts +40 -0
- package/src/edit/streaming.ts +4 -2
- package/src/exec/bash-executor.ts +24 -20
- package/src/export/html/index.ts +51 -2
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +17 -4
- package/src/extensibility/extensions/types.ts +31 -6
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
- package/src/extensibility/gjc-plugins/installer.ts +9 -5
- package/src/extensibility/gjc-plugins/observability.ts +8 -2
- package/src/extensibility/gjc-plugins/registry.ts +13 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
- package/src/extensibility/gjc-plugins/schema.ts +222 -39
- package/src/extensibility/hooks/loader.ts +154 -10
- package/src/extensibility/runtime-skill-discovery.ts +257 -60
- package/src/extensibility/skill-management.ts +368 -0
- package/src/extensibility/skills.ts +52 -16
- package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
- package/src/gjc-runtime/launch-worktree.ts +56 -2
- package/src/gjc-runtime/linux-proc.ts +7 -1
- package/src/gjc-runtime/ralplan-runtime.ts +100 -164
- package/src/gjc-runtime/session-state-lock.ts +992 -0
- package/src/gjc-runtime/session-state-sidecar.ts +644 -142
- package/src/gjc-runtime/state-writer.ts +39 -14
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
- package/src/gjc-runtime/workflow-settings.ts +596 -0
- package/src/goals/tools/goal-tool.ts +6 -1
- package/src/hooks/events.ts +291 -0
- package/src/hooks/native-skill-hook.ts +4 -0
- package/src/hooks/normalize.ts +369 -0
- package/src/hooks/skill-state.ts +165 -46
- package/src/internal-urls/docs-index.generated.ts +41 -32
- package/src/main.ts +36 -5
- package/src/migrate/mcp-mapper.ts +25 -2
- package/src/modes/acp/abort-scope.ts +29 -0
- package/src/modes/acp/acp-agent.ts +423 -98
- package/src/modes/components/assistant-message.ts +6 -1
- package/src/modes/components/chrome.ts +4 -3
- package/src/modes/components/customization/customization-dashboard.ts +443 -0
- package/src/modes/components/customization/import-wizard.ts +372 -0
- package/src/modes/components/customization/index.ts +1 -0
- package/src/modes/components/dynamic-theme-text.ts +22 -0
- package/src/modes/components/gajae-pet-widget.ts +607 -193
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/model-selector.ts +1 -6
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +35 -31
- package/src/modes/components/settings-selector.ts +29 -5
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +19 -4
- package/src/modes/components/tool-status-header.ts +13 -3
- package/src/modes/controllers/command-controller.ts +20 -8
- package/src/modes/controllers/extension-ui-controller.ts +56 -257
- package/src/modes/controllers/input-controller.ts +10 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
- package/src/modes/controllers/selector-controller.ts +238 -77
- package/src/modes/controllers/stt-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +88 -16
- package/src/modes/print-mode.ts +35 -6
- package/src/modes/runtime-init.ts +21 -68
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/defaults/ouroboros.json +118 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +25 -0
- package/src/modes/utils/terminal-bell.ts +15 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/critic.md +1 -1
- package/src/prompts/system/rlm-research.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -2
- package/src/prompts/tools/bash.md +7 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +6 -6
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/skill-discovery.md +4 -2
- package/src/runtime-mcp/client.ts +427 -23
- package/src/runtime-mcp/config.ts +37 -5
- package/src/runtime-mcp/manager-pool.test.ts +56 -0
- package/src/runtime-mcp/manager.ts +52 -2
- package/src/runtime-mcp/oauth-discovery.ts +13 -0
- package/src/runtime-mcp/oauth-flow.ts +46 -1
- package/src/runtime-mcp/pool-key.ts +5 -1
- package/src/runtime-mcp/pool.test.ts +9 -0
- package/src/runtime-mcp/protocol.test.ts +353 -0
- package/src/runtime-mcp/protocol.ts +624 -0
- package/src/runtime-mcp/scope-config.ts +78 -0
- package/src/runtime-mcp/tool-bridge.ts +44 -10
- package/src/runtime-mcp/transports/http.ts +84 -13
- package/src/runtime-mcp/types.ts +123 -12
- package/src/sdk/acp/adapter.ts +328 -97
- package/src/sdk/broker/broker.ts +160 -741
- package/src/sdk/broker/ensure.ts +9 -1
- package/src/sdk/broker/lifecycle.ts +510 -116
- package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
- package/src/sdk/broker/process-incarnation.ts +19 -0
- package/src/sdk/broker/session-index.ts +516 -117
- package/src/sdk/broker/startup-failure.ts +28 -5
- package/src/sdk/broker/transport.ts +1 -13
- package/src/sdk/bus/chat-daemon-cli.ts +4 -2
- package/src/sdk/bus/chat-daemon-control.ts +67 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
- package/src/sdk/bus/config.ts +13 -0
- package/src/sdk/bus/conversation-store.ts +59 -171
- package/src/sdk/bus/daemon-paths.ts +20 -0
- package/src/sdk/bus/discord-conversation.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +524 -126
- package/src/sdk/bus/discord-live-provider.ts +206 -79
- package/src/sdk/bus/existing-thread-readiness.ts +4 -97
- package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
- package/src/sdk/bus/index.ts +2480 -456
- package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
- package/src/sdk/bus/lifecycle-commands.ts +2 -2
- package/src/sdk/bus/managed-daemon.ts +2 -145
- package/src/sdk/bus/notification-service.ts +196 -0
- package/src/sdk/bus/operator-runtime.ts +1 -1
- package/src/sdk/bus/prompt-reconciliation.ts +24 -15
- package/src/sdk/bus/reconciliation-store.ts +553 -24
- package/src/sdk/bus/session-control.ts +49 -3
- package/src/sdk/bus/slack-conversation.ts +4 -0
- package/src/sdk/bus/slack-daemon.ts +733 -134
- package/src/sdk/bus/slack-live-provider.ts +65 -15
- package/src/sdk/bus/slack-provider.ts +4 -0
- package/src/sdk/bus/slack-thread-binding.ts +9 -104
- package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
- package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
- package/src/sdk/bus/telegram-daemon-control.ts +3 -13
- package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
- package/src/sdk/bus/telegram-daemon.ts +2552 -2280
- package/src/sdk/bus/telegram-reference.ts +1 -226
- package/src/sdk/bus/telegram-setup.ts +2 -2
- package/src/sdk/bus/threaded-render.ts +1 -0
- package/src/sdk/bus/topic-registry.ts +279 -146
- package/src/sdk/cli/rows.ts +2 -35
- package/src/sdk/cli/session-cli.ts +776 -700
- package/src/sdk/client/client.ts +892 -1
- package/src/sdk/host/control/dispatch.ts +93 -58
- package/src/sdk/host/control/index.ts +1 -1
- package/src/sdk/host/control/operations.ts +18 -10
- package/src/sdk/host/control/runtime-gate.ts +40 -0
- package/src/sdk/host/host.ts +63 -12
- package/src/sdk/host/query/handlers.ts +66 -7
- package/src/sdk/host/reverse-leases.ts +2 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +2787 -34
- package/src/sdk/host/session-runtime.ts +1489 -101
- package/src/sdk/host/surface-policy.ts +12 -8
- package/src/sdk/host/types.ts +1 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
- package/src/sdk/host/websocket-transport.ts +85 -50
- package/src/sdk/index.ts +2 -1
- package/src/sdk/lifecycle/broker-client.ts +53 -0
- package/src/sdk/lifecycle/client.ts +234 -0
- package/src/sdk/lifecycle/index.ts +3 -0
- package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
- package/src/sdk/lifecycle/service.ts +720 -0
- package/src/sdk/mcp/server.ts +226 -188
- package/src/sdk/prompt-deadline-lease.ts +55 -0
- package/src/sdk/prompt-deadline-manager.ts +148 -0
- package/src/sdk/prompt-status.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +272 -129
- package/src/sdk/protocol/operation-registry.ts +71 -8
- package/src/sdk/receipt-state.ts +43 -0
- package/src/sdk/reconciliation-extensions.ts +14 -0
- package/src/sdk/router/index.ts +1 -0
- package/src/sdk/router/session-router.ts +1658 -0
- package/src/sdk/session-list.ts +93 -0
- package/src/sdk/session-reconnect.ts +45 -1
- package/src/sdk/session.ts +716 -120
- package/src/sdk/transport/serve-cli.ts +18 -34
- package/src/session/account-inventory.ts +603 -0
- package/src/session/agent-session.ts +2873 -559
- package/src/session/agent-storage.ts +35 -0
- package/src/session/blob-store.ts +32 -1
- package/src/session/coordinator-tool-label.ts +40 -0
- package/src/session/internal/managed-session-scope.ts +4 -0
- package/src/session/internal/managed-session-storage.ts +199 -31
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +757 -57
- package/src/session/session-storage.ts +25 -16
- package/src/session/startup-auth-config.ts +297 -0
- package/src/session/terminal-abort.ts +1253 -0
- package/src/session/yield-queue.ts +57 -15
- package/src/session-import/claude.ts +334 -0
- package/src/session-import/codex.ts +1033 -0
- package/src/session-import/command.ts +77 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +7 -0
- package/src/session-import/provider-codex.ts +327 -0
- package/src/session-import/provider-service.ts +457 -0
- package/src/session-import/redact.ts +128 -0
- package/src/session-import/service.ts +1264 -0
- package/src/session-import/types.ts +148 -0
- package/src/setup/paseo/check.ts +213 -0
- package/src/setup/paseo/install-saga.ts +293 -0
- package/src/setup/paseo/json-publisher.ts +263 -0
- package/src/setup/paseo/orchestration-preferences.ts +86 -0
- package/src/setup/paseo/paseo-ownership.ts +224 -0
- package/src/setup/paseo/paseo-setup.ts +253 -0
- package/src/setup/paseo/provider-config.ts +139 -0
- package/src/setup/paseo/remove.ts +183 -0
- package/src/setup/paseo/result-types.ts +79 -0
- package/src/setup/paseo/setup-deps.ts +175 -0
- package/src/setup/paseo/skills-bridge.ts +260 -0
- package/src/skill-state/active-state.ts +21 -11
- package/src/slash-commands/acp-builtins.ts +9 -1
- package/src/slash-commands/builtin-registry.ts +117 -19
- package/src/slash-commands/helpers/credential-switch.ts +143 -0
- package/src/slash-commands/helpers/mcp.ts +9 -3
- package/src/slash-commands/helpers/usage-report.ts +61 -56
- package/src/slash-commands/types.ts +7 -2
- package/src/task/executor.ts +30 -11
- package/src/task/index.ts +66 -7
- package/src/task/render.ts +23 -9
- package/src/tools/ask-answer-registry.ts +23 -7
- package/src/tools/ask.ts +8 -4
- package/src/tools/bash-sleep-advisory.ts +115 -0
- package/src/tools/bash.ts +98 -18
- package/src/tools/browser/attach.ts +71 -3
- package/src/tools/browser/launch.ts +86 -13
- package/src/tools/browser/profile-discovery.ts +57 -7
- package/src/tools/browser/render.ts +8 -1
- package/src/tools/browser.ts +130 -21
- package/src/tools/computer.enforcement.test.ts +1 -1
- package/src/tools/computer.ts +3 -2
- package/src/tools/cron.ts +12 -1
- package/src/tools/image-gen.ts +349 -142
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +33 -6
- package/src/tools/monitor.ts +41 -2
- package/src/tools/skill-discovery.ts +21 -29
- package/src/tools/subagent.ts +142 -24
- package/src/tools/todo-contract.ts +55 -3
- package/src/tools/todo-write.ts +4 -3
- package/src/tools/tool-catalog.generated.ts +6 -6
- package/src/tools/ultragoal-ask-guard.ts +10 -3
- package/src/utils/herdr-pane.ts +345 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/scrapers/index.ts +6 -4
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
- package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
- package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
- package/dist/types/runtime/lazy-service.test.d.ts +0 -1
- package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
- package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
- package/dist/types/runtime-mcp/config.test.d.ts +0 -1
- package/dist/types/runtime-mcp/http.test.d.ts +0 -1
- package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
- package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
- package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
- package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
- package/dist/types/sdk/elevation/audit.d.ts +0 -93
- package/dist/types/sdk/elevation/capability.d.ts +0 -3
- package/dist/types/sdk/elevation/digest.d.ts +0 -72
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
- package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
- package/dist/types/sdk/elevation/owner.d.ts +0 -48
- package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
- package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
- package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
- package/dist/types/skills/skills.test.d.ts +0 -1
- package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
- package/dist/types/tools/descriptors.test.d.ts +0 -1
- package/scripts/g005-daemon-smoke.ts +0 -180
- package/scripts/g011-daemon-path-smoke.ts +0 -153
- package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
- package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
- package/src/sdk/bus/telegram-cli.ts +0 -212
- package/src/sdk/elevation/allowlist.ts +0 -54
- package/src/sdk/elevation/audit.ts +0 -208
- package/src/sdk/elevation/capability.ts +0 -41
- package/src/sdk/elevation/digest.ts +0 -129
- package/src/sdk/elevation/dispatch-receipt.ts +0 -184
- package/src/sdk/elevation/grant-ledger.ts +0 -1142
- package/src/sdk/elevation/owner.ts +0 -109
- package/src/session/auth-broker-config.ts +0 -109
- /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
- /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
- /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Forum-topic registry for eligible orchestration sessions on the threaded
|
|
4
|
+
* Telegram surface.
|
|
4
5
|
*
|
|
5
|
-
* Each
|
|
6
|
-
* daemon-created topics without deleting their durable
|
|
7
|
-
* successors move inactive records into retained history before
|
|
8
|
-
* active authority. The registry also tracks whether the one-time
|
|
9
|
-
* header has already been pinned.
|
|
6
|
+
* Each eligible orchestration session owns one active Telegram forum topic.
|
|
7
|
+
* Remote archive closes daemon-created topics without deleting their durable
|
|
8
|
+
* records; rotated successors move inactive records into retained history before
|
|
9
|
+
* creating a new active authority. The registry also tracks whether the one-time
|
|
10
|
+
* identity header has already been pinned.
|
|
10
11
|
*
|
|
11
12
|
* State is a plain serialisable map persisted beside the daemon state files;
|
|
12
13
|
* topic creation is injected so this module is pure and unit-testable without a
|
|
@@ -60,6 +61,10 @@ export interface TopicRecord {
|
|
|
60
61
|
authorityState?: TopicLifecycleState;
|
|
61
62
|
/** Telegram chat and endpoint authority last proven to use this topic. */
|
|
62
63
|
chatId?: string;
|
|
64
|
+
/** Stable provider binding; never contains SDK endpoint or attachment identity. */
|
|
65
|
+
telegramBinding?: TelegramTopicBinding;
|
|
66
|
+
/** Why this record is archive-fenced; absence means no remote archive is authorized. */
|
|
67
|
+
archiveReason?: "session_closed" | "notification_subscription_removed" | "create_compensation";
|
|
63
68
|
/** Canonical endpoint tuple (URL + token) that currently holds the lease. */
|
|
64
69
|
endpointKey?: string;
|
|
65
70
|
/** Authenticated endpoint authority digest, excluding transport presentation. */
|
|
@@ -87,6 +92,8 @@ export interface TopicCreateClaim {
|
|
|
87
92
|
leaseOwner?: string;
|
|
88
93
|
authorityEpoch: number;
|
|
89
94
|
createdAt: number;
|
|
95
|
+
telegramBinding?: TelegramTopicBinding;
|
|
96
|
+
/** Legacy v2 input only; never emitted by serialize(). */
|
|
90
97
|
binding?: TopicEndpointBinding;
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -114,8 +121,8 @@ export interface TopicRegistryState {
|
|
|
114
121
|
topics: Record<string, TopicRecord>;
|
|
115
122
|
/** Durable lifecycle epochs retained after an archive starts. */
|
|
116
123
|
fences?: Record<string, number>;
|
|
117
|
-
/** Closed
|
|
118
|
-
closedEndpoints?: Record<string,
|
|
124
|
+
/** Closed durable Telegram bindings; values intentionally contain no SDK endpoint identity. */
|
|
125
|
+
closedEndpoints?: Record<string, TelegramTopicBinding>;
|
|
119
126
|
/** Persistent host identity used to distinguish concurrent installations. */
|
|
120
127
|
installationHostId?: string;
|
|
121
128
|
/** Bounded durable archive work; no topic record is physically deleted. */
|
|
@@ -134,11 +141,22 @@ export interface TopicRegistryCasAuthority {
|
|
|
134
141
|
compareAndSet(expectedGeneration: number, next: TopicRegistryState): Promise<boolean>;
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
/** Durable Telegram authority. This is the only binding shape writers persist. */
|
|
145
|
+
export interface TelegramTopicBinding {
|
|
146
|
+
chatId: string;
|
|
147
|
+
transport: "telegram";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type TopicArchiveReason = NonNullable<TopicRecord["archiveReason"]>;
|
|
151
|
+
|
|
137
152
|
/** Authenticated runtime binding for a durable topic lease. */
|
|
138
153
|
export interface TopicEndpointBinding {
|
|
139
154
|
chatId: string;
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
transport?: "telegram";
|
|
156
|
+
logicalSessionId?: string;
|
|
157
|
+
/** Legacy input-only fields. They are never authority or persistence fields. */
|
|
158
|
+
endpointKey?: string;
|
|
159
|
+
endpointDigest?: string;
|
|
142
160
|
endpointGeneration?: number;
|
|
143
161
|
}
|
|
144
162
|
|
|
@@ -159,6 +177,12 @@ export interface TopicArchiveAuthoritySnapshot {
|
|
|
159
177
|
record?: TopicRecord;
|
|
160
178
|
}
|
|
161
179
|
|
|
180
|
+
function isTopicArchiveReason(value: unknown): value is TopicArchiveReason {
|
|
181
|
+
return (
|
|
182
|
+
value === "session_closed" || value === "notification_subscription_removed" || value === "create_compensation"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
162
186
|
function isValidBindingString(value: unknown): value is string {
|
|
163
187
|
return typeof value === "string" && value.trim().length > 0;
|
|
164
188
|
}
|
|
@@ -168,6 +192,7 @@ function isValidBindingGeneration(value: unknown): value is number {
|
|
|
168
192
|
}
|
|
169
193
|
function hasAnyBinding(record: TopicRecord): boolean {
|
|
170
194
|
return (
|
|
195
|
+
record.telegramBinding !== undefined ||
|
|
171
196
|
record.chatId !== undefined ||
|
|
172
197
|
record.endpointKey !== undefined ||
|
|
173
198
|
record.endpointDigest !== undefined ||
|
|
@@ -176,22 +201,23 @@ function hasAnyBinding(record: TopicRecord): boolean {
|
|
|
176
201
|
);
|
|
177
202
|
}
|
|
178
203
|
|
|
179
|
-
function
|
|
204
|
+
function hasStableTelegramBinding(record: TopicRecord): boolean {
|
|
180
205
|
return (
|
|
181
|
-
|
|
182
|
-
isValidBindingString(record.
|
|
183
|
-
|
|
184
|
-
(record.endpointGeneration === undefined || isValidBindingGeneration(record.endpointGeneration)) &&
|
|
185
|
-
(record.endpointIncarnation === undefined || isValidBindingGeneration(record.endpointIncarnation))
|
|
206
|
+
record.telegramBinding !== undefined &&
|
|
207
|
+
isValidBindingString(record.telegramBinding.chatId) &&
|
|
208
|
+
record.telegramBinding.transport === "telegram"
|
|
186
209
|
);
|
|
187
210
|
}
|
|
188
211
|
|
|
212
|
+
function hasCompleteBinding(record: TopicRecord): boolean {
|
|
213
|
+
return hasStableTelegramBinding(record);
|
|
214
|
+
}
|
|
215
|
+
|
|
189
216
|
function hasValidBinding(binding: TopicEndpointBinding): boolean {
|
|
190
217
|
return (
|
|
191
218
|
isValidBindingString(binding.chatId) &&
|
|
192
|
-
|
|
193
|
-
isValidBindingString(binding.
|
|
194
|
-
(binding.endpointGeneration === undefined || isValidBindingGeneration(binding.endpointGeneration))
|
|
219
|
+
(binding.transport === undefined || binding.transport === "telegram") &&
|
|
220
|
+
(binding.logicalSessionId === undefined || isValidBindingString(binding.logicalSessionId))
|
|
195
221
|
);
|
|
196
222
|
}
|
|
197
223
|
|
|
@@ -207,15 +233,38 @@ function nextAuthorityEpoch(current: number): number {
|
|
|
207
233
|
export function emptyTopicRegistryState(): TopicRegistryState {
|
|
208
234
|
return { version: 2, topics: {} };
|
|
209
235
|
}
|
|
236
|
+
|
|
237
|
+
/** Strip SDK endpoint authority from legacy v2 records without archiving them. */
|
|
238
|
+
function migrateLegacyTopicBinding(record: Record<string, unknown>): Record<string, unknown> {
|
|
239
|
+
const migrated = { ...record };
|
|
240
|
+
const chatId =
|
|
241
|
+
isObjectLike(migrated.telegramBinding) && isValidBindingString(migrated.telegramBinding.chatId)
|
|
242
|
+
? migrated.telegramBinding.chatId
|
|
243
|
+
: migrated.chatId;
|
|
244
|
+
if (isValidBindingString(chatId)) {
|
|
245
|
+
migrated.telegramBinding = { chatId, transport: "telegram" };
|
|
246
|
+
delete migrated.endpointKey;
|
|
247
|
+
delete migrated.endpointDigest;
|
|
248
|
+
delete migrated.endpointGeneration;
|
|
249
|
+
delete migrated.endpointIncarnation;
|
|
250
|
+
}
|
|
251
|
+
return migrated;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
255
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
256
|
+
}
|
|
210
257
|
/**
|
|
211
258
|
* Reject snapshots written by a newer daemon. Missing versions are preserved as
|
|
212
259
|
* evidence but quarantined: legacy records must never route or mutate remotely.
|
|
213
260
|
*/
|
|
214
261
|
export function parseTopicRegistryState(value: unknown): TopicRegistryState | undefined {
|
|
215
|
-
if (
|
|
262
|
+
if (value === undefined) return undefined;
|
|
263
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("malformed Telegram topic state");
|
|
216
264
|
let state = value as TopicRegistryState;
|
|
217
265
|
if (state.version !== undefined && state.version !== 2) throw new Error("unsupported future Telegram topic state");
|
|
218
|
-
if (!state.topics || typeof state.topics !== "object" || Array.isArray(state.topics))
|
|
266
|
+
if (!state.topics || typeof state.topics !== "object" || Array.isArray(state.topics))
|
|
267
|
+
throw new Error("malformed Telegram topic state");
|
|
219
268
|
if (state.version === undefined) {
|
|
220
269
|
if (state.registryGeneration !== undefined) throw new Error("malformed Telegram topic state");
|
|
221
270
|
return parseTopicRegistryState({
|
|
@@ -228,6 +277,7 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
228
277
|
record && typeof record === "object"
|
|
229
278
|
? {
|
|
230
279
|
...record,
|
|
280
|
+
...migrateLegacyTopicBinding(record as unknown as Record<string, unknown>),
|
|
231
281
|
topicOrigin: (record as TopicRecord).topicOrigin ?? "daemon_created",
|
|
232
282
|
authorityState: "legacy_quarantined",
|
|
233
283
|
}
|
|
@@ -258,7 +308,40 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
258
308
|
: records,
|
|
259
309
|
]),
|
|
260
310
|
),
|
|
311
|
+
// Pre-#4401 writers persisted closed-endpoint bindings without the
|
|
312
|
+
// `transport` discriminator. Rejecting them would permanently brick an
|
|
313
|
+
// otherwise-healthy shared registry (the same class as the
|
|
314
|
+
// disconnectGraceExpiresAt artifact below): the CAS authority refuses
|
|
315
|
+
// every read and write forever. The transport can only be "telegram" in
|
|
316
|
+
// those snapshots, so heal the in-memory record; the serializer no
|
|
317
|
+
// longer persists closed endpoints at all. Only heal records that match
|
|
318
|
+
// the complete pre-#4401 shape (chatId + endpointKey + endpointDigest +
|
|
319
|
+
// endpointGeneration): partial records are corruption, not legacy data.
|
|
320
|
+
closedEndpoints:
|
|
321
|
+
state.closedEndpoints === undefined
|
|
322
|
+
? undefined
|
|
323
|
+
: isObject(state.closedEndpoints)
|
|
324
|
+
? Object.fromEntries(
|
|
325
|
+
Object.entries(state.closedEndpoints).map(([sessionId, binding]) => [
|
|
326
|
+
sessionId,
|
|
327
|
+
isObject(binding) &&
|
|
328
|
+
binding.transport === undefined &&
|
|
329
|
+
isValidBindingString(binding.chatId) &&
|
|
330
|
+
isValidBindingString(binding.endpointKey) &&
|
|
331
|
+
isValidBindingString(binding.endpointDigest) &&
|
|
332
|
+
isValidBindingGeneration(binding.endpointGeneration)
|
|
333
|
+
? ({ chatId: binding.chatId, transport: "telegram" } as TelegramTopicBinding)
|
|
334
|
+
: binding,
|
|
335
|
+
]),
|
|
336
|
+
)
|
|
337
|
+
: state.closedEndpoints,
|
|
261
338
|
};
|
|
339
|
+
state.topics = Object.fromEntries(
|
|
340
|
+
Object.entries(state.topics).map(([sessionId, record]) => [
|
|
341
|
+
sessionId,
|
|
342
|
+
isObject(record) ? (migrateLegacyTopicBinding(record) as unknown as TopicRecord) : record,
|
|
343
|
+
]),
|
|
344
|
+
);
|
|
262
345
|
const validTimestamp = (candidate: unknown): candidate is number =>
|
|
263
346
|
typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0;
|
|
264
347
|
const validEpoch = (candidate: unknown): candidate is number =>
|
|
@@ -268,12 +351,13 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
268
351
|
const validBinding = (candidate: unknown): candidate is TopicEndpointBinding =>
|
|
269
352
|
isObject(candidate) &&
|
|
270
353
|
isValidBindingString(candidate.chatId) &&
|
|
271
|
-
|
|
272
|
-
isValidBindingString(candidate.
|
|
273
|
-
(candidate.endpointGeneration === undefined || isValidBindingGeneration(candidate.endpointGeneration));
|
|
354
|
+
(candidate.transport === undefined || candidate.transport === "telegram") &&
|
|
355
|
+
(candidate.logicalSessionId === undefined || isValidBindingString(candidate.logicalSessionId));
|
|
274
356
|
const malformed = (): never => {
|
|
275
357
|
throw new Error("malformed Telegram topic state");
|
|
276
358
|
};
|
|
359
|
+
const validTelegramBinding = (candidate: unknown): candidate is TelegramTopicBinding =>
|
|
360
|
+
isObject(candidate) && isValidBindingString(candidate.chatId) && candidate.transport === "telegram";
|
|
277
361
|
|
|
278
362
|
if (
|
|
279
363
|
(state.registryGeneration !== undefined && !validEpoch(state.registryGeneration)) ||
|
|
@@ -286,6 +370,13 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
286
370
|
for (const [sessionId, raw] of Object.entries(state.topics)) {
|
|
287
371
|
if (!isValidBindingString(sessionId) || !isObject(raw) || !isValidTopicId(raw.topicId)) malformed();
|
|
288
372
|
if (typeof raw.identitySent !== "boolean" || !validTimestamp(raw.createdAt)) malformed();
|
|
373
|
+
if (
|
|
374
|
+
raw.telegramBinding !== undefined &&
|
|
375
|
+
(!isObject(raw.telegramBinding) ||
|
|
376
|
+
!isValidBindingString(raw.telegramBinding.chatId) ||
|
|
377
|
+
raw.telegramBinding.transport !== "telegram")
|
|
378
|
+
)
|
|
379
|
+
malformed();
|
|
289
380
|
if (
|
|
290
381
|
!validOptionalString(raw.sessionUuid) ||
|
|
291
382
|
(raw.orphanedAt !== undefined && !validTimestamp(raw.orphanedAt)) ||
|
|
@@ -332,8 +423,13 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
332
423
|
raw.authorityState !== "inactive"
|
|
333
424
|
)
|
|
334
425
|
malformed();
|
|
335
|
-
//
|
|
336
|
-
//
|
|
426
|
+
// A grace deadline on a non-grace record is a known artifact of the
|
|
427
|
+
// v0.12.12–v0.12.17 archive writers, which moved disconnect_grace records
|
|
428
|
+
// to archive_pending without clearing disconnectGraceExpiresAt (fixed by
|
|
429
|
+
// #4054). Rejecting it here permanently bricked otherwise-healthy shared
|
|
430
|
+
// registries: the CAS authority refused every read and write forever.
|
|
431
|
+
// The field is inert outside disconnect_grace, so drop it here to heal
|
|
432
|
+
// the in-memory state; the next serialize no longer persists it.
|
|
337
433
|
delete raw.disconnectGraceExpiresAt;
|
|
338
434
|
}
|
|
339
435
|
const hasBinding = hasAnyBinding(raw);
|
|
@@ -346,15 +442,12 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
346
442
|
raw.endpointDigest === undefined &&
|
|
347
443
|
raw.endpointGeneration === undefined &&
|
|
348
444
|
raw.endpointIncarnation === undefined;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
(raw.endpointGeneration !== undefined && !isValidBindingGeneration(raw.endpointGeneration)) ||
|
|
356
|
-
(raw.endpointIncarnation !== undefined && !isValidBindingGeneration(raw.endpointIncarnation)))
|
|
357
|
-
)
|
|
445
|
+
const hasStableTelegramBinding =
|
|
446
|
+
raw.telegramBinding !== undefined &&
|
|
447
|
+
isObjectLike(raw.telegramBinding) &&
|
|
448
|
+
isValidBindingString(raw.telegramBinding.chatId) &&
|
|
449
|
+
raw.telegramBinding.transport === "telegram";
|
|
450
|
+
if (hasBinding && !hasStableTelegramBinding && !hasArchiveOnlyChatIdentity && !isValidBindingString(raw.chatId))
|
|
358
451
|
malformed();
|
|
359
452
|
}
|
|
360
453
|
for (const [sessionId, records] of Object.entries(state.retiredTopics ?? {})) {
|
|
@@ -380,7 +473,11 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
380
473
|
!validTimestamp(claim.createdAt) ||
|
|
381
474
|
!validOptionalString(claim.hostId) ||
|
|
382
475
|
!validOptionalString(claim.leaseOwner) ||
|
|
383
|
-
(claim.binding !== undefined && !validBinding(claim.binding))
|
|
476
|
+
(claim.binding !== undefined && !validBinding(claim.binding)) ||
|
|
477
|
+
(claim.telegramBinding !== undefined &&
|
|
478
|
+
(!isObject(claim.telegramBinding) ||
|
|
479
|
+
!isValidBindingString(claim.telegramBinding.chatId) ||
|
|
480
|
+
claim.telegramBinding.transport !== "telegram"))
|
|
384
481
|
)
|
|
385
482
|
malformed();
|
|
386
483
|
}
|
|
@@ -400,7 +497,7 @@ export function parseTopicRegistryState(value: unknown): TopicRegistryState | un
|
|
|
400
497
|
malformed();
|
|
401
498
|
}
|
|
402
499
|
for (const [sessionId, binding] of Object.entries(state.closedEndpoints ?? {}))
|
|
403
|
-
if (!isValidBindingString(sessionId) || !
|
|
500
|
+
if (!isValidBindingString(sessionId) || !validTelegramBinding(binding)) malformed();
|
|
404
501
|
return state;
|
|
405
502
|
}
|
|
406
503
|
|
|
@@ -457,7 +554,7 @@ export class TopicRegistry {
|
|
|
457
554
|
if (Array.isArray(records))
|
|
458
555
|
this.retiredTopics.set(
|
|
459
556
|
sessionId,
|
|
460
|
-
records.map(record => (
|
|
557
|
+
records.map(record => this.#normalizeLoadedRecord(record)),
|
|
461
558
|
);
|
|
462
559
|
for (const [sessionId, job] of Object.entries(state.archiveJobs ?? {})) {
|
|
463
560
|
const attempt =
|
|
@@ -513,6 +610,9 @@ export class TopicRegistry {
|
|
|
513
610
|
...(isValidBindingString(claim.hostId) ? { hostId: claim.hostId } : {}),
|
|
514
611
|
...(isValidBindingString(claim.leaseOwner) ? { leaseOwner: claim.leaseOwner } : {}),
|
|
515
612
|
...(claim.binding ? { binding: claim.binding } : {}),
|
|
613
|
+
...(isObjectLike(claim.telegramBinding) && isValidBindingString(claim.telegramBinding.chatId)
|
|
614
|
+
? { telegramBinding: { chatId: claim.telegramBinding.chatId, transport: "telegram" } }
|
|
615
|
+
: {}),
|
|
516
616
|
});
|
|
517
617
|
}
|
|
518
618
|
|
|
@@ -554,6 +654,9 @@ export class TopicRegistry {
|
|
|
554
654
|
typeof raw.sessionUuid === "string" && raw.sessionUuid.length > 0 ? raw.sessionUuid : randomUUID(),
|
|
555
655
|
identitySent: raw.identitySent === true,
|
|
556
656
|
createdAt: typeof raw.createdAt === "number" ? raw.createdAt : 0,
|
|
657
|
+
...(isObjectLike(raw.telegramBinding) && isValidBindingString(raw.telegramBinding.chatId)
|
|
658
|
+
? { telegramBinding: { chatId: raw.telegramBinding.chatId, transport: "telegram" as const } }
|
|
659
|
+
: {}),
|
|
557
660
|
...(typeof raw.name === "string" ? { name: raw.name } : {}),
|
|
558
661
|
...(typeof raw.orphanedAt === "number" && Number.isFinite(raw.orphanedAt) && raw.orphanedAt >= 0
|
|
559
662
|
? { orphanedAt: raw.orphanedAt }
|
|
@@ -564,6 +667,11 @@ export class TopicRegistry {
|
|
|
564
667
|
? { userNameUpdateId: raw.userNameUpdateId }
|
|
565
668
|
: {}),
|
|
566
669
|
...(typeof raw.identityKey === "string" ? { identityKey: raw.identityKey } : {}),
|
|
670
|
+
...(raw.archiveReason === "session_closed" ||
|
|
671
|
+
raw.archiveReason === "notification_subscription_removed" ||
|
|
672
|
+
raw.archiveReason === "create_compensation"
|
|
673
|
+
? { archiveReason: raw.archiveReason }
|
|
674
|
+
: {}),
|
|
567
675
|
...(typeof raw.creationLeaseEpoch === "number" &&
|
|
568
676
|
Number.isSafeInteger(raw.creationLeaseEpoch) &&
|
|
569
677
|
raw.creationLeaseEpoch >= 0
|
|
@@ -592,12 +700,6 @@ export class TopicRegistry {
|
|
|
592
700
|
}
|
|
593
701
|
: { authorityState: "active" as const }),
|
|
594
702
|
...(isValidBindingString(raw.chatId) ? { chatId: raw.chatId } : {}),
|
|
595
|
-
...(isValidBindingString(raw.endpointKey) ? { endpointKey: raw.endpointKey } : {}),
|
|
596
|
-
...(isValidBindingString(raw.endpointDigest) ? { endpointDigest: raw.endpointDigest } : {}),
|
|
597
|
-
...(isValidBindingGeneration(raw.endpointGeneration) ? { endpointGeneration: raw.endpointGeneration } : {}),
|
|
598
|
-
...(isValidBindingGeneration(raw.endpointIncarnation)
|
|
599
|
-
? { endpointIncarnation: raw.endpointIncarnation }
|
|
600
|
-
: {}),
|
|
601
703
|
...(isValidBindingString(raw.leaseOwner) ? { leaseOwner: raw.leaseOwner } : {}),
|
|
602
704
|
...(typeof raw.leaseHeartbeatAt === "number" && Number.isFinite(raw.leaseHeartbeatAt)
|
|
603
705
|
? { leaseHeartbeatAt: raw.leaseHeartbeatAt }
|
|
@@ -615,16 +717,37 @@ export class TopicRegistry {
|
|
|
615
717
|
: {}),
|
|
616
718
|
...(bindingMalformed ? { bindingMalformed: true as const } : {}),
|
|
617
719
|
};
|
|
618
|
-
this
|
|
720
|
+
const normalizedRecord = this.#normalizeLoadedRecord(record);
|
|
721
|
+
this.epochs.set(sessionId, Math.max(fenceEpoch, normalizedRecord.authorityEpoch ?? 0));
|
|
619
722
|
// Pre-generation-17 records have no endpoint authority. Retire them locally:
|
|
620
723
|
// their unknown remote topic must neither be rebound nor deleted cross-chat.
|
|
621
724
|
if (legacyUnbound) continue;
|
|
622
725
|
|
|
623
|
-
this.topics.set(sessionId,
|
|
726
|
+
this.topics.set(sessionId, normalizedRecord);
|
|
624
727
|
}
|
|
625
728
|
this.rebuildInboundRoutes();
|
|
626
729
|
}
|
|
627
730
|
|
|
731
|
+
/** Keep durable topic authority at stable logical session + Telegram binding. */
|
|
732
|
+
#normalizeLoadedRecord(raw: TopicRecord): TopicRecord {
|
|
733
|
+
const normalized = { ...raw };
|
|
734
|
+
const chatId =
|
|
735
|
+
isObjectLike(raw.telegramBinding) && isValidBindingString(raw.telegramBinding.chatId)
|
|
736
|
+
? raw.telegramBinding.chatId
|
|
737
|
+
: isValidBindingString(raw.chatId)
|
|
738
|
+
? raw.chatId
|
|
739
|
+
: undefined;
|
|
740
|
+
if (chatId !== undefined) {
|
|
741
|
+
normalized.chatId = chatId;
|
|
742
|
+
normalized.telegramBinding = { chatId, transport: "telegram" };
|
|
743
|
+
}
|
|
744
|
+
delete normalized.endpointKey;
|
|
745
|
+
delete normalized.endpointDigest;
|
|
746
|
+
delete normalized.endpointGeneration;
|
|
747
|
+
delete normalized.endpointIncarnation;
|
|
748
|
+
return normalized;
|
|
749
|
+
}
|
|
750
|
+
|
|
628
751
|
private rebuildInboundRoutes(): void {
|
|
629
752
|
this.byTopic.clear();
|
|
630
753
|
this.#ambiguousTopicIds.clear();
|
|
@@ -680,12 +803,7 @@ export class TopicRegistry {
|
|
|
680
803
|
if (topic.authorityEpoch !== claim.authorityEpoch) return false;
|
|
681
804
|
if (
|
|
682
805
|
claim.binding &&
|
|
683
|
-
(!hasCompleteBinding(topic) ||
|
|
684
|
-
!hasValidBinding(claim.binding) ||
|
|
685
|
-
topic.chatId !== claim.binding.chatId ||
|
|
686
|
-
topic.endpointKey !== claim.binding.endpointKey ||
|
|
687
|
-
topic.endpointDigest !== claim.binding.endpointDigest ||
|
|
688
|
-
topic.endpointGeneration !== claim.binding.endpointGeneration)
|
|
806
|
+
(!hasCompleteBinding(topic) || !hasValidBinding(claim.binding) || topic.chatId !== claim.binding.chatId)
|
|
689
807
|
)
|
|
690
808
|
return false;
|
|
691
809
|
this.topics.set(sessionId, { ...topic });
|
|
@@ -735,31 +853,29 @@ export class TopicRegistry {
|
|
|
735
853
|
*/
|
|
736
854
|
endpointAuthority(binding: TopicEndpointBinding, excludedTransientClaimant?: object): TopicEndpointAuthority {
|
|
737
855
|
if (!hasValidBinding(binding)) return { state: "ambiguous" };
|
|
738
|
-
const canClaim = (record: Pick<TopicRecord, "chatId"
|
|
856
|
+
const canClaim = (sessionId: string, record: Pick<TopicRecord, "chatId">): boolean =>
|
|
739
857
|
(record.chatId === undefined || record.chatId === binding.chatId) &&
|
|
740
|
-
(
|
|
741
|
-
|
|
742
|
-
const committed = [...this.topics].filter(([, record]) => canClaim(record));
|
|
858
|
+
(binding.logicalSessionId === undefined || sessionId === binding.logicalSessionId);
|
|
859
|
+
const committed = [...this.topics].filter(([sessionId, record]) => canClaim(sessionId, record));
|
|
743
860
|
const excludesClaimant = (sessionId: string): boolean =>
|
|
744
861
|
excludedTransientClaimant !== undefined &&
|
|
745
862
|
this.transientClaimants.get(sessionId) === excludedTransientClaimant;
|
|
746
|
-
const staged = [...this.staged].filter(
|
|
863
|
+
const staged = [...this.staged].filter(
|
|
864
|
+
([sessionId, record]) => canClaim(sessionId, record) && !excludesClaimant(sessionId),
|
|
865
|
+
);
|
|
747
866
|
const creating = [...this.creatingBindings].filter(
|
|
748
|
-
([sessionId, claim]) => canClaim(claim) && !excludesClaimant(sessionId),
|
|
867
|
+
([sessionId, claim]) => canClaim(sessionId, claim) && !excludesClaimant(sessionId),
|
|
749
868
|
);
|
|
750
869
|
const durableClaims = [...this.createClaims].filter(
|
|
751
|
-
([sessionId, claim]) =>
|
|
870
|
+
([sessionId, claim]) =>
|
|
871
|
+
claim.binding !== undefined && canClaim(sessionId, claim.binding) && !excludesClaimant(sessionId),
|
|
752
872
|
);
|
|
753
873
|
if (committed.length === 0 && staged.length === 0 && creating.length === 0 && durableClaims.length === 0)
|
|
754
874
|
return { state: "none" };
|
|
755
875
|
if (committed.length !== 1 || staged.length !== 0 || creating.length !== 0 || durableClaims.length !== 0)
|
|
756
876
|
return { state: "ambiguous" };
|
|
757
877
|
const [sessionId, record] = committed[0]!;
|
|
758
|
-
return record.chatId === binding.chatId &&
|
|
759
|
-
record.endpointKey === binding.endpointKey &&
|
|
760
|
-
record.endpointDigest === binding.endpointDigest &&
|
|
761
|
-
this.isActiveUnambiguous(sessionId) &&
|
|
762
|
-
!record.bindingMalformed
|
|
878
|
+
return record.chatId === binding.chatId && this.isActiveUnambiguous(sessionId) && !record.bindingMalformed
|
|
763
879
|
? { state: "unique", sessionId }
|
|
764
880
|
: { state: "ambiguous" };
|
|
765
881
|
}
|
|
@@ -773,13 +889,7 @@ export class TopicRegistry {
|
|
|
773
889
|
/** Whether this exact session owns the complete durable endpoint binding. */
|
|
774
890
|
matchesEndpoint(sessionId: string, binding: TopicEndpointBinding): boolean {
|
|
775
891
|
const record = this.topics.get(sessionId);
|
|
776
|
-
return (
|
|
777
|
-
this.isActiveUnambiguous(sessionId) &&
|
|
778
|
-
!record?.bindingMalformed &&
|
|
779
|
-
record?.chatId === binding.chatId &&
|
|
780
|
-
record.endpointKey === binding.endpointKey &&
|
|
781
|
-
record.endpointDigest === binding.endpointDigest
|
|
782
|
-
);
|
|
892
|
+
return this.isActiveUnambiguous(sessionId) && !record?.bindingMalformed && record?.chatId === binding.chatId;
|
|
783
893
|
}
|
|
784
894
|
|
|
785
895
|
/**
|
|
@@ -794,7 +904,7 @@ export class TopicRegistry {
|
|
|
794
904
|
record.bindingMalformed ||
|
|
795
905
|
!hasCompleteBinding(record) ||
|
|
796
906
|
!hasValidBinding(binding) ||
|
|
797
|
-
|
|
907
|
+
false
|
|
798
908
|
)
|
|
799
909
|
return false;
|
|
800
910
|
const history = this.retiredTopics.get(sessionId) ?? [];
|
|
@@ -816,8 +926,8 @@ export class TopicRegistry {
|
|
|
816
926
|
bindEndpoint(
|
|
817
927
|
sessionId: string,
|
|
818
928
|
binding: TopicEndpointBinding,
|
|
819
|
-
|
|
820
|
-
|
|
929
|
+
_activeEndpointKeys: ReadonlySet<string> = new Set(),
|
|
930
|
+
_allowEndpointRotation = false,
|
|
821
931
|
): "bound" | "unchanged" | "rejected" {
|
|
822
932
|
const record = this.topics.get(sessionId);
|
|
823
933
|
if (
|
|
@@ -833,33 +943,10 @@ export class TopicRegistry {
|
|
|
833
943
|
// authenticated its logical session id.
|
|
834
944
|
if (record.chatId === undefined || record.chatId !== binding.chatId) return "rejected";
|
|
835
945
|
|
|
836
|
-
const
|
|
837
|
-
record.endpointKey === binding.endpointKey && record.endpointDigest === binding.endpointDigest;
|
|
838
|
-
if (
|
|
839
|
-
sameEndpoint &&
|
|
840
|
-
record.endpointGeneration !== undefined &&
|
|
841
|
-
binding.endpointGeneration !== undefined &&
|
|
842
|
-
binding.endpointGeneration < record.endpointGeneration
|
|
843
|
-
)
|
|
844
|
-
return "rejected";
|
|
845
|
-
if (
|
|
846
|
-
!sameEndpoint &&
|
|
847
|
-
hasAnyBinding(record) &&
|
|
848
|
-
(!allowEndpointRotation || (record.endpointKey !== undefined && activeEndpointKeys.has(record.endpointKey)))
|
|
849
|
-
)
|
|
850
|
-
return "rejected";
|
|
851
|
-
|
|
852
|
-
const changed =
|
|
853
|
-
record.chatId !== binding.chatId ||
|
|
854
|
-
record.endpointKey !== binding.endpointKey ||
|
|
855
|
-
record.endpointDigest !== binding.endpointDigest ||
|
|
856
|
-
record.endpointGeneration !== binding.endpointGeneration;
|
|
946
|
+
const changed = false;
|
|
857
947
|
if (!changed && record.authorityState === "active") return "unchanged";
|
|
858
948
|
record.chatId = binding.chatId;
|
|
859
|
-
record.
|
|
860
|
-
record.endpointDigest = binding.endpointDigest;
|
|
861
|
-
record.endpointGeneration = binding.endpointGeneration;
|
|
862
|
-
if (!sameEndpoint) record.endpointIncarnation = (record.endpointIncarnation ?? 0) + 1;
|
|
949
|
+
record.telegramBinding = { chatId: binding.chatId, transport: "telegram" };
|
|
863
950
|
if (record.authorityState === "disconnect_grace") {
|
|
864
951
|
record.authorityState = "active";
|
|
865
952
|
delete record.orphanedAt;
|
|
@@ -873,27 +960,13 @@ export class TopicRegistry {
|
|
|
873
960
|
restoreEndpointBinding(
|
|
874
961
|
sessionId: string,
|
|
875
962
|
expected: TopicEndpointBinding,
|
|
876
|
-
previous: Pick<
|
|
877
|
-
TopicRecord,
|
|
878
|
-
"chatId" | "endpointKey" | "endpointDigest" | "endpointGeneration" | "endpointIncarnation"
|
|
879
|
-
>,
|
|
963
|
+
previous: Pick<TopicRecord, "chatId">,
|
|
880
964
|
): boolean {
|
|
881
965
|
const record = this.topics.get(sessionId);
|
|
882
|
-
if (
|
|
883
|
-
!record ||
|
|
884
|
-
record.chatId !== expected.chatId ||
|
|
885
|
-
record.endpointKey !== expected.endpointKey ||
|
|
886
|
-
record.endpointDigest !== expected.endpointDigest ||
|
|
887
|
-
record.endpointGeneration !== expected.endpointGeneration
|
|
888
|
-
)
|
|
889
|
-
return false;
|
|
966
|
+
if (!record || record.chatId !== expected.chatId) return false;
|
|
890
967
|
record.chatId = previous.chatId;
|
|
891
|
-
|
|
892
|
-
record.
|
|
893
|
-
if (previous.endpointGeneration === undefined) delete record.endpointGeneration;
|
|
894
|
-
else record.endpointGeneration = previous.endpointGeneration;
|
|
895
|
-
if (previous.endpointIncarnation === undefined) delete record.endpointIncarnation;
|
|
896
|
-
else record.endpointIncarnation = previous.endpointIncarnation;
|
|
968
|
+
if (previous.chatId === undefined) delete record.telegramBinding;
|
|
969
|
+
else record.telegramBinding = { chatId: previous.chatId, transport: "telegram" };
|
|
897
970
|
return true;
|
|
898
971
|
}
|
|
899
972
|
|
|
@@ -950,15 +1023,11 @@ export class TopicRegistry {
|
|
|
950
1023
|
authorityEpoch: revoked ? (this.epochs.get(sessionId) ?? 0) : epoch,
|
|
951
1024
|
creationLeaseEpoch: epoch,
|
|
952
1025
|
authorityState: revoked ? "archive_pending" : "active",
|
|
1026
|
+
...(revoked ? { archiveReason: "create_compensation" as const } : {}),
|
|
953
1027
|
...(binding
|
|
954
1028
|
? {
|
|
955
1029
|
chatId: binding.chatId,
|
|
956
|
-
|
|
957
|
-
endpointDigest: binding.endpointDigest,
|
|
958
|
-
endpointIncarnation: 0,
|
|
959
|
-
...(binding.endpointGeneration === undefined
|
|
960
|
-
? {}
|
|
961
|
-
: { endpointGeneration: binding.endpointGeneration }),
|
|
1030
|
+
telegramBinding: { chatId: binding.chatId, transport: "telegram" },
|
|
962
1031
|
}
|
|
963
1032
|
: {}),
|
|
964
1033
|
};
|
|
@@ -978,6 +1047,7 @@ export class TopicRegistry {
|
|
|
978
1047
|
if ((this.epochs.get(sessionId) ?? 0) !== epoch) {
|
|
979
1048
|
record.authorityEpoch = this.epochs.get(sessionId) ?? 0;
|
|
980
1049
|
record.authorityState = "archive_pending";
|
|
1050
|
+
record.archiveReason = "create_compensation";
|
|
981
1051
|
this.topics.set(sessionId, record);
|
|
982
1052
|
throw new Error("topic authority was revoked during creation");
|
|
983
1053
|
}
|
|
@@ -1236,6 +1306,7 @@ export class TopicRegistry {
|
|
|
1236
1306
|
...snapshot.record,
|
|
1237
1307
|
authorityEpoch: deleteEpoch,
|
|
1238
1308
|
authorityState: "archive_pending",
|
|
1309
|
+
archiveReason: snapshot.record.archiveReason ?? "create_compensation",
|
|
1239
1310
|
});
|
|
1240
1311
|
const restored = this.topics.get(snapshot.sessionId);
|
|
1241
1312
|
if (restored) delete restored.disconnectGraceExpiresAt;
|
|
@@ -1244,6 +1315,7 @@ export class TopicRegistry {
|
|
|
1244
1315
|
} else {
|
|
1245
1316
|
record.authorityEpoch = deleteEpoch;
|
|
1246
1317
|
record.authorityState = "archive_pending";
|
|
1318
|
+
record.archiveReason = snapshot.record?.archiveReason ?? record.archiveReason ?? "create_compensation";
|
|
1247
1319
|
delete record.disconnectGraceExpiresAt;
|
|
1248
1320
|
if (this.byTopic.get(record.topicId) === snapshot.sessionId) this.byTopic.delete(record.topicId);
|
|
1249
1321
|
}
|
|
@@ -1252,7 +1324,13 @@ export class TopicRegistry {
|
|
|
1252
1324
|
}
|
|
1253
1325
|
|
|
1254
1326
|
/** Fence new work before the remote archive starts, including an absent in-flight create. */
|
|
1255
|
-
beginArchive(
|
|
1327
|
+
beginArchive(
|
|
1328
|
+
sessionId: string,
|
|
1329
|
+
hostId: string | undefined,
|
|
1330
|
+
now: number,
|
|
1331
|
+
reason: TopicArchiveReason,
|
|
1332
|
+
): TopicRecord | undefined {
|
|
1333
|
+
if (!isTopicArchiveReason(reason)) return undefined;
|
|
1256
1334
|
const record = this.topics.get(sessionId);
|
|
1257
1335
|
if (
|
|
1258
1336
|
record?.topicOrigin === "user_created" ||
|
|
@@ -1278,6 +1356,7 @@ export class TopicRegistry {
|
|
|
1278
1356
|
if (!record) return undefined;
|
|
1279
1357
|
record.authorityEpoch = epoch;
|
|
1280
1358
|
record.authorityState = "archive_pending";
|
|
1359
|
+
record.archiveReason = reason;
|
|
1281
1360
|
delete record.disconnectGraceExpiresAt;
|
|
1282
1361
|
if (hostId) record.archiveHostId = hostId;
|
|
1283
1362
|
record.archiveLeaseEpoch = epoch;
|
|
@@ -1318,7 +1397,9 @@ export class TopicRegistry {
|
|
|
1318
1397
|
binding?: TopicEndpointBinding,
|
|
1319
1398
|
topicOrigin?: TopicRecord["topicOrigin"],
|
|
1320
1399
|
archiveChatId?: string,
|
|
1400
|
+
reason: TopicArchiveReason = "create_compensation",
|
|
1321
1401
|
): TopicRecord {
|
|
1402
|
+
if (!isTopicArchiveReason(reason)) throw new Error("archive reason is required");
|
|
1322
1403
|
const epoch = Math.max(this.epochs.get(sessionId) ?? 0, this.topics.get(sessionId)?.authorityEpoch ?? 0);
|
|
1323
1404
|
const record: TopicRecord = {
|
|
1324
1405
|
topicId,
|
|
@@ -1329,15 +1410,11 @@ export class TopicRegistry {
|
|
|
1329
1410
|
createdAt: now(),
|
|
1330
1411
|
authorityEpoch: epoch,
|
|
1331
1412
|
authorityState: "archive_pending",
|
|
1413
|
+
archiveReason: reason,
|
|
1332
1414
|
...(binding
|
|
1333
1415
|
? {
|
|
1334
1416
|
chatId: binding.chatId,
|
|
1335
|
-
|
|
1336
|
-
endpointDigest: binding.endpointDigest,
|
|
1337
|
-
endpointIncarnation: 0,
|
|
1338
|
-
...(binding.endpointGeneration === undefined
|
|
1339
|
-
? {}
|
|
1340
|
-
: { endpointGeneration: binding.endpointGeneration }),
|
|
1417
|
+
telegramBinding: { chatId: binding.chatId, transport: "telegram" },
|
|
1341
1418
|
}
|
|
1342
1419
|
: archiveChatId
|
|
1343
1420
|
? { chatId: archiveChatId }
|
|
@@ -1360,22 +1437,28 @@ export class TopicRegistry {
|
|
|
1360
1437
|
binding?: TopicEndpointBinding,
|
|
1361
1438
|
topicOrigin?: TopicRecord["topicOrigin"],
|
|
1362
1439
|
archiveChatId?: string,
|
|
1440
|
+
reason: TopicArchiveReason = "create_compensation",
|
|
1363
1441
|
): TopicRecord | undefined {
|
|
1364
1442
|
const record = this.topics.get(sessionId);
|
|
1365
|
-
const matchesBinding =
|
|
1366
|
-
record?.chatId === binding?.chatId &&
|
|
1367
|
-
record?.endpointKey === binding?.endpointKey &&
|
|
1368
|
-
record?.endpointDigest === binding?.endpointDigest &&
|
|
1369
|
-
record?.endpointGeneration === binding?.endpointGeneration;
|
|
1443
|
+
const matchesBinding = record?.chatId === binding?.chatId;
|
|
1370
1444
|
if (
|
|
1371
1445
|
record
|
|
1372
1446
|
? record.topicId !== topicId || record.creationLeaseEpoch !== creationLeaseEpoch || !matchesBinding
|
|
1373
1447
|
: (this.epochs.get(sessionId) ?? 0) !== creationLeaseEpoch
|
|
1374
1448
|
)
|
|
1375
1449
|
return undefined;
|
|
1376
|
-
const archiveFence = this.beginArchive(sessionId, hostId, now());
|
|
1450
|
+
const archiveFence = this.beginArchive(sessionId, hostId, now(), reason);
|
|
1377
1451
|
if (record && !archiveFence) return undefined;
|
|
1378
|
-
const fenced = this.fenceAcceptedCreate(
|
|
1452
|
+
const fenced = this.fenceAcceptedCreate(
|
|
1453
|
+
sessionId,
|
|
1454
|
+
topicId,
|
|
1455
|
+
now,
|
|
1456
|
+
name,
|
|
1457
|
+
binding,
|
|
1458
|
+
topicOrigin,
|
|
1459
|
+
archiveChatId,
|
|
1460
|
+
reason,
|
|
1461
|
+
);
|
|
1379
1462
|
fenced.creationLeaseEpoch = creationLeaseEpoch;
|
|
1380
1463
|
fenced.archiveHostId = hostId;
|
|
1381
1464
|
fenced.archiveLeaseEpoch = fenced.authorityEpoch;
|
|
@@ -1391,7 +1474,12 @@ export class TopicRegistry {
|
|
|
1391
1474
|
* Retain a topic record after a definite remote archive only while the exact
|
|
1392
1475
|
* dispatched authority epoch is still current.
|
|
1393
1476
|
*/
|
|
1394
|
-
settleArchive(
|
|
1477
|
+
settleArchive(
|
|
1478
|
+
sessionId: string,
|
|
1479
|
+
topicId: string,
|
|
1480
|
+
dispatchedAuthorityEpoch: number,
|
|
1481
|
+
reason: TopicArchiveReason,
|
|
1482
|
+
): boolean {
|
|
1395
1483
|
if (
|
|
1396
1484
|
!Number.isSafeInteger(dispatchedAuthorityEpoch) ||
|
|
1397
1485
|
dispatchedAuthorityEpoch < 0 ||
|
|
@@ -1401,6 +1489,8 @@ export class TopicRegistry {
|
|
|
1401
1489
|
const record = this.topics.get(sessionId);
|
|
1402
1490
|
if (
|
|
1403
1491
|
!record ||
|
|
1492
|
+
!isTopicArchiveReason(reason) ||
|
|
1493
|
+
record.archiveReason !== reason ||
|
|
1404
1494
|
record.topicId !== topicId ||
|
|
1405
1495
|
record.authorityState !== "archive_pending" ||
|
|
1406
1496
|
record.authorityEpoch !== dispatchedAuthorityEpoch ||
|
|
@@ -1417,7 +1507,7 @@ export class TopicRegistry {
|
|
|
1417
1507
|
/** Durable archive jobs that are eligible for a retry at `now`. */
|
|
1418
1508
|
archivePendingSessionIds(now = Date.now()): string[] {
|
|
1419
1509
|
return [...this.topics].flatMap(([sessionId, record]) => {
|
|
1420
|
-
if (record.authorityState === "archive_exhausted") {
|
|
1510
|
+
if (record.authorityState === "archive_exhausted" && record.topicOrigin === "daemon_created") {
|
|
1421
1511
|
const currentEpoch = Math.max(this.epochs.get(sessionId) ?? 0, record.authorityEpoch ?? 0);
|
|
1422
1512
|
if (currentEpoch < Number.MAX_SAFE_INTEGER) {
|
|
1423
1513
|
const epoch = Math.min(currentEpoch + 1, Number.MAX_SAFE_INTEGER - 1);
|
|
@@ -1427,7 +1517,8 @@ export class TopicRegistry {
|
|
|
1427
1517
|
delete record.disconnectGraceExpiresAt;
|
|
1428
1518
|
}
|
|
1429
1519
|
}
|
|
1430
|
-
return (record.
|
|
1520
|
+
return isTopicArchiveReason(record.archiveReason) &&
|
|
1521
|
+
(record.authorityState === "archive_pending" || record.authorityState === "archive_exhausted") &&
|
|
1431
1522
|
(this.archiveJobs.get(sessionId)?.nextAttemptAt ?? 0) <= now
|
|
1432
1523
|
? [sessionId]
|
|
1433
1524
|
: [];
|
|
@@ -1441,16 +1532,29 @@ export class TopicRegistry {
|
|
|
1441
1532
|
}
|
|
1442
1533
|
|
|
1443
1534
|
/** Persist an indefinitely discoverable retry after an ambiguous archive result. */
|
|
1444
|
-
scheduleArchiveRetry(
|
|
1535
|
+
scheduleArchiveRetry(
|
|
1536
|
+
sessionId: string,
|
|
1537
|
+
now: number,
|
|
1538
|
+
reason: TopicArchiveReason,
|
|
1539
|
+
diagnostic?: string,
|
|
1540
|
+
): ArchiveJob | undefined {
|
|
1445
1541
|
const record = this.topics.get(sessionId);
|
|
1446
|
-
if (
|
|
1542
|
+
if (
|
|
1543
|
+
!record ||
|
|
1544
|
+
!isTopicArchiveReason(reason) ||
|
|
1545
|
+
record.archiveReason !== reason ||
|
|
1546
|
+
(record.authorityState !== "archive_pending" && record.authorityState !== "archive_exhausted")
|
|
1547
|
+
)
|
|
1447
1548
|
return undefined;
|
|
1448
1549
|
const previous = this.archiveJobs.get(sessionId);
|
|
1449
1550
|
const firstAttemptAt = previous?.firstAttemptAt ?? now;
|
|
1450
1551
|
const attempt = (previous?.attempt ?? 0) + 1;
|
|
1451
1552
|
const exhausted = attempt > 8 || now - firstAttemptAt > 24 * 60 * 60 * 1000;
|
|
1452
1553
|
const effectiveAttempt = Math.min(attempt, 8);
|
|
1453
|
-
|
|
1554
|
+
// Post-budget retries stay discoverable but are paced: a permanently
|
|
1555
|
+
// ambiguous archive drained by a periodic sweep must not redispatch (and
|
|
1556
|
+
// bump the authority epoch) every sweep tick forever.
|
|
1557
|
+
const backoffMs = exhausted ? 15 * 60_000 : Math.min(60_000, 250 * 2 ** effectiveAttempt);
|
|
1454
1558
|
const job = {
|
|
1455
1559
|
sessionId,
|
|
1456
1560
|
topicId: record.topicId,
|
|
@@ -1469,14 +1573,43 @@ export class TopicRegistry {
|
|
|
1469
1573
|
|
|
1470
1574
|
/** Serialise active records plus unpublished staged creates for atomic commit. */
|
|
1471
1575
|
serialize(): TopicRegistryState {
|
|
1576
|
+
const stripSdkAuthority = (record: TopicRecord): TopicRecord => {
|
|
1577
|
+
const next = { ...record };
|
|
1578
|
+
if (next.chatId) next.telegramBinding = { chatId: next.chatId, transport: "telegram" };
|
|
1579
|
+
if (!next.chatId && next.telegramBinding) next.chatId = next.telegramBinding.chatId;
|
|
1580
|
+
delete next.endpointKey;
|
|
1581
|
+
delete next.endpointDigest;
|
|
1582
|
+
delete next.endpointGeneration;
|
|
1583
|
+
delete next.endpointIncarnation;
|
|
1584
|
+
return next;
|
|
1585
|
+
};
|
|
1586
|
+
const topics = Object.fromEntries(
|
|
1587
|
+
[...this.topics, ...this.staged].map(([id, record]) => [id, stripSdkAuthority(record)]),
|
|
1588
|
+
);
|
|
1589
|
+
const claims = Object.fromEntries(
|
|
1590
|
+
[...this.createClaims].map(([id, claim]) => [
|
|
1591
|
+
id,
|
|
1592
|
+
{
|
|
1593
|
+
...claim,
|
|
1594
|
+
...(claim.binding
|
|
1595
|
+
? { telegramBinding: { chatId: claim.binding.chatId, transport: "telegram" as const } }
|
|
1596
|
+
: {}),
|
|
1597
|
+
binding: undefined,
|
|
1598
|
+
},
|
|
1599
|
+
]),
|
|
1600
|
+
);
|
|
1601
|
+
const closedEndpoints: Record<string, TelegramTopicBinding> = {};
|
|
1472
1602
|
return {
|
|
1473
1603
|
version: 2,
|
|
1474
1604
|
registryGeneration: this.registryGeneration,
|
|
1475
|
-
topics
|
|
1605
|
+
topics,
|
|
1476
1606
|
fences: Object.fromEntries(this.epochs),
|
|
1607
|
+
closedEndpoints,
|
|
1477
1608
|
archiveJobs: Object.fromEntries(this.archiveJobs),
|
|
1478
|
-
createClaims:
|
|
1479
|
-
retiredTopics: Object.fromEntries(
|
|
1609
|
+
createClaims: claims,
|
|
1610
|
+
retiredTopics: Object.fromEntries(
|
|
1611
|
+
[...this.retiredTopics].map(([id, records]) => [id, records.map(stripSdkAuthority)]),
|
|
1612
|
+
),
|
|
1480
1613
|
};
|
|
1481
1614
|
}
|
|
1482
1615
|
}
|