@gajae-code/coding-agent 0.13.3 → 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 +184 -2
- 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 +9 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +70 -6
- 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 +1 -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/gajae-pet-widget.d.ts +8 -30
- 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 +11 -25
- 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/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 +40 -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 +9 -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 +20 -1
- package/src/config/model-profiles.ts +29 -19
- package/src/config/model-registry.ts +132 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +76 -6
- 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/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 +40 -32
- package/src/main.ts +32 -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/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/gajae-pet-widget.ts +539 -213
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +39 -40
- package/src/modes/components/settings-selector.ts +9 -0
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +16 -1
- 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 +177 -72
- 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/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 +2857 -582
- 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 +173 -12
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +730 -58
- 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 +98 -16
- 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 +27 -16
- 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/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,49 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Durable
|
|
3
|
-
* topic into a preallocated session" intents.
|
|
2
|
+
* Durable provider-local reservations for Telegram topic adoption.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* returns the user's existing topicId to `TopicRegistry.getOrCreateTopic`, which
|
|
10
|
-
* is the only authority that commits a topic binding. This keeps the intent out
|
|
11
|
-
* of routing/endpoint authority until commit, so a failed spawn or a late socket
|
|
12
|
-
* never deletes the user's topic and never strands a duplicate.
|
|
13
|
-
*
|
|
14
|
-
* Invariants (see `pending-approval.md`):
|
|
15
|
-
* - One file per intent, named `<intendedSessionId>.adoption-intent.json` under
|
|
16
|
-
* the daemon notifications dir. Sweep touches files only; never the Telegram API.
|
|
17
|
-
* - Only `{ intendedSessionId, topicId, chatId, target, createdAt, expiresAt }`
|
|
18
|
-
* is persisted. Bot/control tokens and endpoint digests are never stored.
|
|
19
|
-
* - Owner-only dir (0o700) and 0o600 files; atomic temp→fsync→chmod→rename→parent sync.
|
|
20
|
-
* - `tryClaim` is a synchronous, in-memory claim keyed by topicId; it excludes a
|
|
21
|
-
* second `intendedSessionId` from winning the same topic across awaits. It
|
|
22
|
-
* does not delete the sidecar; `releaseClaim` re-enables retry on commit failure.
|
|
4
|
+
* A reservation is keyed by the stable provider request identity, never by a
|
|
5
|
+
* preallocated Broker SessionId. Broker allocates the SessionId; the daemon
|
|
6
|
+
* CAS-binds that opaque value only after the lifecycle service returns it.
|
|
7
|
+
* This store owns only presentation mappings and pending-topic authorization.
|
|
23
8
|
*/
|
|
24
9
|
|
|
25
10
|
import { randomUUID } from "node:crypto";
|
|
26
11
|
import * as fs from "node:fs";
|
|
27
12
|
import * as path from "node:path";
|
|
13
|
+
import { logger } from "@gajae-code/utils";
|
|
28
14
|
import { daemonPaths } from "./daemon-paths";
|
|
29
15
|
import type { SessionCreateTarget } from "./index";
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
export const TELEGRAM_ADOPTION_INTENT_VERSION = 2;
|
|
18
|
+
const LEGACY_TELEGRAM_ADOPTION_INTENT_VERSION = 1 as const;
|
|
33
19
|
export type TelegramAdoptionTarget = Extract<SessionCreateTarget, { kind: "existing_path" }>;
|
|
34
|
-
/** Default intent TTL (configurable at write time). Plan starts at 10 minutes. */
|
|
35
20
|
export const DEFAULT_ADOPTION_INTENT_TTL_MS = 10 * 60 * 1000;
|
|
36
|
-
/** Per-intent filename prefix/suffix under the notifications dir. */
|
|
37
21
|
const INTENT_FILE_SUFFIX = ".adoption-intent.json";
|
|
38
22
|
const PENDING_TOPIC_FILE_SUFFIX = ".pending-topic.json";
|
|
23
|
+
const MIGRATION_DIAGNOSTIC_MAX_LENGTH = 256;
|
|
39
24
|
|
|
40
|
-
/**
|
|
41
|
-
* Minimal async filesystem surface the store needs. Structurally a subset of the
|
|
42
|
-
* daemon's `TelegramDaemonFs` / `ConversationStoreFs`, so the daemon can pass its
|
|
43
|
-
* own `fs` straight through and tests can inject a fake. `open`/`sync`/`close`
|
|
44
|
-
* mirror `ConversationStoreFileHandle` so the atomic fsync path reuses the same
|
|
45
|
-
* shape as the conversation store.
|
|
46
|
-
*/
|
|
47
25
|
export interface AdoptionIntentFs {
|
|
48
26
|
mkdir(directory: string, options: { recursive: true; mode: number }): Promise<unknown>;
|
|
49
27
|
chmod(target: string, mode: number): Promise<void>;
|
|
@@ -71,14 +49,15 @@ const nodeFs: AdoptionIntentFs = {
|
|
|
71
49
|
open: async (file, flags) => fs.promises.open(file, flags),
|
|
72
50
|
};
|
|
73
51
|
|
|
74
|
-
/**
|
|
52
|
+
/** Persisted before create; `sessionId` is added only by bindSession. */
|
|
75
53
|
export interface TelegramAdoptionIntent {
|
|
76
|
-
|
|
77
|
-
topicId: number;
|
|
78
|
-
chatId: string;
|
|
79
|
-
target: TelegramAdoptionTarget;
|
|
80
|
-
createdAt: number;
|
|
81
|
-
expiresAt: number;
|
|
54
|
+
readonly providerRequestKey: string;
|
|
55
|
+
readonly topicId: number;
|
|
56
|
+
readonly chatId: string;
|
|
57
|
+
readonly target: TelegramAdoptionTarget;
|
|
58
|
+
readonly createdAt: number;
|
|
59
|
+
readonly expiresAt: number;
|
|
60
|
+
readonly sessionId?: string;
|
|
82
61
|
}
|
|
83
62
|
|
|
84
63
|
interface PersistedIntent {
|
|
@@ -86,12 +65,27 @@ interface PersistedIntent {
|
|
|
86
65
|
intent: TelegramAdoptionIntent;
|
|
87
66
|
}
|
|
88
67
|
|
|
89
|
-
|
|
68
|
+
interface LegacyTelegramAdoptionIntent {
|
|
69
|
+
readonly intendedSessionId: string;
|
|
70
|
+
readonly topicId: number;
|
|
71
|
+
readonly chatId: string;
|
|
72
|
+
readonly target: TelegramAdoptionTarget;
|
|
73
|
+
readonly createdAt: number;
|
|
74
|
+
readonly expiresAt: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface LegacyPersistedIntent {
|
|
78
|
+
version: typeof LEGACY_TELEGRAM_ADOPTION_INTENT_VERSION;
|
|
79
|
+
intent: LegacyTelegramAdoptionIntent;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type PersistedIntentRecord = PersistedIntent | LegacyPersistedIntent;
|
|
83
|
+
|
|
90
84
|
export interface TelegramPendingTopic {
|
|
91
|
-
topicId: number;
|
|
92
|
-
chatId: string;
|
|
93
|
-
createdAt: number;
|
|
94
|
-
expiresAt: number;
|
|
85
|
+
readonly topicId: number;
|
|
86
|
+
readonly chatId: string;
|
|
87
|
+
readonly createdAt: number;
|
|
88
|
+
readonly expiresAt: number;
|
|
95
89
|
}
|
|
96
90
|
|
|
97
91
|
interface PersistedPendingTopic {
|
|
@@ -99,6 +93,13 @@ interface PersistedPendingTopic {
|
|
|
99
93
|
pendingTopic: TelegramPendingTopic;
|
|
100
94
|
}
|
|
101
95
|
|
|
96
|
+
interface LegacyPersistedPendingTopic {
|
|
97
|
+
version: typeof LEGACY_TELEGRAM_ADOPTION_INTENT_VERSION;
|
|
98
|
+
pendingTopic: TelegramPendingTopic;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type PersistedPendingTopicRecord = PersistedPendingTopic | LegacyPersistedPendingTopic;
|
|
102
|
+
|
|
102
103
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
103
104
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
105
|
}
|
|
@@ -107,11 +108,28 @@ function isSessionCreateTarget(value: unknown): value is TelegramAdoptionTarget
|
|
|
107
108
|
return isRecord(value) && value.kind === "existing_path" && typeof value.path === "string";
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
function isPersistedIntent(value: unknown): value is
|
|
111
|
-
if (!isRecord(value)) return false;
|
|
112
|
-
if (value.version
|
|
111
|
+
function isPersistedIntent(value: unknown): value is PersistedIntentRecord {
|
|
112
|
+
if (!isRecord(value) || !isRecord(value.intent)) return false;
|
|
113
|
+
if (value.version === TELEGRAM_ADOPTION_INTENT_VERSION) {
|
|
114
|
+
const intent = value.intent;
|
|
115
|
+
return (
|
|
116
|
+
typeof intent.providerRequestKey === "string" &&
|
|
117
|
+
intent.providerRequestKey.length > 0 &&
|
|
118
|
+
typeof intent.topicId === "number" &&
|
|
119
|
+
Number.isSafeInteger(intent.topicId) &&
|
|
120
|
+
intent.topicId > 0 &&
|
|
121
|
+
typeof intent.chatId === "string" &&
|
|
122
|
+
typeof intent.createdAt === "number" &&
|
|
123
|
+
Number.isFinite(intent.createdAt) &&
|
|
124
|
+
typeof intent.expiresAt === "number" &&
|
|
125
|
+
Number.isFinite(intent.expiresAt) &&
|
|
126
|
+
intent.expiresAt > intent.createdAt &&
|
|
127
|
+
(intent.sessionId === undefined || (typeof intent.sessionId === "string" && intent.sessionId.length > 0)) &&
|
|
128
|
+
isSessionCreateTarget(intent.target)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (value.version !== LEGACY_TELEGRAM_ADOPTION_INTENT_VERSION) return false;
|
|
113
132
|
const intent = value.intent;
|
|
114
|
-
if (!isRecord(intent)) return false;
|
|
115
133
|
return (
|
|
116
134
|
typeof intent.intendedSessionId === "string" &&
|
|
117
135
|
intent.intendedSessionId.length > 0 &&
|
|
@@ -128,11 +146,28 @@ function isPersistedIntent(value: unknown): value is PersistedIntent {
|
|
|
128
146
|
);
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
function
|
|
132
|
-
if (!
|
|
149
|
+
function normalizePersistedIntent(value: unknown): { intent: TelegramAdoptionIntent; legacy: boolean } | undefined {
|
|
150
|
+
if (!isPersistedIntent(value)) return undefined;
|
|
151
|
+
if (value.version === TELEGRAM_ADOPTION_INTENT_VERSION) return { intent: value.intent, legacy: false };
|
|
152
|
+
return {
|
|
153
|
+
legacy: true,
|
|
154
|
+
intent: {
|
|
155
|
+
providerRequestKey: `legacy:v1:${value.intent.intendedSessionId}`,
|
|
156
|
+
topicId: value.intent.topicId,
|
|
157
|
+
chatId: value.intent.chatId,
|
|
158
|
+
target: value.intent.target,
|
|
159
|
+
createdAt: value.intent.createdAt,
|
|
160
|
+
expiresAt: value.intent.expiresAt,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isPersistedPendingTopic(value: unknown): value is PersistedPendingTopicRecord {
|
|
166
|
+
if (!isRecord(value) || !isRecord(value.pendingTopic)) return false;
|
|
167
|
+
if (value.version !== TELEGRAM_ADOPTION_INTENT_VERSION && value.version !== LEGACY_TELEGRAM_ADOPTION_INTENT_VERSION)
|
|
168
|
+
return false;
|
|
133
169
|
const pending = value.pendingTopic;
|
|
134
170
|
return (
|
|
135
|
-
isRecord(pending) &&
|
|
136
171
|
typeof pending.topicId === "number" &&
|
|
137
172
|
Number.isSafeInteger(pending.topicId) &&
|
|
138
173
|
pending.topicId > 0 &&
|
|
@@ -145,19 +180,29 @@ function isPersistedPendingTopic(value: unknown): value is PersistedPendingTopic
|
|
|
145
180
|
);
|
|
146
181
|
}
|
|
147
182
|
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
return
|
|
183
|
+
/** Sidecar names are opaque, deterministic encodings of provider request keys. */
|
|
184
|
+
function encodeProviderRequestKey(providerRequestKey: string): string {
|
|
185
|
+
return Buffer.from(providerRequestKey, "utf8").toString("base64url");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function decodeProviderRequestKey(value: string): string | undefined {
|
|
189
|
+
try {
|
|
190
|
+
return Buffer.from(value, "base64url").toString("utf8");
|
|
191
|
+
} catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function adoptionIntentFilePath(agentDir: string, providerRequestKey: string): string {
|
|
197
|
+
return path.join(daemonPaths(agentDir).dir, `${encodeProviderRequestKey(providerRequestKey)}${INTENT_FILE_SUFFIX}`);
|
|
151
198
|
}
|
|
152
199
|
|
|
153
|
-
/** Compose the sidecar path for a user-created topic awaiting a folder choice. */
|
|
154
200
|
export function pendingTopicFilePath(agentDir: string, topicId: number): string {
|
|
155
201
|
return path.join(daemonPaths(agentDir).dir, `${topicId}${PENDING_TOPIC_FILE_SUFFIX}`);
|
|
156
202
|
}
|
|
157
203
|
|
|
158
|
-
/** Build a fresh intent with `createdAt`/`expiresAt` derived from `now`/`ttlMs`. */
|
|
159
204
|
export function buildAdoptionIntent(input: {
|
|
160
|
-
|
|
205
|
+
providerRequestKey: string;
|
|
161
206
|
topicId: number;
|
|
162
207
|
chatId: string;
|
|
163
208
|
target: TelegramAdoptionTarget;
|
|
@@ -165,45 +210,31 @@ export function buildAdoptionIntent(input: {
|
|
|
165
210
|
ttlMs?: number;
|
|
166
211
|
}): TelegramAdoptionIntent {
|
|
167
212
|
const createdAt = input.now ?? Date.now();
|
|
168
|
-
const ttl = input.ttlMs ?? DEFAULT_ADOPTION_INTENT_TTL_MS;
|
|
169
213
|
return {
|
|
170
|
-
|
|
214
|
+
providerRequestKey: input.providerRequestKey,
|
|
171
215
|
topicId: input.topicId,
|
|
172
216
|
chatId: input.chatId,
|
|
173
217
|
target: input.target,
|
|
174
218
|
createdAt,
|
|
175
|
-
expiresAt: createdAt +
|
|
219
|
+
expiresAt: createdAt + (input.ttlMs ?? DEFAULT_ADOPTION_INTENT_TTL_MS),
|
|
176
220
|
};
|
|
177
221
|
}
|
|
178
222
|
|
|
179
|
-
/** Test-only: filename suffix used to recognize intent sidecars in a directory listing. */
|
|
180
223
|
export const ADOPTION_INTENT_FILENAME_SUFFIX = INTENT_FILE_SUFFIX;
|
|
181
224
|
export const PENDING_TOPIC_FILENAME_SUFFIX = PENDING_TOPIC_FILE_SUFFIX;
|
|
182
225
|
|
|
183
|
-
/**
|
|
184
|
-
* Durable, non-routable adoption-intent store. Owns durable sidecar files and
|
|
185
|
-
* the synchronous in-memory topicId claim table. The daemon owns Telegram and
|
|
186
|
-
* lifecycle wiring; this store never calls the Telegram API.
|
|
187
|
-
*/
|
|
188
226
|
export class TelegramAdoptionIntentStore {
|
|
189
227
|
readonly #agentDir: string;
|
|
190
228
|
readonly #dir: string;
|
|
191
229
|
readonly #fsImpl: AdoptionIntentFs;
|
|
192
230
|
readonly #now: () => number;
|
|
193
231
|
readonly #platform: NodeJS.Platform;
|
|
194
|
-
/** intendedSessionId -> intent, rehydrated from disk. */
|
|
195
232
|
readonly #intents = new Map<string, TelegramAdoptionIntent>();
|
|
196
|
-
/** topicId -> intendedSessionId currently holding the synchronous claim. */
|
|
197
233
|
readonly #claims = new Map<number, string>();
|
|
198
|
-
/** topicId -> durable authorization observed from forum_topic_created. */
|
|
199
234
|
readonly #pendingTopics = new Map<number, TelegramPendingTopic>();
|
|
235
|
+
readonly #bindingKeys = new Set<string>();
|
|
200
236
|
|
|
201
|
-
constructor(input: {
|
|
202
|
-
agentDir: string;
|
|
203
|
-
fs?: AdoptionIntentFs;
|
|
204
|
-
now?: () => number;
|
|
205
|
-
platform?: NodeJS.Platform;
|
|
206
|
-
}) {
|
|
237
|
+
constructor(input: { agentDir: string; fs?: AdoptionIntentFs; now?: () => number; platform?: NodeJS.Platform }) {
|
|
207
238
|
this.#agentDir = input.agentDir;
|
|
208
239
|
this.#dir = daemonPaths(input.agentDir).dir;
|
|
209
240
|
this.#fsImpl = input.fs ?? nodeFs;
|
|
@@ -211,113 +242,117 @@ export class TelegramAdoptionIntentStore {
|
|
|
211
242
|
this.#platform = input.platform ?? process.platform;
|
|
212
243
|
}
|
|
213
244
|
|
|
214
|
-
/** Directory holding the sidecar files. */
|
|
215
245
|
get directory(): string {
|
|
216
246
|
return this.#dir;
|
|
217
247
|
}
|
|
218
248
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
249
|
+
byProviderRequestKey(providerRequestKey: string): TelegramAdoptionIntent | undefined {
|
|
250
|
+
const intent = this.#intents.get(providerRequestKey);
|
|
251
|
+
return intent && this.#now() < intent.expiresAt ? intent : undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Only a post-create CAS binding makes a reservation visible by SessionId. */
|
|
255
|
+
bySession(sessionId: string): TelegramAdoptionIntent | undefined {
|
|
256
|
+
for (const intent of this.#intents.values()) {
|
|
257
|
+
if (intent.sessionId === sessionId && this.#now() < intent.expiresAt) return intent;
|
|
258
|
+
}
|
|
259
|
+
return undefined;
|
|
224
260
|
}
|
|
225
261
|
|
|
226
|
-
/** In-memory lookup of the (single) non-expired intent for a topicId. */
|
|
227
262
|
byTopic(topicId: number): TelegramAdoptionIntent | undefined {
|
|
228
263
|
for (const intent of this.#intents.values()) {
|
|
229
|
-
if (intent.topicId
|
|
230
|
-
if (this.#now() >= intent.expiresAt) continue;
|
|
231
|
-
return intent;
|
|
264
|
+
if (intent.topicId === topicId && this.#now() < intent.expiresAt) return intent;
|
|
232
265
|
}
|
|
233
266
|
return undefined;
|
|
234
267
|
}
|
|
235
268
|
|
|
236
|
-
/** `true` if a non-expired intent already references `topicId`. */
|
|
237
269
|
hasNonExpiredTopic(topicId: number): boolean {
|
|
238
270
|
return this.byTopic(topicId) !== undefined;
|
|
239
271
|
}
|
|
240
272
|
|
|
241
|
-
/** Durable authorization for an observed user-created topic, if still live. */
|
|
242
273
|
pendingTopic(topicId: number): TelegramPendingTopic | undefined {
|
|
243
274
|
const pending = this.#pendingTopics.get(topicId);
|
|
244
|
-
return pending
|
|
275
|
+
return pending && this.#now() < pending.expiresAt ? pending : undefined;
|
|
245
276
|
}
|
|
246
277
|
|
|
247
278
|
hasPendingTopic(topicId: number, chatId: string): boolean {
|
|
248
279
|
return this.pendingTopic(topicId)?.chatId === chatId;
|
|
249
280
|
}
|
|
250
281
|
|
|
251
|
-
/**
|
|
252
|
-
|
|
253
|
-
* *different* non-expired intendedSessionId already holds the claim (or has a
|
|
254
|
-
* non-expired intent for the topic). Same intendedSessionId re-claiming its
|
|
255
|
-
* own topic is idempotent and succeeds. The claim is in-memory only; it never
|
|
256
|
-
* deletes the sidecar, so a commit failure can `releaseClaim` and retry.
|
|
257
|
-
*/
|
|
258
|
-
tryClaim(topicId: number, intendedSessionId: string): boolean {
|
|
282
|
+
/** Synchronous fail-closed topic claim keyed by provider request identity. */
|
|
283
|
+
tryClaim(topicId: number, providerRequestKey: string): boolean {
|
|
259
284
|
const holder = this.#claims.get(topicId);
|
|
260
|
-
if (holder !== undefined) return holder ===
|
|
261
|
-
// No live claim yet: refuse if a *different* non-expired intent already
|
|
262
|
-
// targets this topic (race between two pre-spawn intents on one topic).
|
|
285
|
+
if (holder !== undefined) return holder === providerRequestKey;
|
|
263
286
|
const existing = this.byTopic(topicId);
|
|
264
|
-
if (existing !== undefined && existing.
|
|
265
|
-
this.#claims.set(topicId,
|
|
287
|
+
if (existing !== undefined && existing.providerRequestKey !== providerRequestKey) return false;
|
|
288
|
+
this.#claims.set(topicId, providerRequestKey);
|
|
266
289
|
return true;
|
|
267
290
|
}
|
|
268
291
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (this.#claims.get(topicId) === intendedSessionId) this.#claims.delete(topicId);
|
|
292
|
+
releaseClaim(topicId: number, providerRequestKey: string): void {
|
|
293
|
+
if (this.#claims.get(topicId) === providerRequestKey) this.#claims.delete(topicId);
|
|
272
294
|
}
|
|
273
295
|
|
|
274
|
-
/**
|
|
275
|
-
* Durable write of one intent: temp→write→fsync→chmod(0600)→atomic rename→
|
|
276
|
-
* chmod(final, 0600)→parent-dir fsync, then track it in memory. The dir is
|
|
277
|
-
* created owner-only (0700). chmod-ing the final path after the rename makes
|
|
278
|
-
* the 0600 contract durable regardless of whether the platform's rename
|
|
279
|
-
* preserves the source mode (POSIX does; some test/network filesystems and
|
|
280
|
-
* the in-memory test double model rename as copy+delete and would otherwise
|
|
281
|
-
* drop it). The rename is still the atomic commit point; the final chmod only
|
|
282
|
-
* tightens permissions on the already-durable file. The in-memory map is only
|
|
283
|
-
* mutated after the atomic persist succeeds. Rejects (does not swallow) so the
|
|
284
|
-
* daemon can decide fail-closed behavior.
|
|
285
|
-
*/
|
|
286
296
|
async put(intent: TelegramAdoptionIntent): Promise<void> {
|
|
297
|
+
const current =
|
|
298
|
+
this.#intents.get(intent.providerRequestKey) ?? (await this.readIntent(intent.providerRequestKey));
|
|
299
|
+
if (
|
|
300
|
+
current &&
|
|
301
|
+
(current.topicId !== intent.topicId ||
|
|
302
|
+
current.chatId !== intent.chatId ||
|
|
303
|
+
current.target.path !== intent.target.path)
|
|
304
|
+
)
|
|
305
|
+
throw new Error("provider request key is already reserved for a different topic or target");
|
|
287
306
|
const stored: TelegramAdoptionIntent = {
|
|
288
|
-
|
|
307
|
+
providerRequestKey: intent.providerRequestKey,
|
|
289
308
|
topicId: intent.topicId,
|
|
290
309
|
chatId: intent.chatId,
|
|
291
310
|
target: { kind: "existing_path", path: intent.target.path },
|
|
292
311
|
createdAt: intent.createdAt,
|
|
293
312
|
expiresAt: intent.expiresAt,
|
|
313
|
+
...((intent.sessionId ?? current?.sessionId) ? { sessionId: intent.sessionId ?? current?.sessionId } : {}),
|
|
294
314
|
};
|
|
295
|
-
const file = adoptionIntentFilePath(this.#agentDir, stored.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
315
|
+
const file = adoptionIntentFilePath(this.#agentDir, stored.providerRequestKey);
|
|
316
|
+
await this.#writeSidecar(file, { version: TELEGRAM_ADOPTION_INTENT_VERSION, intent: stored });
|
|
317
|
+
this.#intents.set(stored.providerRequestKey, stored);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** CAS-bind the Broker-returned opaque SessionId exactly once. */
|
|
321
|
+
async bindSession(providerRequestKey: string, sessionId: string): Promise<boolean> {
|
|
322
|
+
if (!sessionId || this.#bindingKeys.has(providerRequestKey)) return false;
|
|
323
|
+
const current = this.byProviderRequestKey(providerRequestKey);
|
|
324
|
+
if (!current) return false;
|
|
325
|
+
if (current.sessionId !== undefined) return current.sessionId === sessionId;
|
|
326
|
+
this.#bindingKeys.add(providerRequestKey);
|
|
327
|
+
try {
|
|
328
|
+
const onDisk = await this.readIntent(providerRequestKey);
|
|
329
|
+
if (!onDisk || (onDisk.sessionId !== undefined && onDisk.sessionId !== sessionId)) return false;
|
|
330
|
+
const bound: TelegramAdoptionIntent = { ...current, sessionId };
|
|
331
|
+
await this.#writeSidecar(adoptionIntentFilePath(this.#agentDir, providerRequestKey), {
|
|
332
|
+
version: TELEGRAM_ADOPTION_INTENT_VERSION,
|
|
333
|
+
intent: bound,
|
|
334
|
+
});
|
|
335
|
+
this.#intents.set(providerRequestKey, bound);
|
|
336
|
+
return true;
|
|
337
|
+
} finally {
|
|
338
|
+
this.#bindingKeys.delete(providerRequestKey);
|
|
339
|
+
}
|
|
300
340
|
}
|
|
301
341
|
|
|
302
|
-
/** Persist authorization for a topic observed through forum_topic_created. */
|
|
303
342
|
async putPendingTopic(pendingTopic: TelegramPendingTopic): Promise<void> {
|
|
304
|
-
const stored
|
|
305
|
-
|
|
306
|
-
chatId: pendingTopic.chatId,
|
|
307
|
-
createdAt: pendingTopic.createdAt,
|
|
308
|
-
expiresAt: pendingTopic.expiresAt,
|
|
309
|
-
};
|
|
310
|
-
const file = pendingTopicFilePath(this.#agentDir, stored.topicId);
|
|
311
|
-
const payload: PersistedPendingTopic = {
|
|
343
|
+
const stored = { ...pendingTopic };
|
|
344
|
+
await this.#writeSidecar(pendingTopicFilePath(this.#agentDir, stored.topicId), {
|
|
312
345
|
version: TELEGRAM_ADOPTION_INTENT_VERSION,
|
|
313
346
|
pendingTopic: stored,
|
|
314
|
-
};
|
|
315
|
-
if (!isPersistedPendingTopic(payload)) throw new Error("invalid Telegram pending topic");
|
|
316
|
-
await this.#writeSidecar(file, payload);
|
|
347
|
+
});
|
|
317
348
|
this.#pendingTopics.set(stored.topicId, stored);
|
|
318
349
|
}
|
|
319
350
|
|
|
320
|
-
async #writeSidecar(
|
|
351
|
+
async #writeSidecar(
|
|
352
|
+
file: string,
|
|
353
|
+
payload: PersistedIntent | PersistedPendingTopic,
|
|
354
|
+
retainRenamedFileOnFailure = false,
|
|
355
|
+
): Promise<void> {
|
|
321
356
|
await this.#fsImpl.mkdir(this.#dir, { recursive: true, mode: 0o700 });
|
|
322
357
|
await this.#fsImpl.chmod(this.#dir, 0o700);
|
|
323
358
|
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
@@ -336,6 +371,7 @@ export class TelegramAdoptionIntentStore {
|
|
|
336
371
|
await this.#fsImpl.chmod(file, 0o600);
|
|
337
372
|
await this.#syncParentDirectory();
|
|
338
373
|
} catch (error) {
|
|
374
|
+
if (renamed && retainRenamedFileOnFailure) throw new SidecarDurabilityUncertainError(error);
|
|
339
375
|
const cleanupTarget = renamed ? file : temporary;
|
|
340
376
|
let cleanupError: unknown;
|
|
341
377
|
try {
|
|
@@ -349,45 +385,27 @@ export class TelegramAdoptionIntentStore {
|
|
|
349
385
|
}
|
|
350
386
|
}
|
|
351
387
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
async readIntent(intendedSessionId: string): Promise<TelegramAdoptionIntent | undefined> {
|
|
358
|
-
const file = adoptionIntentFilePath(this.#agentDir, intendedSessionId);
|
|
359
|
-
let raw: string;
|
|
360
|
-
try {
|
|
361
|
-
raw = await this.#fsImpl.readFile(file, "utf8");
|
|
362
|
-
} catch (error) {
|
|
363
|
-
if (isMissing(error)) return undefined;
|
|
364
|
-
throw error;
|
|
365
|
-
}
|
|
366
|
-
let parsed: unknown;
|
|
367
|
-
try {
|
|
368
|
-
parsed = JSON.parse(raw);
|
|
369
|
-
} catch {
|
|
388
|
+
async readIntent(providerRequestKey: string): Promise<TelegramAdoptionIntent | undefined> {
|
|
389
|
+
const file = adoptionIntentFilePath(this.#agentDir, providerRequestKey);
|
|
390
|
+
const record = await this.#readSidecarRecord(file);
|
|
391
|
+
const parsed = record?.intent;
|
|
392
|
+
if (!record || !parsed || parsed.providerRequestKey !== providerRequestKey || this.#now() >= parsed.expiresAt)
|
|
370
393
|
return undefined;
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
this.#intents.set(intendedSessionId, parsed.intent);
|
|
375
|
-
return parsed.intent;
|
|
394
|
+
this.#intents.set(providerRequestKey, parsed);
|
|
395
|
+
if (record.legacy) await this.#migrateIntentSidecar(file, parsed);
|
|
396
|
+
return parsed;
|
|
376
397
|
}
|
|
377
398
|
|
|
378
399
|
async readPendingTopic(topicId: number): Promise<TelegramPendingTopic | undefined> {
|
|
379
400
|
const file = pendingTopicFilePath(this.#agentDir, topicId);
|
|
380
|
-
const
|
|
381
|
-
|
|
401
|
+
const record = await this.#readPendingSidecarRecord(file);
|
|
402
|
+
const pending = record?.pendingTopic;
|
|
403
|
+
if (!record || !pending || pending.topicId !== topicId || this.#now() >= pending.expiresAt) return undefined;
|
|
382
404
|
this.#pendingTopics.set(topicId, pending);
|
|
405
|
+
if (record.legacy) await this.#migratePendingTopicSidecar(file, pending);
|
|
383
406
|
return pending;
|
|
384
407
|
}
|
|
385
408
|
|
|
386
|
-
/**
|
|
387
|
-
* Rehydrate all non-expired intents from the notifications dir. Files are the
|
|
388
|
-
* only source: missing/corrupt/expired entries are skipped (never thrown).
|
|
389
|
-
* Returns the count of live intents loaded.
|
|
390
|
-
*/
|
|
391
409
|
async rehydrate(): Promise<number> {
|
|
392
410
|
let names: readonly string[];
|
|
393
411
|
try {
|
|
@@ -398,55 +416,52 @@ export class TelegramAdoptionIntentStore {
|
|
|
398
416
|
}
|
|
399
417
|
let loaded = 0;
|
|
400
418
|
for (const name of names) {
|
|
419
|
+
const file = path.join(this.#dir, name);
|
|
401
420
|
if (name.endsWith(INTENT_FILE_SUFFIX)) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
421
|
+
const encoded = name.slice(0, -INTENT_FILE_SUFFIX.length);
|
|
422
|
+
const key = decodeProviderRequestKey(encoded);
|
|
423
|
+
const record = await this.#readSidecarRecord(file);
|
|
424
|
+
const intent = record?.intent;
|
|
425
|
+
if (!intent || this.#now() >= intent.expiresAt) continue;
|
|
426
|
+
if (!record.legacy && (!key || intent.providerRequestKey !== key)) continue;
|
|
427
|
+
this.#intents.set(intent.providerRequestKey, intent);
|
|
428
|
+
if (record.legacy) await this.#migrateIntentSidecar(file, intent);
|
|
429
|
+
loaded++;
|
|
406
430
|
continue;
|
|
407
431
|
}
|
|
408
432
|
if (!name.endsWith(PENDING_TOPIC_FILE_SUFFIX)) continue;
|
|
409
|
-
const
|
|
410
|
-
const topicId = Number(rawTopicId);
|
|
433
|
+
const topicId = Number(name.slice(0, -PENDING_TOPIC_FILE_SUFFIX.length));
|
|
411
434
|
if (!Number.isSafeInteger(topicId) || topicId <= 0) continue;
|
|
412
|
-
const
|
|
413
|
-
|
|
435
|
+
const record = await this.#readPendingSidecarRecord(file);
|
|
436
|
+
const pending = record?.pendingTopic;
|
|
437
|
+
if (!pending || pending.topicId !== topicId || this.#now() >= pending.expiresAt) continue;
|
|
438
|
+
this.#pendingTopics.set(topicId, pending);
|
|
439
|
+
if (record.legacy) await this.#migratePendingTopicSidecar(file, pending);
|
|
440
|
+
loaded++;
|
|
414
441
|
}
|
|
415
442
|
return loaded;
|
|
416
443
|
}
|
|
417
444
|
|
|
418
|
-
|
|
419
|
-
* Remove a single intent sidecar and drop its in-memory entry. Safe to call
|
|
420
|
-
* after a successful registry commit or a definite spawn failure. Missing
|
|
421
|
-
* files are not an error. Never calls the Telegram API.
|
|
422
|
-
*/
|
|
423
|
-
async remove(intendedSessionId: string): Promise<void> {
|
|
424
|
-
const file = adoptionIntentFilePath(this.#agentDir, intendedSessionId);
|
|
445
|
+
async remove(providerRequestKey: string): Promise<void> {
|
|
425
446
|
try {
|
|
426
|
-
await this.#fsImpl.unlink(
|
|
447
|
+
await this.#fsImpl.unlink(adoptionIntentFilePath(this.#agentDir, providerRequestKey));
|
|
427
448
|
} catch (error) {
|
|
428
449
|
if (!isMissing(error)) throw error;
|
|
429
450
|
}
|
|
430
|
-
const intent = this.#intents.get(
|
|
431
|
-
this.#intents.delete(
|
|
432
|
-
if (intent) this.releaseClaim(intent.topicId,
|
|
451
|
+
const intent = this.#intents.get(providerRequestKey);
|
|
452
|
+
this.#intents.delete(providerRequestKey);
|
|
453
|
+
if (intent) this.releaseClaim(intent.topicId, providerRequestKey);
|
|
433
454
|
}
|
|
434
455
|
|
|
435
|
-
/** Remove a pending-topic authorization after an adoption intent is durable. */
|
|
436
456
|
async removePendingTopic(topicId: number): Promise<void> {
|
|
437
|
-
const file = pendingTopicFilePath(this.#agentDir, topicId);
|
|
438
457
|
try {
|
|
439
|
-
await this.#fsImpl.unlink(
|
|
458
|
+
await this.#fsImpl.unlink(pendingTopicFilePath(this.#agentDir, topicId));
|
|
440
459
|
} catch (error) {
|
|
441
460
|
if (!isMissing(error)) throw error;
|
|
442
461
|
}
|
|
443
462
|
this.#pendingTopics.delete(topicId);
|
|
444
463
|
}
|
|
445
464
|
|
|
446
|
-
/**
|
|
447
|
-
* Sweep expired intent sidecars from disk. Touches files only. Returns the
|
|
448
|
-
* count of expired sidecars removed. Safe to run at startup and periodically.
|
|
449
|
-
*/
|
|
450
465
|
async sweepExpired(): Promise<number> {
|
|
451
466
|
let names: readonly string[];
|
|
452
467
|
try {
|
|
@@ -460,10 +475,9 @@ export class TelegramAdoptionIntentStore {
|
|
|
460
475
|
for (const name of names) {
|
|
461
476
|
const file = path.join(this.#dir, name);
|
|
462
477
|
if (name.endsWith(INTENT_FILE_SUFFIX)) {
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (intent && intent.intendedSessionId === intendedSessionId && now < intent.expiresAt) continue;
|
|
478
|
+
const record = await this.#readSidecarRecord(file);
|
|
479
|
+
const intent = record?.intent;
|
|
480
|
+
if (intent && now < intent.expiresAt) continue;
|
|
467
481
|
try {
|
|
468
482
|
await this.#fsImpl.unlink(file);
|
|
469
483
|
removed++;
|
|
@@ -471,15 +485,14 @@ export class TelegramAdoptionIntentStore {
|
|
|
471
485
|
if (!isMissing(error)) throw error;
|
|
472
486
|
}
|
|
473
487
|
if (intent) {
|
|
474
|
-
this.#intents.delete(intent.
|
|
475
|
-
this.releaseClaim(intent.topicId, intent.
|
|
488
|
+
this.#intents.delete(intent.providerRequestKey);
|
|
489
|
+
this.releaseClaim(intent.topicId, intent.providerRequestKey);
|
|
476
490
|
}
|
|
477
491
|
continue;
|
|
478
492
|
}
|
|
479
493
|
if (!name.endsWith(PENDING_TOPIC_FILE_SUFFIX)) continue;
|
|
480
|
-
const
|
|
481
|
-
const
|
|
482
|
-
const pending = await this.#readPendingSidecar(file);
|
|
494
|
+
const topicId = Number(name.slice(0, -PENDING_TOPIC_FILE_SUFFIX.length));
|
|
495
|
+
const pending = (await this.#readPendingSidecarRecord(file))?.pendingTopic;
|
|
483
496
|
if (Number.isSafeInteger(topicId) && topicId > 0 && pending?.topicId === topicId && now < pending.expiresAt)
|
|
484
497
|
continue;
|
|
485
498
|
try {
|
|
@@ -493,8 +506,7 @@ export class TelegramAdoptionIntentStore {
|
|
|
493
506
|
return removed;
|
|
494
507
|
}
|
|
495
508
|
|
|
496
|
-
|
|
497
|
-
async #readSidecar(file: string): Promise<TelegramAdoptionIntent | undefined> {
|
|
509
|
+
async #readSidecarRecord(file: string): Promise<{ intent: TelegramAdoptionIntent; legacy: boolean } | undefined> {
|
|
498
510
|
let raw: string;
|
|
499
511
|
try {
|
|
500
512
|
raw = await this.#fsImpl.readFile(file, "utf8");
|
|
@@ -502,16 +514,16 @@ export class TelegramAdoptionIntentStore {
|
|
|
502
514
|
if (isMissing(error)) return undefined;
|
|
503
515
|
throw error;
|
|
504
516
|
}
|
|
505
|
-
let parsed: unknown;
|
|
506
517
|
try {
|
|
507
|
-
|
|
518
|
+
return normalizePersistedIntent(JSON.parse(raw) as unknown);
|
|
508
519
|
} catch {
|
|
509
520
|
return undefined;
|
|
510
521
|
}
|
|
511
|
-
return isPersistedIntent(parsed) ? parsed.intent : undefined;
|
|
512
522
|
}
|
|
513
523
|
|
|
514
|
-
async #
|
|
524
|
+
async #readPendingSidecarRecord(
|
|
525
|
+
file: string,
|
|
526
|
+
): Promise<{ pendingTopic: TelegramPendingTopic; legacy: boolean } | undefined> {
|
|
515
527
|
let raw: string;
|
|
516
528
|
try {
|
|
517
529
|
raw = await this.#fsImpl.readFile(file, "utf8");
|
|
@@ -519,16 +531,60 @@ export class TelegramAdoptionIntentStore {
|
|
|
519
531
|
if (isMissing(error)) return undefined;
|
|
520
532
|
throw error;
|
|
521
533
|
}
|
|
522
|
-
let parsed: unknown;
|
|
523
534
|
try {
|
|
524
|
-
parsed = JSON.parse(raw);
|
|
535
|
+
const parsed: unknown = JSON.parse(raw);
|
|
536
|
+
if (!isPersistedPendingTopic(parsed)) return undefined;
|
|
537
|
+
return {
|
|
538
|
+
pendingTopic: parsed.pendingTopic,
|
|
539
|
+
legacy: parsed.version === LEGACY_TELEGRAM_ADOPTION_INTENT_VERSION,
|
|
540
|
+
};
|
|
525
541
|
} catch {
|
|
526
542
|
return undefined;
|
|
527
543
|
}
|
|
528
|
-
return isPersistedPendingTopic(parsed) ? parsed.pendingTopic : undefined;
|
|
529
544
|
}
|
|
530
545
|
|
|
531
|
-
|
|
546
|
+
async #migrateIntentSidecar(file: string, intent: TelegramAdoptionIntent): Promise<void> {
|
|
547
|
+
const destination = adoptionIntentFilePath(this.#agentDir, intent.providerRequestKey);
|
|
548
|
+
try {
|
|
549
|
+
await this.#writeSidecar(
|
|
550
|
+
destination,
|
|
551
|
+
{ version: TELEGRAM_ADOPTION_INTENT_VERSION, intent },
|
|
552
|
+
destination === file,
|
|
553
|
+
);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (error instanceof SidecarDurabilityUncertainError) logMigrationDurabilityUncertain("intent", error);
|
|
556
|
+
else logMigrationFailure("intent", "write", error);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (destination === file) return;
|
|
560
|
+
try {
|
|
561
|
+
await this.#fsImpl.unlink(file);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
logMigrationFailure("intent", "unlink", error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async #migratePendingTopicSidecar(file: string, pendingTopic: TelegramPendingTopic): Promise<void> {
|
|
568
|
+
const destination = pendingTopicFilePath(this.#agentDir, pendingTopic.topicId);
|
|
569
|
+
try {
|
|
570
|
+
await this.#writeSidecar(
|
|
571
|
+
destination,
|
|
572
|
+
{ version: TELEGRAM_ADOPTION_INTENT_VERSION, pendingTopic },
|
|
573
|
+
destination === file,
|
|
574
|
+
);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
if (error instanceof SidecarDurabilityUncertainError) logMigrationDurabilityUncertain("pending_topic", error);
|
|
577
|
+
else logMigrationFailure("pending_topic", "write", error);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (destination === file) return;
|
|
581
|
+
try {
|
|
582
|
+
await this.#fsImpl.unlink(file);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logMigrationFailure("pending_topic", "unlink", error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
532
588
|
async #syncParentDirectory(): Promise<void> {
|
|
533
589
|
let handle: AdoptionIntentFileHandle;
|
|
534
590
|
try {
|
|
@@ -556,6 +612,40 @@ export class TelegramAdoptionIntentStore {
|
|
|
556
612
|
}
|
|
557
613
|
}
|
|
558
614
|
|
|
615
|
+
function migrationFailureMessage(error: unknown): string {
|
|
616
|
+
const message =
|
|
617
|
+
error instanceof Error ? error.message : typeof error === "string" ? error : "non-error migration failure";
|
|
618
|
+
return message.length <= MIGRATION_DIAGNOSTIC_MAX_LENGTH
|
|
619
|
+
? message
|
|
620
|
+
: `${message.slice(0, MIGRATION_DIAGNOSTIC_MAX_LENGTH - 1)}…`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
class SidecarDurabilityUncertainError extends Error {
|
|
624
|
+
constructor(error: unknown) {
|
|
625
|
+
super(migrationFailureMessage(error));
|
|
626
|
+
this.name = "SidecarDurabilityUncertainError";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function logMigrationDurabilityUncertain(sidecar: "intent" | "pending_topic", error: unknown): void {
|
|
631
|
+
logger.warn(
|
|
632
|
+
"notifications: Telegram adoption sidecar migration durability is uncertain; retained migrated v2 sidecar",
|
|
633
|
+
{
|
|
634
|
+
sidecar,
|
|
635
|
+
stage: "durability",
|
|
636
|
+
error: migrationFailureMessage(error),
|
|
637
|
+
},
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function logMigrationFailure(sidecar: "intent" | "pending_topic", stage: "write" | "unlink", error: unknown): void {
|
|
642
|
+
logger.warn("notifications: Telegram adoption sidecar migration failed; retaining legacy sidecar", {
|
|
643
|
+
sidecar,
|
|
644
|
+
stage,
|
|
645
|
+
error: migrationFailureMessage(error),
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
559
649
|
async function syncRequired(handle: AdoptionIntentFileHandle): Promise<void> {
|
|
560
650
|
if (typeof handle.sync !== "function")
|
|
561
651
|
throw new Error("adoption sidecar durability requires filesystem sync support");
|