pi-harness-runtime 1.1.397 → 1.1.402
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/AGENTS.md +44 -2
- package/CHANGELOG.md +21 -0
- package/RULES.md +28 -0
- package/harness/agent-handoff.ts +2 -3
- package/harness/autonomous-review.ts +1 -1
- package/harness/blackboard.ts +2 -3
- package/harness/context-compact-orchestrator.ts +1 -1
- package/harness/context-window-manager.ts +2 -2
- package/harness/e2e/playwright-runner.ts +6 -4
- package/harness/herdr-agents.ts +1 -1
- package/harness/job-state-machine.ts +1 -1
- package/harness/langchain/daemon.ts +3 -3
- package/harness/langchain/graph.ts +2 -2
- package/harness/langchain/ping-pong-middleware.ts +2 -2
- package/harness/loop-code-agent.ts +6 -4
- package/harness/loop-controller.ts +1 -1
- package/harness/loop-review-agent.ts +1 -1
- package/harness/loop-types.ts +1 -1
- package/harness/master-planner.ts +1 -1
- package/harness/notification-events.ts +6 -6
- package/harness/project-detector/detector.ts +1 -2
- package/harness/repair-engine.ts +2 -4
- package/harness/service-diagnostics.ts +1 -1
- package/harness/skill-sync.ts +3 -3
- package/index.ts +276 -55
- package/mirror.ts +1 -1
- package/package.json +5 -3
- package/packages/a2a-adapter/dist/discovery.d.ts.map +1 -1
- package/packages/a2a-adapter/dist/discovery.js +0 -1
- package/packages/a2a-adapter/dist/discovery.js.map +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/a2a-adapter/src/discovery.ts +0 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/dist/index.d.ts +9 -0
- package/packages/auth/dist/index.d.ts.map +1 -0
- package/packages/auth/dist/index.js +9 -0
- package/packages/auth/dist/index.js.map +1 -0
- package/packages/auth/package.json +1 -1
- package/packages/auth/src/index.ts +9 -0
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/dist/index.d.ts +52 -0
- package/packages/config-capture/dist/index.d.ts.map +1 -0
- package/packages/config-capture/dist/index.js +261 -0
- package/packages/config-capture/dist/index.js.map +1 -0
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/dist/extension.d.ts +15 -0
- package/packages/file-copy-helper/dist/extension.d.ts.map +1 -0
- package/packages/file-copy-helper/dist/extension.debug.d.ts +15 -0
- package/packages/file-copy-helper/dist/extension.debug.d.ts.map +1 -0
- package/packages/file-copy-helper/dist/extension.debug.js +215 -0
- package/packages/file-copy-helper/dist/extension.debug.js.map +1 -0
- package/packages/file-copy-helper/dist/extension.js +222 -0
- package/packages/file-copy-helper/dist/extension.js.map +1 -0
- package/packages/file-copy-helper/dist/index.d.ts +3 -0
- package/packages/file-copy-helper/dist/index.d.ts.map +1 -0
- package/packages/file-copy-helper/dist/index.js +3 -0
- package/packages/file-copy-helper/dist/index.js.map +1 -0
- package/packages/file-copy-helper/dist/intent-detector.d.ts +21 -0
- package/packages/file-copy-helper/dist/intent-detector.d.ts.map +1 -0
- package/packages/file-copy-helper/dist/intent-detector.js +348 -0
- package/packages/file-copy-helper/dist/intent-detector.js.map +1 -0
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/dist/auto-continue.d.ts +161 -2
- package/packages/jev-judge/dist/auto-continue.d.ts.map +1 -1
- package/packages/jev-judge/dist/auto-continue.js +267 -22
- package/packages/jev-judge/dist/auto-continue.js.map +1 -1
- package/packages/jev-judge/dist/index.d.ts +2 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +2 -0
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/question-risk.d.ts +50 -0
- package/packages/jev-judge/dist/question-risk.d.ts.map +1 -0
- package/packages/jev-judge/dist/question-risk.js +100 -0
- package/packages/jev-judge/dist/question-risk.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/auto-continue.ts +426 -29
- package/packages/jev-judge/src/index.ts +4 -0
- package/packages/jev-judge/src/question-risk.ts +151 -0
- package/packages/jev-judge/tests/auto-continue-context.test.ts +221 -0
- package/packages/jev-judge/tests/outcome-application.test.ts +132 -0
- package/packages/jev-judge/tests/question-risk.test.ts +116 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/README.md +36 -3
- package/packages/logging/dist/config.d.ts +7 -0
- package/packages/logging/dist/config.d.ts.map +1 -1
- package/packages/logging/dist/config.js +14 -0
- package/packages/logging/dist/config.js.map +1 -1
- package/packages/logging/dist/incidents/schema.d.ts +111 -0
- package/packages/logging/dist/incidents/schema.d.ts.map +1 -0
- package/packages/logging/dist/incidents/schema.js +0 -0
- package/packages/logging/dist/incidents/schema.js.map +1 -0
- package/packages/logging/dist/index.d.ts +2 -0
- package/packages/logging/dist/index.d.ts.map +1 -1
- package/packages/logging/dist/index.js +2 -0
- package/packages/logging/dist/index.js.map +1 -1
- package/packages/logging/dist/server/http.d.ts +4 -3
- package/packages/logging/dist/server/http.d.ts.map +1 -1
- package/packages/logging/dist/server/http.js +35 -3
- package/packages/logging/dist/server/http.js.map +1 -1
- package/packages/logging/dist/server/incident-qdrant.d.ts +83 -0
- package/packages/logging/dist/server/incident-qdrant.d.ts.map +1 -0
- package/packages/logging/dist/server/incident-qdrant.js +129 -0
- package/packages/logging/dist/server/incident-qdrant.js.map +1 -0
- package/packages/logging/dist/server/incident-store.d.ts +76 -0
- package/packages/logging/dist/server/incident-store.d.ts.map +1 -0
- package/packages/logging/dist/server/incident-store.js +157 -0
- package/packages/logging/dist/server/incident-store.js.map +1 -0
- package/packages/logging/dist/server/index.d.ts +4 -0
- package/packages/logging/dist/server/index.d.ts.map +1 -1
- package/packages/logging/dist/server/index.js +2 -0
- package/packages/logging/dist/server/index.js.map +1 -1
- package/packages/logging/dist/server/service.d.ts +33 -0
- package/packages/logging/dist/server/service.d.ts.map +1 -1
- package/packages/logging/dist/server/service.js +63 -0
- package/packages/logging/dist/server/service.js.map +1 -1
- package/packages/logging/dist/shipper.d.ts +74 -3
- package/packages/logging/dist/shipper.d.ts.map +1 -1
- package/packages/logging/dist/shipper.js +88 -5
- package/packages/logging/dist/shipper.js.map +1 -1
- package/packages/logging/dist/singleton.d.ts.map +1 -1
- package/packages/logging/dist/singleton.js +1 -0
- package/packages/logging/dist/singleton.js.map +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/logging/src/config.ts +22 -0
- package/packages/logging/src/incidents/schema.ts +0 -0
- package/packages/logging/src/index.ts +17 -0
- package/packages/logging/src/server/http.ts +42 -3
- package/packages/logging/src/server/incident-qdrant.ts +177 -0
- package/packages/logging/src/server/incident-store.ts +192 -0
- package/packages/logging/src/server/index.ts +21 -0
- package/packages/logging/src/server/service.ts +79 -0
- package/packages/logging/src/shipper.ts +120 -5
- package/packages/logging/src/singleton.ts +1 -0
- package/packages/logging/test/fanout.test.ts +186 -0
- package/packages/logging/test/incidents.test.ts +404 -0
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/dist/adapters/detail-format.d.ts +42 -0
- package/packages/notification/dist/adapters/detail-format.d.ts.map +1 -0
- package/packages/notification/dist/adapters/detail-format.js +94 -0
- package/packages/notification/dist/adapters/detail-format.js.map +1 -0
- package/packages/notification/dist/adapters/email-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/email-adapter.js +3 -2
- package/packages/notification/dist/adapters/email-adapter.js.map +1 -1
- package/packages/notification/dist/adapters/line-adapter.d.ts +33 -0
- package/packages/notification/dist/adapters/line-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/line-adapter.js +87 -9
- package/packages/notification/dist/adapters/line-adapter.js.map +1 -1
- package/packages/notification/dist/adapters/ntfy-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/ntfy-adapter.js +3 -2
- package/packages/notification/dist/adapters/ntfy-adapter.js.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts +14 -0
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +89 -69
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/dist/notification-center.d.ts.map +1 -1
- package/packages/notification/dist/notification-center.js +60 -7
- package/packages/notification/dist/notification-center.js.map +1 -1
- package/packages/notification/dist/pending-question.d.ts +69 -2
- package/packages/notification/dist/pending-question.d.ts.map +1 -1
- package/packages/notification/dist/pending-question.js +88 -2
- package/packages/notification/dist/pending-question.js.map +1 -1
- package/packages/notification/dist/types.d.ts +21 -0
- package/packages/notification/dist/types.d.ts.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/detail-format.ts +114 -0
- package/packages/notification/src/adapters/email-adapter.ts +3 -2
- package/packages/notification/src/adapters/line-adapter.ts +97 -9
- package/packages/notification/src/adapters/ntfy-adapter.ts +3 -2
- package/packages/notification/src/adapters/telegram-adapter.ts +98 -76
- package/packages/notification/src/notification-center.ts +66 -8
- package/packages/notification/src/pending-question.ts +160 -8
- package/packages/notification/src/types.ts +21 -0
- package/packages/notification/test/approval-payload.test.ts +269 -0
- package/packages/notification/test/detail-format.test.ts +106 -0
- package/packages/notification/test/pending-question-lifecycle.test.ts +195 -0
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/dist/index.d.ts +9 -0
- package/packages/okf-indexer/dist/index.d.ts.map +1 -0
- package/packages/okf-indexer/dist/index.js +9 -0
- package/packages/okf-indexer/dist/index.js.map +1 -0
- package/packages/okf-indexer/package.json +1 -1
- package/packages/okf-indexer/src/index.ts +9 -0
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/dist/provider-router.d.ts.map +1 -1
- package/packages/provider-router/dist/types.d.ts +2 -2
- package/packages/provider-router/dist/types.d.ts.map +1 -1
- package/packages/provider-router/dist/types.js +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-router/src/types.ts +2 -2
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/dist/index.d.ts +9 -0
- package/packages/scheduler/dist/index.d.ts.map +1 -0
- package/packages/scheduler/dist/index.js +9 -0
- package/packages/scheduler/dist/index.js.map +1 -0
- package/packages/scheduler/dist/scheduler.d.ts +17 -0
- package/packages/scheduler/dist/scheduler.d.ts.map +1 -0
- package/packages/scheduler/dist/scheduler.js +41 -0
- package/packages/scheduler/dist/scheduler.js.map +1 -0
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler/src/index.ts +9 -0
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/dist/index.d.ts +7 -0
- package/packages/shared-context/dist/index.d.ts.map +1 -0
- package/packages/shared-context/dist/index.js +7 -0
- package/packages/shared-context/dist/index.js.map +1 -0
- package/packages/shared-context/dist/shared-context.d.ts +12 -0
- package/packages/shared-context/dist/shared-context.d.ts.map +1 -0
- package/packages/shared-context/dist/shared-context.js +39 -0
- package/packages/shared-context/dist/shared-context.js.map +1 -0
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/dist/client.d.ts +56 -0
- package/packages/skill-mcp-client/dist/client.d.ts.map +1 -0
- package/packages/skill-mcp-client/dist/client.js +101 -0
- package/packages/skill-mcp-client/dist/client.js.map +1 -0
- package/packages/skill-mcp-client/dist/errors.d.ts +44 -0
- package/packages/skill-mcp-client/dist/errors.d.ts.map +1 -0
- package/packages/skill-mcp-client/dist/errors.js +68 -0
- package/packages/skill-mcp-client/dist/errors.js.map +1 -0
- package/packages/skill-mcp-client/dist/index.d.ts +9 -0
- package/packages/skill-mcp-client/dist/index.d.ts.map +1 -0
- package/packages/skill-mcp-client/dist/index.js +9 -0
- package/packages/skill-mcp-client/dist/index.js.map +1 -0
- package/packages/skill-mcp-client/dist/types.d.ts +106 -0
- package/packages/skill-mcp-client/dist/types.d.ts.map +1 -0
- package/packages/skill-mcp-client/dist/types.js +14 -0
- package/packages/skill-mcp-client/dist/types.js.map +1 -0
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -5
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/telegram/src/agent-messages.d.ts +28 -0
- package/packages/telegram/src/agent-messages.js +90 -0
- package/packages/telegram/src/bindings.d.ts +296 -0
- package/packages/telegram/src/bindings.js +1086 -0
- package/packages/telegram/src/bus.d.ts +759 -0
- package/packages/telegram/src/bus.js +2732 -0
- package/packages/telegram/src/commands.d.ts +761 -0
- package/packages/telegram/src/commands.js +2622 -0
- package/packages/telegram/src/config.d.ts +222 -0
- package/packages/telegram/src/config.js +844 -0
- package/packages/telegram/src/delivery.d.ts +161 -0
- package/packages/telegram/src/delivery.js +550 -0
- package/packages/telegram/src/extension.d.ts +7 -0
- package/packages/telegram/src/extension.js +2444 -0
- package/packages/telegram/src/inbound.d.ts +94 -0
- package/packages/telegram/src/inbound.js +478 -0
- package/packages/telegram/src/index.d.ts +6 -0
- package/packages/telegram/src/index.js +6 -0
- package/packages/telegram/src/keyboard.d.ts +21 -0
- package/packages/telegram/src/keyboard.js +37 -0
- package/packages/telegram/src/lifecycle.d.ts +210 -0
- package/packages/telegram/src/lifecycle.js +538 -0
- package/packages/telegram/src/pi.d.ts +76 -0
- package/packages/telegram/src/pi.js +130 -0
- package/packages/telegram/src/polling.d.ts +332 -0
- package/packages/telegram/src/polling.js +1219 -0
- package/packages/telegram/src/replies.d.ts +210 -0
- package/packages/telegram/src/replies.js +752 -0
- package/packages/telegram/src/routing.d.ts +388 -0
- package/packages/telegram/src/routing.js +7886 -0
- package/packages/telegram/src/runtime.d.ts +159 -0
- package/packages/telegram/src/runtime.js +351 -0
- package/packages/telegram/src/telegram-api.d.ts +564 -0
- package/packages/telegram/src/telegram-api.js +1332 -0
- package/packages/telegram/src/turns.d.ts +108 -0
- package/packages/telegram/src/turns.js +482 -0
- package/packages/telegram/src/updates.d.ts +1545 -0
- package/packages/telegram/src/updates.js +6420 -0
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/dist/detector.d.ts +54 -0
- package/packages/todo-bd-sync/dist/detector.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/detector.js +132 -0
- package/packages/todo-bd-sync/dist/detector.js.map +1 -0
- package/packages/todo-bd-sync/dist/extension.d.ts +30 -0
- package/packages/todo-bd-sync/dist/extension.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/extension.js +35 -0
- package/packages/todo-bd-sync/dist/extension.js.map +1 -0
- package/packages/todo-bd-sync/dist/index.d.ts +87 -0
- package/packages/todo-bd-sync/dist/index.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/index.js +296 -0
- package/packages/todo-bd-sync/dist/index.js.map +1 -0
- package/packages/todo-bd-sync/dist/sync.d.ts +72 -0
- package/packages/todo-bd-sync/dist/sync.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/sync.js +290 -0
- package/packages/todo-bd-sync/dist/sync.js.map +1 -0
- package/packages/todo-bd-sync/dist/task-analyzer.d.ts +49 -0
- package/packages/todo-bd-sync/dist/task-analyzer.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/task-analyzer.js +240 -0
- package/packages/todo-bd-sync/dist/task-analyzer.js.map +1 -0
- package/packages/todo-bd-sync/dist/todo-reminder.d.ts +126 -0
- package/packages/todo-bd-sync/dist/todo-reminder.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/todo-reminder.js +329 -0
- package/packages/todo-bd-sync/dist/todo-reminder.js.map +1 -0
- package/packages/todo-bd-sync/dist/types.d.ts +65 -0
- package/packages/todo-bd-sync/dist/types.d.ts.map +1 -0
- package/packages/todo-bd-sync/dist/types.js +67 -0
- package/packages/todo-bd-sync/dist/types.js.map +1 -0
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/dist/index.d.ts +94 -0
- package/packages/token-estimation/dist/index.d.ts.map +1 -0
- package/packages/token-estimation/dist/index.js +115 -0
- package/packages/token-estimation/dist/index.js.map +1 -0
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/dist/ai-providers.d.ts +36 -0
- package/packages/types/dist/ai-providers.d.ts.map +1 -0
- package/packages/types/dist/ai-providers.js +66 -0
- package/packages/types/dist/ai-providers.js.map +1 -0
- package/packages/types/dist/index.d.ts +12 -0
- package/packages/types/dist/index.d.ts.map +1 -0
- package/packages/types/dist/index.js +12 -0
- package/packages/types/dist/index.js.map +1 -0
- package/packages/types/package.json +5 -5
- package/packages/types/src/index.ts +12 -0
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/worktree/worktree.ts +1 -1
- package/packages/write-review/package.json +1 -1
- package/renderer.ts +1 -1
- package/scripts/build-packages.ts +162 -0
- package/scripts/relay-client.ts +2 -2
- package/scripts/typecheck-packages.mjs +3 -3
- package/scripts/unified-webhook-server.ts +22 -10
- package/scripts/verify-package-contents.ts +345 -0
- package/scripts/ws-server.ts +1 -1
- package/tracker.ts +1 -1
- package/harness/browser-scrapers/base-browser-scraper.d.ts +0 -91
- package/harness/browser-scrapers/chrome-profile-manager.d.ts +0 -80
- package/harness/browser-scrapers/codex-browser-scraper.d.ts +0 -60
- package/harness/browser-scrapers/index.d.ts +0 -29
- package/harness/browser-scrapers/zai-browser-scraper.d.ts +0 -79
- package/harness/e2e/browser-scrapers/base-browser-scraper.d.ts +0 -92
- package/harness/e2e/browser-scrapers/chrome-profile-manager.d.ts +0 -81
- package/harness/e2e/browser-scrapers/codex-browser-scraper.d.ts +0 -61
- package/harness/e2e/browser-scrapers/index.d.ts +0 -30
- package/harness/e2e/browser-scrapers/zai-browser-scraper.d.ts +0 -80
- package/harness/langchain/agents.d.ts +0 -143
- package/harness/langchain/agents.test.d.ts +0 -2
- package/harness/langchain/checkpointer.d.ts +0 -48
- package/harness/langchain/checkpointer.test.d.ts +0 -8
- package/harness/langchain/cron-cli.d.ts +0 -12
- package/harness/langchain/daemon.d.ts +0 -161
- package/harness/langchain/daemon.test.d.ts +0 -11
- package/harness/langchain/file-tools.d.ts +0 -97
- package/harness/langchain/graph.d.ts +0 -706
- package/harness/langchain/model-router.d.ts +0 -63
- package/harness/langchain/model-router.test.d.ts +0 -2
- package/harness/langchain/models.d.ts +0 -72
- package/harness/langchain/ping-pong-middleware.d.ts +0 -28
- package/harness/langchain/ping-pong-middleware.test.d.ts +0 -2
- package/harness/langchain/run.d.ts +0 -15
- package/harness/langchain/scoreboard.test.d.ts +0 -13
- package/harness/langchain/status-line.d.ts +0 -91
- package/harness/langchain/status-line.test.d.ts +0 -8
- package/harness/langchain/surge.d.ts +0 -111
- package/harness/langchain/surge.test.d.ts +0 -10
- package/harness/langchain/widget.d.ts +0 -105
- package/harness/langchain/widget.test.d.ts +0 -8
- package/packages/autonomous-runtime/dist/worker.d.ts +0 -63
- package/packages/autonomous-runtime/dist/worker.js +0 -133
- package/packages/capability-registry/src/types.d.ts +0 -47
- package/packages/clipboard/dist/clipboard.d.ts +0 -11
- package/packages/clipboard/dist/clipboard.js +0 -40
- package/packages/clipboard/dist/index.d.ts +0 -2
- package/packages/clipboard/dist/index.js +0 -2
- package/packages/clipboard/dist/types.d.ts +0 -13
- package/packages/clipboard/dist/types.js +0 -5
- package/packages/context-discovery/dist/discover.d.ts +0 -3
- package/packages/context-discovery/dist/discover.js +0 -42
- package/packages/context-discovery/dist/discovery.d.ts +0 -28
- package/packages/context-discovery/dist/discovery.js +0 -21
- package/packages/context-discovery/dist/index.d.ts +0 -3
- package/packages/context-discovery/dist/index.js +0 -2
- package/packages/context-discovery/dist/types.d.ts +0 -16
- package/packages/context-discovery/dist/types.js +0 -2
- package/packages/event-bus/dist/bus.d.ts +0 -15
- package/packages/event-bus/dist/bus.js +0 -90
- package/packages/event-bus/dist/index.d.ts +0 -4
- package/packages/event-bus/dist/index.js +0 -3
- package/packages/event-bus/dist/test/bus.test.js +0 -52
- package/packages/event-bus/dist/types.d.ts +0 -27
- package/packages/event-bus/dist/types.js +0 -10
- package/packages/event-store/dist/index.d.ts +0 -3
- package/packages/event-store/dist/index.js +0 -2
- package/packages/event-store/dist/store.d.ts +0 -15
- package/packages/event-store/dist/store.js +0 -131
- package/packages/event-store/dist/test/store.test.js +0 -57
- package/packages/event-store/dist/types.d.ts +0 -48
- package/packages/event-store/dist/types.js +0 -5
- package/packages/event-store/src/types.d.ts +0 -48
- package/packages/file-copy-helper/dist/src/extension.js +0 -186
- package/packages/model-registry/src/types.d.ts +0 -63
- package/packages/notification/dist/src/adapters/email-adapter.d.ts +0 -17
- package/packages/notification/dist/src/adapters/email-adapter.d.ts.map +0 -1
- package/packages/notification/dist/src/adapters/email-adapter.js +0 -89
- package/packages/notification/dist/src/adapters/email-adapter.js.map +0 -1
- package/packages/notification/dist/src/adapters/line-adapter.d.ts +0 -17
- package/packages/notification/dist/src/adapters/line-adapter.d.ts.map +0 -1
- package/packages/notification/dist/src/adapters/line-adapter.js +0 -99
- package/packages/notification/dist/src/adapters/line-adapter.js.map +0 -1
- package/packages/notification/dist/src/adapters/ntfy-adapter.d.ts +0 -17
- package/packages/notification/dist/src/adapters/ntfy-adapter.d.ts.map +0 -1
- package/packages/notification/dist/src/adapters/ntfy-adapter.js +0 -84
- package/packages/notification/dist/src/adapters/ntfy-adapter.js.map +0 -1
- package/packages/notification/dist/src/adapters/telegram-adapter.d.ts +0 -56
- package/packages/notification/dist/src/adapters/telegram-adapter.d.ts.map +0 -1
- package/packages/notification/dist/src/adapters/telegram-adapter.js +0 -251
- package/packages/notification/dist/src/adapters/telegram-adapter.js.map +0 -1
- package/packages/notification/dist/src/adapters/webhook-adapter.d.ts +0 -15
- package/packages/notification/dist/src/adapters/webhook-adapter.d.ts.map +0 -1
- package/packages/notification/dist/src/adapters/webhook-adapter.js +0 -64
- package/packages/notification/dist/src/adapters/webhook-adapter.js.map +0 -1
- package/packages/notification/dist/src/base-adapter.d.ts +0 -36
- package/packages/notification/dist/src/base-adapter.d.ts.map +0 -1
- package/packages/notification/dist/src/base-adapter.js +0 -39
- package/packages/notification/dist/src/base-adapter.js.map +0 -1
- package/packages/notification/dist/src/index.d.ts +0 -12
- package/packages/notification/dist/src/index.d.ts.map +0 -1
- package/packages/notification/dist/src/index.js +0 -12
- package/packages/notification/dist/src/index.js.map +0 -1
- package/packages/notification/dist/src/notification-center.d.ts +0 -89
- package/packages/notification/dist/src/notification-center.d.ts.map +0 -1
- package/packages/notification/dist/src/notification-center.js +0 -356
- package/packages/notification/dist/src/notification-center.js.map +0 -1
- package/packages/notification/dist/src/notification-policy.d.ts +0 -66
- package/packages/notification/dist/src/notification-policy.d.ts.map +0 -1
- package/packages/notification/dist/src/notification-policy.js +0 -153
- package/packages/notification/dist/src/notification-policy.js.map +0 -1
- package/packages/notification/dist/src/telegram-webhook-handler.d.ts +0 -86
- package/packages/notification/dist/src/telegram-webhook-handler.d.ts.map +0 -1
- package/packages/notification/dist/src/telegram-webhook-handler.js +0 -149
- package/packages/notification/dist/src/telegram-webhook-handler.js.map +0 -1
- package/packages/notification/dist/src/types.d.ts +0 -106
- package/packages/notification/dist/src/types.d.ts.map +0 -1
- package/packages/notification/dist/src/types.js +0 -7
- package/packages/notification/dist/src/types.js.map +0 -1
- package/packages/projection-engine/dist/engine.d.ts +0 -61
- package/packages/projection-engine/dist/engine.js +0 -76
- package/packages/projection-engine/dist/index.d.ts +0 -2
- package/packages/projection-engine/dist/index.js +0 -2
- package/packages/projection-engine/dist/project.d.ts +0 -3
- package/packages/projection-engine/dist/project.js +0 -24
- package/packages/projection-engine/dist/types.d.ts +0 -55
- package/packages/projection-engine/dist/types.js +0 -5
- package/packages/projection-engine/src/types.d.ts +0 -55
- package/packages/provider-adapter-sdk/dist/src/builder.d.ts +0 -140
- package/packages/provider-adapter-sdk/dist/src/builder.d.ts.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/builder.js +0 -332
- package/packages/provider-adapter-sdk/dist/src/builder.js.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/errors.d.ts +0 -71
- package/packages/provider-adapter-sdk/dist/src/errors.d.ts.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/errors.js +0 -111
- package/packages/provider-adapter-sdk/dist/src/errors.js.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/index.d.ts +0 -15
- package/packages/provider-adapter-sdk/dist/src/index.d.ts.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/index.js +0 -19
- package/packages/provider-adapter-sdk/dist/src/index.js.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/registry.d.ts +0 -108
- package/packages/provider-adapter-sdk/dist/src/registry.d.ts.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/registry.js +0 -301
- package/packages/provider-adapter-sdk/dist/src/registry.js.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/tester.d.ts +0 -71
- package/packages/provider-adapter-sdk/dist/src/tester.d.ts.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/tester.js +0 -392
- package/packages/provider-adapter-sdk/dist/src/tester.js.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/types.d.ts +0 -182
- package/packages/provider-adapter-sdk/dist/src/types.d.ts.map +0 -1
- package/packages/provider-adapter-sdk/dist/src/types.js +0 -11
- package/packages/provider-adapter-sdk/dist/src/types.js.map +0 -1
- package/packages/provider-router/dist/capability-registry/src/types.d.ts +0 -47
- package/packages/provider-router/dist/capability-registry/src/types.d.ts.map +0 -1
- package/packages/provider-router/dist/capability-registry/src/types.js +0 -10
- package/packages/provider-router/dist/capability-registry/src/types.js.map +0 -1
- package/packages/provider-router/dist/provider-router/src/index.d.ts +0 -23
- package/packages/provider-router/dist/provider-router/src/index.d.ts.map +0 -1
- package/packages/provider-router/dist/provider-router/src/index.js +0 -25
- package/packages/provider-router/dist/provider-router/src/index.js.map +0 -1
- package/packages/provider-router/dist/provider-router/src/policy.d.ts +0 -10
- package/packages/provider-router/dist/provider-router/src/policy.d.ts.map +0 -1
- package/packages/provider-router/dist/provider-router/src/policy.js +0 -45
- package/packages/provider-router/dist/provider-router/src/policy.js.map +0 -1
- package/packages/provider-router/dist/provider-router/src/provider-router.d.ts +0 -56
- package/packages/provider-router/dist/provider-router/src/provider-router.d.ts.map +0 -1
- package/packages/provider-router/dist/provider-router/src/provider-router.js +0 -222
- package/packages/provider-router/dist/provider-router/src/provider-router.js.map +0 -1
- package/packages/provider-router/dist/provider-router/src/strategies.d.ts +0 -19
- package/packages/provider-router/dist/provider-router/src/strategies.d.ts.map +0 -1
- package/packages/provider-router/dist/provider-router/src/strategies.js +0 -110
- package/packages/provider-router/dist/provider-router/src/strategies.js.map +0 -1
- package/packages/provider-router/dist/provider-router/src/types.d.ts +0 -91
- package/packages/provider-router/dist/provider-router/src/types.d.ts.map +0 -1
- package/packages/provider-router/dist/provider-router/src/types.js +0 -10
- package/packages/provider-router/dist/provider-router/src/types.js.map +0 -1
- package/packages/provider-router/dist/src/index.d.ts +0 -23
- package/packages/provider-router/dist/src/index.d.ts.map +0 -1
- package/packages/provider-router/dist/src/index.js +0 -25
- package/packages/provider-router/dist/src/index.js.map +0 -1
- package/packages/provider-router/dist/src/policy.d.ts +0 -10
- package/packages/provider-router/dist/src/policy.d.ts.map +0 -1
- package/packages/provider-router/dist/src/policy.js +0 -45
- package/packages/provider-router/dist/src/policy.js.map +0 -1
- package/packages/provider-router/dist/src/provider-router.d.ts +0 -56
- package/packages/provider-router/dist/src/provider-router.d.ts.map +0 -1
- package/packages/provider-router/dist/src/provider-router.js +0 -222
- package/packages/provider-router/dist/src/provider-router.js.map +0 -1
- package/packages/provider-router/dist/src/strategies.d.ts +0 -19
- package/packages/provider-router/dist/src/strategies.d.ts.map +0 -1
- package/packages/provider-router/dist/src/strategies.js +0 -110
- package/packages/provider-router/dist/src/strategies.js.map +0 -1
- package/packages/provider-router/dist/src/types.d.ts +0 -91
- package/packages/provider-router/dist/src/types.d.ts.map +0 -1
- package/packages/provider-router/dist/src/types.js +0 -10
- package/packages/provider-router/dist/src/types.js.map +0 -1
- package/packages/provider-router/dist/types/src/runtime-types.d.ts +0 -412
- package/packages/provider-router/dist/types/src/runtime-types.d.ts.map +0 -1
- package/packages/provider-router/dist/types/src/runtime-types.js +0 -2
- package/packages/provider-router/dist/types/src/runtime-types.js.map +0 -1
- package/packages/session-api/dist/api.d.ts +0 -83
- package/packages/session-api/dist/api.js +0 -35
- package/packages/session-api/dist/index.d.ts +0 -2
- package/packages/session-api/dist/index.js +0 -2
- package/packages/session-api/dist/types.d.ts +0 -22
- package/packages/session-api/dist/types.js +0 -5
- package/packages/session-export/dist/exporter.d.ts +0 -28
- package/packages/session-export/dist/exporter.js +0 -38
- package/packages/session-export/dist/index.d.ts +0 -2
- package/packages/session-export/dist/index.js +0 -2
- package/packages/session-export/dist/types.d.ts +0 -20
- package/packages/session-export/dist/types.js +0 -5
- package/packages/subscription-engine/dist/engine.d.ts +0 -13
- package/packages/subscription-engine/dist/engine.js +0 -40
- package/packages/subscription-engine/dist/index.d.ts +0 -2
- package/packages/subscription-engine/dist/index.js +0 -2
- package/packages/subscription-engine/dist/subscriptions.d.ts +0 -19
- package/packages/subscription-engine/dist/subscriptions.js +0 -32
- package/packages/subscription-engine/dist/types.d.ts +0 -24
- package/packages/subscription-engine/dist/types.js +0 -5
- package/packages/trajectory/test/store.test.d.ts +0 -2
- package/packages/workflow-events/dist/events.d.ts +0 -38
- package/packages/workflow-events/dist/events.js +0 -46
- package/packages/workflow-events/dist/index.d.ts +0 -2
- package/packages/workflow-events/dist/index.js +0 -2
- package/packages/workflow-events/dist/pipeline.d.ts +0 -21
- package/packages/workflow-events/dist/pipeline.js +0 -27
- package/packages/workflow-events/dist/types.d.ts +0 -110
- package/packages/workflow-events/dist/types.js +0 -5
- package/packages/workflow-events/src/types.d.ts +0 -110
|
@@ -0,0 +1,2732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram multi-instance bus protocol and IPC helpers
|
|
3
|
+
* Zones: multi-instance bus, local IPC contract, live instance routing
|
|
4
|
+
* Owns serializable bus envelopes, socket/auth helpers, local IPC client/server primitives,
|
|
5
|
+
* cross-instance forwarding helpers, and the live follower registry model.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
8
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readlinkSync, renameSync, symlinkSync, unlinkSync, } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { createConnection, createServer, } from "node:net";
|
|
11
|
+
import { platform as getPlatform, tmpdir } from "node:os";
|
|
12
|
+
import { basename, dirname, join } from "node:path";
|
|
13
|
+
import { isDeepStrictEqual } from "node:util";
|
|
14
|
+
import { classifyTelegramBusTransportError, createTelegramBusTransportTimeoutError, delayTelegramBusTransportRetry, getTelegramBusEndpointDiagnostics, getTelegramBusFollowerEndpoint, getTelegramBusLeaderEndpoint, getTelegramBusPipePath, getTelegramBusTransportRetryPolicy, isRetryableTelegramBusTransportError, isTelegramBusPipePath, probeTelegramBusEndpoint, TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES, } from "./bus-transport.js";
|
|
15
|
+
import { isSameTelegramLockOwner, TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
|
|
16
|
+
import { resolveAgentDir } from "./paths.js";
|
|
17
|
+
import { getTelegramProcessBirthIdentity } from "./process-identity.js";
|
|
18
|
+
import { parseTelegramQueueHandoffPayload, TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES, } from "./queue.js";
|
|
19
|
+
import { areTelegramTargetsEqual, parseTelegramTarget as parseTarget, } from "./target.js";
|
|
20
|
+
import { normalizeTelegramSessionReplacementIntent, } from "./threads.js";
|
|
21
|
+
import { hasOnlyWireKeys, isWireRecord as isRecord } from "./wire.js";
|
|
22
|
+
export function createCurrentTelegramBusProcessRuntime(input) {
|
|
23
|
+
return createTelegramBusProcessRuntime({
|
|
24
|
+
getActiveProfileName: input.getActiveProfileName,
|
|
25
|
+
endpointLayout: input.endpointLayout,
|
|
26
|
+
pid: input.pid ?? process.pid,
|
|
27
|
+
parentPid: input.parentPid ?? process.ppid,
|
|
28
|
+
createdAtMs: input.createdAtMs ?? Date.now(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function createTelegramBusProcessRuntime(input) {
|
|
32
|
+
const getActiveProfileName = input.getActiveProfileName, endpointLayout = input.endpointLayout;
|
|
33
|
+
const instanceId = `${input.pid}:${input.createdAtMs}`;
|
|
34
|
+
const ownerPid = input.parentPid || input.pid;
|
|
35
|
+
const manualFollowerOwnerId = input.parentProcessIdentity ??
|
|
36
|
+
getTelegramProcessBirthIdentity(ownerPid, input.createdAtMs);
|
|
37
|
+
return {
|
|
38
|
+
instanceId,
|
|
39
|
+
processId: input.pid,
|
|
40
|
+
processBirthId: getTelegramProcessBirthIdentity(input.pid, instanceId),
|
|
41
|
+
manualFollowerOwnerId,
|
|
42
|
+
getLeaderSocketPath: () => getTelegramBusSocketPath(undefined, undefined, getActiveProfileName(), endpointLayout),
|
|
43
|
+
getFollowerSocketPath: () => getTelegramBusFollowerSocketPath(instanceId, undefined, undefined, getActiveProfileName(), endpointLayout),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function createTelegramBusAuthSecret() {
|
|
47
|
+
return randomBytes(32).toString("base64url");
|
|
48
|
+
}
|
|
49
|
+
// v3: journal source serialization left the config transaction; mixed v2/v3 peers would not exclude each other.
|
|
50
|
+
const TELEGRAM_BUS_PROTOCOL_VERSION = 3;
|
|
51
|
+
export const TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION = "durable-follower-admission-v1";
|
|
52
|
+
export const TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF = "queue-handoff-v1";
|
|
53
|
+
export const TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE = "input-custody-reference-v1";
|
|
54
|
+
export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME = "workspace-thread-rename-v1";
|
|
55
|
+
export const TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE = "thread-display-mode-v1";
|
|
56
|
+
export const TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT = "directory-display-format-v1";
|
|
57
|
+
export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT = "workspace-follower-auto-connect-v1";
|
|
58
|
+
export const TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT = "session-replacement-intent-v1";
|
|
59
|
+
export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE = "workspace-restore-v1";
|
|
60
|
+
export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE = "live-thread-rebind-save-v1";
|
|
61
|
+
export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY = "live-thread-rebind-apply-v1";
|
|
62
|
+
export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE = "live-thread-rebind-settle-v1";
|
|
63
|
+
/** Restricted recipient text/menu delivery; negotiated identity and captured effect authority remain required. */
|
|
64
|
+
export const TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY = "selected-menu-delivery-v1";
|
|
65
|
+
/** Versioned held-command set; syntax is shared, while the captured recipient Commands registry owns supported plans. */
|
|
66
|
+
export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET = "live-thread-rebind-command-set-v1";
|
|
67
|
+
/** Every live rebinding needs the save/apply/settle trio; held command selections additionally need these two. */
|
|
68
|
+
export const TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES = [
|
|
69
|
+
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
|
|
70
|
+
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
|
|
71
|
+
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
|
|
72
|
+
];
|
|
73
|
+
export const TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES = [
|
|
74
|
+
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
|
|
75
|
+
TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
|
|
76
|
+
];
|
|
77
|
+
function parseSelectedCommandInput(value) {
|
|
78
|
+
if (!isRecord(value) ||
|
|
79
|
+
!hasOnlyWireKeys(value, ["name", "target"]) ||
|
|
80
|
+
typeof value.name !== "string" ||
|
|
81
|
+
!/^[a-z0-9_]{1,32}$/u.test(value.name) ||
|
|
82
|
+
!isRecord(value.target) ||
|
|
83
|
+
!hasOnlyWireKeys(value.target, ["chatId", "threadId"]))
|
|
84
|
+
return undefined;
|
|
85
|
+
const target = parseThreadTarget(value.target);
|
|
86
|
+
return target &&
|
|
87
|
+
Number.isSafeInteger(target.chatId) &&
|
|
88
|
+
target.chatId > 0 &&
|
|
89
|
+
Number.isSafeInteger(target.threadId) &&
|
|
90
|
+
target.threadId > 0
|
|
91
|
+
? { name: value.name, target }
|
|
92
|
+
: undefined;
|
|
93
|
+
}
|
|
94
|
+
/** Pure live-rebind recipient/operation identity shared by prepare/apply/settle envelopes. */
|
|
95
|
+
function parseLiveRebindRecipient(value) {
|
|
96
|
+
const { recipientInstanceId, recipientRegistrationGeneration, recipientSessionId, recipientBindingKey, operationId, } = value;
|
|
97
|
+
return typeof recipientInstanceId === "string" &&
|
|
98
|
+
recipientInstanceId &&
|
|
99
|
+
typeof recipientRegistrationGeneration === "string" &&
|
|
100
|
+
recipientRegistrationGeneration &&
|
|
101
|
+
typeof recipientSessionId === "string" &&
|
|
102
|
+
recipientSessionId &&
|
|
103
|
+
typeof recipientBindingKey === "string" &&
|
|
104
|
+
recipientBindingKey &&
|
|
105
|
+
typeof operationId === "string" &&
|
|
106
|
+
operationId.length > 0 &&
|
|
107
|
+
operationId.length <= 128
|
|
108
|
+
? {
|
|
109
|
+
recipientInstanceId,
|
|
110
|
+
recipientRegistrationGeneration,
|
|
111
|
+
recipientSessionId,
|
|
112
|
+
recipientBindingKey,
|
|
113
|
+
operationId,
|
|
114
|
+
}
|
|
115
|
+
: undefined;
|
|
116
|
+
}
|
|
117
|
+
/** Non-empty, duplicate-free, non-negative safe-integer Telegram update ids. */
|
|
118
|
+
function isUniqueSourceUpdateIds(value) {
|
|
119
|
+
return (Array.isArray(value) &&
|
|
120
|
+
value.length > 0 &&
|
|
121
|
+
value.every((id) => Number.isSafeInteger(id) && id >= 0) &&
|
|
122
|
+
new Set(value).size === value.length);
|
|
123
|
+
}
|
|
124
|
+
function parsePreparedCommandSource(value) {
|
|
125
|
+
if (!isRecord(value) ||
|
|
126
|
+
!hasOnlyWireKeys(value, [
|
|
127
|
+
"journalBindingKey",
|
|
128
|
+
"updateId",
|
|
129
|
+
"sourceSha256",
|
|
130
|
+
]) ||
|
|
131
|
+
typeof value.journalBindingKey !== "string" ||
|
|
132
|
+
!value.journalBindingKey ||
|
|
133
|
+
!Number.isSafeInteger(value.updateId) ||
|
|
134
|
+
value.updateId < 0 ||
|
|
135
|
+
typeof value.sourceSha256 !== "string" ||
|
|
136
|
+
!/^[a-f0-9]{64}$/u.test(value.sourceSha256))
|
|
137
|
+
return undefined;
|
|
138
|
+
return {
|
|
139
|
+
journalBindingKey: value.journalBindingKey,
|
|
140
|
+
updateId: value.updateId,
|
|
141
|
+
sourceSha256: value.sourceSha256,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export function createTelegramBusProtocolIdentity(input) {
|
|
145
|
+
const runtimeBuild = input.runtimeBuild.trim();
|
|
146
|
+
if (!runtimeBuild || runtimeBuild.length > 128) {
|
|
147
|
+
throw new Error("Telegram bus runtime build identity is invalid.");
|
|
148
|
+
}
|
|
149
|
+
const capabilities = [...new Set(input.capabilities ?? [])].sort();
|
|
150
|
+
if (capabilities.length > 32 ||
|
|
151
|
+
capabilities.some((capability) => capability.length > 128 || !/^[a-z0-9][a-z0-9._-]*$/u.test(capability))) {
|
|
152
|
+
throw new Error("Telegram bus capabilities must be canonical identifiers.");
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
protocolVersion: TELEGRAM_BUS_PROTOCOL_VERSION,
|
|
156
|
+
runtimeBuild,
|
|
157
|
+
capabilities,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function createTelegramCurrentBusProtocolIdentity(capabilities = []) {
|
|
161
|
+
const packageMetadata = createRequire(import.meta.url)("../package.json");
|
|
162
|
+
if (typeof packageMetadata.version !== "string") {
|
|
163
|
+
throw new Error("Telegram package build identity is unavailable.");
|
|
164
|
+
}
|
|
165
|
+
return createTelegramBusProtocolIdentity({
|
|
166
|
+
runtimeBuild: packageMetadata.version,
|
|
167
|
+
capabilities,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
export function hasTelegramBusCapability(identity, capability) {
|
|
171
|
+
return identity?.capabilities.includes(capability) ?? false;
|
|
172
|
+
}
|
|
173
|
+
export function getTelegramInputCustodyPeerReadiness(followers) {
|
|
174
|
+
return followers.map((follower) => {
|
|
175
|
+
if (!follower.registrationGeneration || !follower.protocol)
|
|
176
|
+
return "unknown";
|
|
177
|
+
return hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION) &&
|
|
178
|
+
hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE)
|
|
179
|
+
? "ready"
|
|
180
|
+
: "legacy";
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** Both peers advertise every required capability over a compatible protocol; reads only the two identities. */
|
|
184
|
+
export function hasTelegramBusSharedCapabilities(local, remote, required) {
|
|
185
|
+
return (required.every((capability) => hasTelegramBusCapability(local, capability) &&
|
|
186
|
+
hasTelegramBusCapability(remote, capability)) && getTelegramBusProtocolCompatibility({ local, remote }).compatible);
|
|
187
|
+
}
|
|
188
|
+
export function getTelegramBusProtocolCompatibility(input) {
|
|
189
|
+
if (!input.remote) {
|
|
190
|
+
return {
|
|
191
|
+
compatible: false,
|
|
192
|
+
reason: "missing-identity",
|
|
193
|
+
missingCapabilities: [],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (input.remote.protocolVersion !== input.local.protocolVersion) {
|
|
197
|
+
return {
|
|
198
|
+
compatible: false,
|
|
199
|
+
reason: "version-mismatch",
|
|
200
|
+
missingCapabilities: [],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const remoteCapabilities = new Set(input.remote.capabilities);
|
|
204
|
+
const missingCapabilities = input.local.capabilities
|
|
205
|
+
.filter((capability) => capability === TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION)
|
|
206
|
+
.filter((capability) => !remoteCapabilities.has(capability));
|
|
207
|
+
return missingCapabilities.length > 0
|
|
208
|
+
? {
|
|
209
|
+
compatible: false,
|
|
210
|
+
reason: "missing-capability",
|
|
211
|
+
missingCapabilities,
|
|
212
|
+
}
|
|
213
|
+
: { compatible: true, missingCapabilities: [] };
|
|
214
|
+
}
|
|
215
|
+
export function getTelegramBusSocketPath(agentDir = resolveAgentDir(), platform = getPlatform(), profileName, layout) {
|
|
216
|
+
return getTelegramBusLeaderEndpoint({
|
|
217
|
+
agentDir,
|
|
218
|
+
platform,
|
|
219
|
+
profileName,
|
|
220
|
+
layout,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
export function getTelegramBusFollowerSocketPath(instanceId, agentDir = resolveAgentDir(), platform = getPlatform(), profileName, layout) {
|
|
224
|
+
return getTelegramBusFollowerEndpoint({
|
|
225
|
+
agentDir,
|
|
226
|
+
platform,
|
|
227
|
+
instanceId,
|
|
228
|
+
profileName,
|
|
229
|
+
layout,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/** Whether a live follower is still exactly the captured registration: identity, process, session, endpoint and negotiated protocol. */
|
|
233
|
+
export function isSameTelegramBusFollowerRegistration(live, captured) {
|
|
234
|
+
return (live.instanceId === captured.instanceId &&
|
|
235
|
+
live.profileKey === captured.profileKey &&
|
|
236
|
+
live.sessionId === captured.sessionId &&
|
|
237
|
+
live.registrationGeneration === captured.registrationGeneration &&
|
|
238
|
+
live.pid === captured.pid &&
|
|
239
|
+
live.processBirthId === captured.processBirthId &&
|
|
240
|
+
live.sessionGeneration === captured.sessionGeneration &&
|
|
241
|
+
live.cwd === captured.cwd &&
|
|
242
|
+
live.slot === captured.slot &&
|
|
243
|
+
live.busSocketPath === captured.busSocketPath &&
|
|
244
|
+
isDeepStrictEqual(live.protocol, captured.protocol));
|
|
245
|
+
}
|
|
246
|
+
export function getTelegramFollowerTargetOwnership(input) {
|
|
247
|
+
const liveFollower = input.followers.find((follower) => {
|
|
248
|
+
return (follower.target?.chatId === input.target.chatId &&
|
|
249
|
+
follower.target.threadId === input.target.threadId);
|
|
250
|
+
});
|
|
251
|
+
if (liveFollower?.registrationGeneration &&
|
|
252
|
+
liveFollower.profileKey &&
|
|
253
|
+
liveFollower.protocol?.capabilities.includes(TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION)) {
|
|
254
|
+
return {
|
|
255
|
+
instanceId: liveFollower.instanceId,
|
|
256
|
+
ownerGeneration: liveFollower.registrationGeneration,
|
|
257
|
+
recipientBindingKey: liveFollower.profileKey,
|
|
258
|
+
protocolIdentity: liveFollower.protocol,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
// Persisted records are restart hints, not live routing authority. Only an
|
|
262
|
+
// authenticated current follower registration may receive forwarded work.
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD = "__piTelegramAggregateDelivery";
|
|
266
|
+
const TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD = "__piTelegramCrossTargetDelivery";
|
|
267
|
+
function hasTelegramBusDeliveryMarker(body, marker) {
|
|
268
|
+
return Boolean(body &&
|
|
269
|
+
typeof body === "object" &&
|
|
270
|
+
!Array.isArray(body) &&
|
|
271
|
+
body[marker] === true);
|
|
272
|
+
}
|
|
273
|
+
export function markTelegramBusAggregateDelivery(body) {
|
|
274
|
+
return { ...body, [TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD]: true };
|
|
275
|
+
}
|
|
276
|
+
export function markTelegramBusCrossTargetDelivery(body) {
|
|
277
|
+
return { ...body, [TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD]: true };
|
|
278
|
+
}
|
|
279
|
+
export function stripTelegramBusApiMetadata(body) {
|
|
280
|
+
if (!(TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD in body) &&
|
|
281
|
+
!(TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD in body)) {
|
|
282
|
+
return body;
|
|
283
|
+
}
|
|
284
|
+
const clean = { ...body };
|
|
285
|
+
delete clean[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD];
|
|
286
|
+
delete clean[TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD];
|
|
287
|
+
return clean;
|
|
288
|
+
}
|
|
289
|
+
export function isTelegramFollowerApiCallAllowed(input) {
|
|
290
|
+
const allowedCallMethods = new Set([
|
|
291
|
+
"answerCallbackQuery",
|
|
292
|
+
"answerGuestQuery",
|
|
293
|
+
"closeForumTopic",
|
|
294
|
+
"deleteForumTopic",
|
|
295
|
+
"deleteMessage",
|
|
296
|
+
"editForumTopic",
|
|
297
|
+
"editMessageReplyMarkup",
|
|
298
|
+
"editMessageText",
|
|
299
|
+
"sendChatAction",
|
|
300
|
+
"sendMessage",
|
|
301
|
+
"sendMessageDraft",
|
|
302
|
+
"sendRichMessage",
|
|
303
|
+
"sendRichMessageDraft",
|
|
304
|
+
]);
|
|
305
|
+
const allowedMultipartMethods = new Set([
|
|
306
|
+
"sendAudio",
|
|
307
|
+
"sendDocument",
|
|
308
|
+
"sendMediaGroup",
|
|
309
|
+
"sendPhoto",
|
|
310
|
+
"sendRichMessage",
|
|
311
|
+
"sendVoice",
|
|
312
|
+
]);
|
|
313
|
+
const target = input.follower.target;
|
|
314
|
+
const matchesId = (value, expected) => value === expected || value === String(expected);
|
|
315
|
+
const isTargetScoped = (body) => {
|
|
316
|
+
if (!target)
|
|
317
|
+
return false;
|
|
318
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
319
|
+
return false;
|
|
320
|
+
const record = body;
|
|
321
|
+
if (!matchesId(record.chat_id, target.chatId))
|
|
322
|
+
return false;
|
|
323
|
+
if (target.threadId === undefined)
|
|
324
|
+
return true;
|
|
325
|
+
return matchesId(record.message_thread_id, target.threadId);
|
|
326
|
+
};
|
|
327
|
+
const isTargetChatScoped = (body) => {
|
|
328
|
+
if (!target)
|
|
329
|
+
return false;
|
|
330
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
331
|
+
return false;
|
|
332
|
+
const record = body;
|
|
333
|
+
return matchesId(record.chat_id, target.chatId);
|
|
334
|
+
};
|
|
335
|
+
const isDifferentTargetScoped = (body) => {
|
|
336
|
+
if (!target || !isTargetChatScoped(body))
|
|
337
|
+
return false;
|
|
338
|
+
const threadId = body.message_thread_id;
|
|
339
|
+
if (threadId === undefined)
|
|
340
|
+
return target.threadId !== undefined;
|
|
341
|
+
const parsedThreadId = typeof threadId === "number" ? threadId : Number(threadId);
|
|
342
|
+
return (Number.isInteger(parsedThreadId) &&
|
|
343
|
+
(target.threadId === undefined || !matchesId(threadId, target.threadId)));
|
|
344
|
+
};
|
|
345
|
+
const isTargetMessageScoped = (body) => {
|
|
346
|
+
if (!isTargetChatScoped(body))
|
|
347
|
+
return false;
|
|
348
|
+
const messageId = body.message_id;
|
|
349
|
+
const parsedMessageId = typeof messageId === "number" ? messageId : Number(messageId);
|
|
350
|
+
return (Number.isInteger(parsedMessageId) && matchesId(messageId, parsedMessageId));
|
|
351
|
+
};
|
|
352
|
+
const isBotCommandRegistration = (body) => {
|
|
353
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
354
|
+
return false;
|
|
355
|
+
const commands = body.commands;
|
|
356
|
+
return (Array.isArray(commands) &&
|
|
357
|
+
commands.every((command) => command &&
|
|
358
|
+
typeof command === "object" &&
|
|
359
|
+
!Array.isArray(command) &&
|
|
360
|
+
typeof command.command === "string" &&
|
|
361
|
+
typeof command.description === "string"));
|
|
362
|
+
};
|
|
363
|
+
if (input.method === "downloadFile")
|
|
364
|
+
return true;
|
|
365
|
+
if (input.method === "call") {
|
|
366
|
+
const apiMethod = input.args[0];
|
|
367
|
+
if (typeof apiMethod !== "string")
|
|
368
|
+
return false;
|
|
369
|
+
if (apiMethod === "answerCallbackQuery" ||
|
|
370
|
+
apiMethod === "answerGuestQuery") {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
if (apiMethod === "getMe")
|
|
374
|
+
return true;
|
|
375
|
+
if (apiMethod === "setMyCommands")
|
|
376
|
+
return isBotCommandRegistration(input.args[1]);
|
|
377
|
+
if (apiMethod === "sendChatAction")
|
|
378
|
+
return isTargetChatScoped(input.args[1]);
|
|
379
|
+
if (apiMethod === "sendMessage" &&
|
|
380
|
+
hasTelegramBusDeliveryMarker(input.args[1], TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD)) {
|
|
381
|
+
const body = input.args[1];
|
|
382
|
+
return body.message_thread_id === undefined && isTargetChatScoped(body);
|
|
383
|
+
}
|
|
384
|
+
if ((apiMethod === "sendMessage" || apiMethod === "sendRichMessage") &&
|
|
385
|
+
hasTelegramBusDeliveryMarker(input.args[1], TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD)) {
|
|
386
|
+
return isDifferentTargetScoped(input.args[1]);
|
|
387
|
+
}
|
|
388
|
+
if (apiMethod === "deleteMessage" ||
|
|
389
|
+
apiMethod === "editMessageReplyMarkup" ||
|
|
390
|
+
apiMethod === "editMessageText") {
|
|
391
|
+
if (!isTargetMessageScoped(input.args[1]))
|
|
392
|
+
return false;
|
|
393
|
+
const body = input.args[1];
|
|
394
|
+
const messageId = typeof body.message_id === "number"
|
|
395
|
+
? body.message_id
|
|
396
|
+
: Number(body.message_id);
|
|
397
|
+
return input.isMessageOwned?.(target.chatId, messageId) === true;
|
|
398
|
+
}
|
|
399
|
+
return allowedCallMethods.has(apiMethod) && isTargetScoped(input.args[1]);
|
|
400
|
+
}
|
|
401
|
+
if (input.method === "callMultipart") {
|
|
402
|
+
const apiMethod = input.args[0];
|
|
403
|
+
return (typeof apiMethod === "string" &&
|
|
404
|
+
allowedMultipartMethods.has(apiMethod) &&
|
|
405
|
+
isTargetScoped(input.args[1]));
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
export function createTelegramFollowerApiCallAuthorizer(deps) {
|
|
410
|
+
return (input) => isTelegramFollowerApiCallAllowed({
|
|
411
|
+
...input,
|
|
412
|
+
isMessageOwned(chatId, messageId) {
|
|
413
|
+
return deps.isMessageOwned({
|
|
414
|
+
chatId,
|
|
415
|
+
messageId,
|
|
416
|
+
follower: input.follower,
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
export function createTelegramBusFollowerDeliveryIdentity(input) {
|
|
422
|
+
if (!input.recipientBindingKey ||
|
|
423
|
+
!Number.isSafeInteger(input.sourceUpdateId) ||
|
|
424
|
+
input.sourceUpdateId < 0 ||
|
|
425
|
+
(input.sourceClaim !== undefined &&
|
|
426
|
+
(!input.sourceClaim.acquisitionId ||
|
|
427
|
+
input.sourceClaim.acquisitionId.length > 256 ||
|
|
428
|
+
!input.sourceClaim.handoffId ||
|
|
429
|
+
input.sourceClaim.handoffId.length > 256))) {
|
|
430
|
+
throw new Error("Telegram follower delivery identity is incomplete.");
|
|
431
|
+
}
|
|
432
|
+
const deliveryId = createHash("sha256")
|
|
433
|
+
.update(JSON.stringify({
|
|
434
|
+
version: 1,
|
|
435
|
+
kind: input.kind,
|
|
436
|
+
recipientBindingKey: input.recipientBindingKey,
|
|
437
|
+
sourceUpdateId: input.sourceUpdateId,
|
|
438
|
+
}))
|
|
439
|
+
.digest("hex");
|
|
440
|
+
return {
|
|
441
|
+
deliveryId: `telegram-follower-v1-${deliveryId}`,
|
|
442
|
+
sourceUpdateId: input.sourceUpdateId,
|
|
443
|
+
recipientBindingKey: input.recipientBindingKey,
|
|
444
|
+
...(input.sourceRecoveryKey
|
|
445
|
+
? { sourceRecoveryKey: input.sourceRecoveryKey }
|
|
446
|
+
: {}),
|
|
447
|
+
...(input.sourceClaim ? { sourceClaim: { ...input.sourceClaim } } : {}),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
export function canUseTelegramBusInputCustodyReference(input) {
|
|
451
|
+
return (hasTelegramBusCapability(input.local, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE) &&
|
|
452
|
+
hasTelegramBusCapability(input.remote, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE));
|
|
453
|
+
}
|
|
454
|
+
export function createTelegramBusFollowerSourceReferenceDeliveryIdentity(input) {
|
|
455
|
+
if (!input.sourceRecoveryKey || !input.source.owner.handoffId)
|
|
456
|
+
throw new Error("Telegram follower source-reference delivery requires an accepted handoff.");
|
|
457
|
+
return createTelegramBusFollowerDeliveryIdentity({
|
|
458
|
+
kind: input.kind,
|
|
459
|
+
recipientBindingKey: input.recipientBindingKey,
|
|
460
|
+
sourceUpdateId: input.source.updateId,
|
|
461
|
+
sourceRecoveryKey: input.sourceRecoveryKey,
|
|
462
|
+
sourceClaim: {
|
|
463
|
+
acquisitionId: input.source.owner.acquisitionId,
|
|
464
|
+
handoffId: input.source.owner.handoffId,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
export function getTelegramBusEnvelopeTrafficClass(envelope) {
|
|
469
|
+
if (envelope.kind === "follower.register" ||
|
|
470
|
+
envelope.kind === "follower.restoreWorkspace" ||
|
|
471
|
+
envelope.kind === "bus.probe")
|
|
472
|
+
return "bootstrap";
|
|
473
|
+
if (envelope.kind === "bus.ack")
|
|
474
|
+
return "response";
|
|
475
|
+
return "generation-fenced";
|
|
476
|
+
}
|
|
477
|
+
export function createTelegramBusRequestId(input) {
|
|
478
|
+
return `${input.instanceId}:${input.sequence}`;
|
|
479
|
+
}
|
|
480
|
+
export function createTelegramBusRequestIdFactory(instanceId) {
|
|
481
|
+
let sequence = 0;
|
|
482
|
+
return () => {
|
|
483
|
+
sequence += 1;
|
|
484
|
+
return createTelegramBusRequestId({ instanceId, sequence });
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
export function encodeTelegramBusEnvelope(envelope) {
|
|
488
|
+
return `${JSON.stringify(envelope)}\n`;
|
|
489
|
+
}
|
|
490
|
+
export function parseTelegramBusEnvelope(line) {
|
|
491
|
+
let value;
|
|
492
|
+
try {
|
|
493
|
+
value = JSON.parse(line);
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
if (!isRecord(value))
|
|
499
|
+
return undefined;
|
|
500
|
+
const kind = value.kind;
|
|
501
|
+
const requestId = value.requestId;
|
|
502
|
+
if (typeof kind !== "string" || typeof requestId !== "string") {
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
let envelope;
|
|
506
|
+
switch (kind) {
|
|
507
|
+
case "follower.register":
|
|
508
|
+
case "follower.restoreWorkspace":
|
|
509
|
+
envelope = parseRegisterEnvelope(value, requestId, kind);
|
|
510
|
+
break;
|
|
511
|
+
case "follower.heartbeat":
|
|
512
|
+
case "follower.disconnect":
|
|
513
|
+
envelope = parseFollowerPresenceEnvelope(value, requestId, kind);
|
|
514
|
+
break;
|
|
515
|
+
case "follower.setThreadDisplayMode":
|
|
516
|
+
if (typeof value.instanceId === "string" &&
|
|
517
|
+
typeof value.registrationGeneration === "string" &&
|
|
518
|
+
(value.mode === "letters" ||
|
|
519
|
+
value.mode === "names" ||
|
|
520
|
+
value.mode === "directory-snake" ||
|
|
521
|
+
value.mode === "directory-title")) {
|
|
522
|
+
envelope = {
|
|
523
|
+
kind,
|
|
524
|
+
requestId,
|
|
525
|
+
instanceId: value.instanceId,
|
|
526
|
+
registrationGeneration: value.registrationGeneration,
|
|
527
|
+
mode: value.mode,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
case "follower.renameThread":
|
|
532
|
+
envelope = parseRenameThreadEnvelope(value, requestId);
|
|
533
|
+
break;
|
|
534
|
+
case "follower.resetThreadName": {
|
|
535
|
+
const target = parseTarget(value.target);
|
|
536
|
+
if (typeof value.instanceId === "string" &&
|
|
537
|
+
typeof value.registrationGeneration === "string" &&
|
|
538
|
+
typeof value.sentAtMs === "number" &&
|
|
539
|
+
target?.threadId !== undefined) {
|
|
540
|
+
envelope = {
|
|
541
|
+
kind,
|
|
542
|
+
requestId,
|
|
543
|
+
instanceId: value.instanceId,
|
|
544
|
+
registrationGeneration: value.registrationGeneration,
|
|
545
|
+
target: { chatId: target.chatId, threadId: target.threadId },
|
|
546
|
+
sentAtMs: value.sentAtMs,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case "follower.publishSessionReplacement":
|
|
552
|
+
case "follower.settleSessionReplacement": {
|
|
553
|
+
const intent = normalizeTelegramSessionReplacementIntent(value.intent);
|
|
554
|
+
if (typeof value.instanceId === "string" &&
|
|
555
|
+
typeof value.registrationGeneration === "string" &&
|
|
556
|
+
typeof value.sentAtMs === "number" &&
|
|
557
|
+
intent) {
|
|
558
|
+
envelope = {
|
|
559
|
+
kind,
|
|
560
|
+
requestId,
|
|
561
|
+
instanceId: value.instanceId,
|
|
562
|
+
registrationGeneration: value.registrationGeneration,
|
|
563
|
+
intent,
|
|
564
|
+
sentAtMs: value.sentAtMs,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "leader.forwardCallback":
|
|
570
|
+
envelope = parseForwardCallbackEnvelope(value, requestId);
|
|
571
|
+
break;
|
|
572
|
+
case "leader.forwardReaction":
|
|
573
|
+
envelope = parseForwardReactionEnvelope(value, requestId);
|
|
574
|
+
break;
|
|
575
|
+
case "leader.offerInputCustodyHandoff":
|
|
576
|
+
envelope = parseOfferInputCustodyHandoffEnvelope(value, requestId);
|
|
577
|
+
break;
|
|
578
|
+
case "leader.wakeInputCustody":
|
|
579
|
+
envelope = parseWakeInputCustodyEnvelope(value, requestId);
|
|
580
|
+
break;
|
|
581
|
+
case "leader.forwardMessage":
|
|
582
|
+
envelope = parseForwardMessageEnvelope(value, requestId, "leader.forwardMessage");
|
|
583
|
+
break;
|
|
584
|
+
case "leader.forwardEditedMessage":
|
|
585
|
+
envelope = parseForwardMessageEnvelope(value, requestId, "leader.forwardEditedMessage");
|
|
586
|
+
break;
|
|
587
|
+
case "leader.prepareLiveRebind": {
|
|
588
|
+
const selectedCommand = parseSelectedCommandInput(value.selectedCommand);
|
|
589
|
+
if (!hasOnlyWireKeys(value, [
|
|
590
|
+
"kind",
|
|
591
|
+
"requestId",
|
|
592
|
+
"auth",
|
|
593
|
+
"recipientInstanceId",
|
|
594
|
+
"recipientRegistrationGeneration",
|
|
595
|
+
"recipientSessionId",
|
|
596
|
+
"recipientBindingKey",
|
|
597
|
+
"operationId",
|
|
598
|
+
"updates",
|
|
599
|
+
"selectedCommand",
|
|
600
|
+
"sentAtMs",
|
|
601
|
+
]) ||
|
|
602
|
+
(Object.hasOwn(value, "selectedCommand") &&
|
|
603
|
+
(!selectedCommand ||
|
|
604
|
+
!Array.isArray(value.updates) ||
|
|
605
|
+
value.updates.length !== 1)))
|
|
606
|
+
break;
|
|
607
|
+
const recipient = parseLiveRebindRecipient(value);
|
|
608
|
+
if (recipient &&
|
|
609
|
+
Array.isArray(value.updates) &&
|
|
610
|
+
value.updates.every(isRecord) &&
|
|
611
|
+
isUniqueSourceUpdateIds(value.updates.map((update) => update.update_id)) &&
|
|
612
|
+
typeof value.sentAtMs === "number" &&
|
|
613
|
+
Number.isFinite(value.sentAtMs)) {
|
|
614
|
+
envelope = {
|
|
615
|
+
kind: "leader.prepareLiveRebind",
|
|
616
|
+
requestId,
|
|
617
|
+
...recipient,
|
|
618
|
+
updates: value.updates,
|
|
619
|
+
sentAtMs: value.sentAtMs,
|
|
620
|
+
...(selectedCommand ? { selectedCommand } : {}),
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case "leader.applyLiveRebind":
|
|
626
|
+
case "leader.settleLiveRebind": {
|
|
627
|
+
const selectedCommand = parseSelectedCommandInput(value.selectedCommand), preparedSource = parsePreparedCommandSource(value.preparedSource);
|
|
628
|
+
if (!hasOnlyWireKeys(value, [
|
|
629
|
+
"kind",
|
|
630
|
+
"requestId",
|
|
631
|
+
"auth",
|
|
632
|
+
"recipientInstanceId",
|
|
633
|
+
"recipientRegistrationGeneration",
|
|
634
|
+
"recipientSessionId",
|
|
635
|
+
"recipientBindingKey",
|
|
636
|
+
"operationId",
|
|
637
|
+
"sourceUpdateIds",
|
|
638
|
+
"mode",
|
|
639
|
+
"oldTarget",
|
|
640
|
+
"selectedCommand",
|
|
641
|
+
"preparedSource",
|
|
642
|
+
"sentAtMs",
|
|
643
|
+
]) ||
|
|
644
|
+
(Object.hasOwn(value, "preparedSource") && !selectedCommand) ||
|
|
645
|
+
(value.mode === "observe-command" &&
|
|
646
|
+
(!selectedCommand || Object.hasOwn(value, "oldTarget"))))
|
|
647
|
+
break;
|
|
648
|
+
if (Object.hasOwn(value, "selectedCommand") &&
|
|
649
|
+
(!selectedCommand ||
|
|
650
|
+
!Array.isArray(value.sourceUpdateIds) ||
|
|
651
|
+
value.sourceUpdateIds.length !== 1 ||
|
|
652
|
+
!preparedSource ||
|
|
653
|
+
preparedSource.updateId !== value.sourceUpdateIds[0] ||
|
|
654
|
+
preparedSource.journalBindingKey !== value.recipientBindingKey))
|
|
655
|
+
break;
|
|
656
|
+
const recipient = parseLiveRebindRecipient(value);
|
|
657
|
+
if (recipient &&
|
|
658
|
+
(value.kind === "leader.applyLiveRebind"
|
|
659
|
+
? value.mode === "apply" || value.mode === "inspect"
|
|
660
|
+
: value.mode === "release" ||
|
|
661
|
+
value.mode === "discard" ||
|
|
662
|
+
value.mode === "observe-command" ||
|
|
663
|
+
(value.mode === "observe" &&
|
|
664
|
+
!!parseThreadTarget(value.oldTarget))) &&
|
|
665
|
+
isUniqueSourceUpdateIds(value.sourceUpdateIds) &&
|
|
666
|
+
typeof value.sentAtMs === "number" &&
|
|
667
|
+
Number.isFinite(value.sentAtMs)) {
|
|
668
|
+
envelope = {
|
|
669
|
+
kind: value.kind,
|
|
670
|
+
requestId,
|
|
671
|
+
...recipient,
|
|
672
|
+
sourceUpdateIds: value.sourceUpdateIds,
|
|
673
|
+
mode: value.mode,
|
|
674
|
+
sentAtMs: value.sentAtMs,
|
|
675
|
+
...(value.mode === "observe"
|
|
676
|
+
? { oldTarget: parseThreadTarget(value.oldTarget) }
|
|
677
|
+
: {}),
|
|
678
|
+
...(selectedCommand ? { selectedCommand, preparedSource } : {}),
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
case "leader.workspaceRestore":
|
|
684
|
+
if (typeof value.recipientInstanceId === "string" &&
|
|
685
|
+
value.recipientInstanceId &&
|
|
686
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
687
|
+
value.recipientRegistrationGeneration &&
|
|
688
|
+
typeof value.operationId === "string" &&
|
|
689
|
+
value.operationId.length > 0 &&
|
|
690
|
+
value.operationId.length <= 128 &&
|
|
691
|
+
(value.mode === "apply" || value.mode === "inspect") &&
|
|
692
|
+
typeof value.sentAtMs === "number" &&
|
|
693
|
+
Number.isFinite(value.sentAtMs)) {
|
|
694
|
+
envelope = {
|
|
695
|
+
kind: "leader.workspaceRestore",
|
|
696
|
+
requestId,
|
|
697
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
698
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
699
|
+
operationId: value.operationId,
|
|
700
|
+
mode: value.mode,
|
|
701
|
+
sentAtMs: value.sentAtMs,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
case "leader.offerQueueHandoff":
|
|
706
|
+
envelope = parseQueueHandoffEnvelope(value, requestId, "leader.offerQueueHandoff");
|
|
707
|
+
break;
|
|
708
|
+
case "follower.offerQueueHandoff":
|
|
709
|
+
envelope = parseQueueHandoffEnvelope(value, requestId, "follower.offerQueueHandoff");
|
|
710
|
+
break;
|
|
711
|
+
case "follower.resolveAgentTarget":
|
|
712
|
+
envelope = parseResolveAgentTargetEnvelope(value, requestId);
|
|
713
|
+
break;
|
|
714
|
+
case "follower.routeAgentMessage":
|
|
715
|
+
envelope = parseRouteAgentMessageEnvelope(value, requestId);
|
|
716
|
+
break;
|
|
717
|
+
case "follower.deliverSelectedMenu":
|
|
718
|
+
envelope = parseSelectedMenuDeliveryEnvelope(value, requestId);
|
|
719
|
+
break;
|
|
720
|
+
case "follower.callApi":
|
|
721
|
+
envelope = parseCallApiEnvelope(value, requestId);
|
|
722
|
+
break;
|
|
723
|
+
case "bus.probe":
|
|
724
|
+
envelope = { kind, requestId };
|
|
725
|
+
break;
|
|
726
|
+
case "bus.ack":
|
|
727
|
+
envelope = parseAckEnvelope(value, requestId);
|
|
728
|
+
break;
|
|
729
|
+
default:
|
|
730
|
+
return undefined;
|
|
731
|
+
}
|
|
732
|
+
const auth = value.auth;
|
|
733
|
+
if (envelope && typeof auth === "string")
|
|
734
|
+
envelope.auth = auth;
|
|
735
|
+
return envelope;
|
|
736
|
+
}
|
|
737
|
+
export function probeTelegramBusLeader(input) {
|
|
738
|
+
const timeoutMs = input.timeoutMs ?? TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
|
|
739
|
+
return new Promise((resolve) => {
|
|
740
|
+
let settled = false;
|
|
741
|
+
let socket;
|
|
742
|
+
const finish = (result) => {
|
|
743
|
+
if (settled)
|
|
744
|
+
return;
|
|
745
|
+
settled = true;
|
|
746
|
+
clearTimeout(timer);
|
|
747
|
+
socket?.destroy();
|
|
748
|
+
resolve(result);
|
|
749
|
+
};
|
|
750
|
+
const timer = setTimeout(() => finish("silent"), timeoutMs);
|
|
751
|
+
let connected = false;
|
|
752
|
+
try {
|
|
753
|
+
socket = createConnection(resolveTelegramBusSocketPath(input.socketPath));
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
finish("unknown");
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
socket.once("connect", () => {
|
|
760
|
+
connected = true;
|
|
761
|
+
socket.write(encodeTelegramBusEnvelope({
|
|
762
|
+
kind: "bus.probe",
|
|
763
|
+
requestId: `probe:${randomBytes(8).toString("hex")}`,
|
|
764
|
+
...(input.secret ? { auth: input.secret } : {}),
|
|
765
|
+
}));
|
|
766
|
+
});
|
|
767
|
+
socket.once("data", () => finish("responsive"));
|
|
768
|
+
socket.once("end", () => finish("responsive"));
|
|
769
|
+
socket.once("error", (error) => {
|
|
770
|
+
const code = error.code;
|
|
771
|
+
finish(!connected && code === "ECONNREFUSED" ? "unreachable" : "unknown");
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Takeover evidence replacing the retired file heartbeat: a full silent window, or an unreachable endpoint
|
|
777
|
+
* confirmed again after one more window (a just-started leader binds its socket well within it). The owner must
|
|
778
|
+
* stay the same throughout; the lock acquisition then CAS-checks that exact owner.
|
|
779
|
+
*/
|
|
780
|
+
export async function proveTelegramBusLeaderUnresponsive(input) {
|
|
781
|
+
const first = await input.probe();
|
|
782
|
+
if (first === "silent")
|
|
783
|
+
return input.isSameOwner();
|
|
784
|
+
if (first !== "unreachable")
|
|
785
|
+
return false;
|
|
786
|
+
await input.sleep(input.windowMs ?? TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS);
|
|
787
|
+
if (!input.isSameOwner())
|
|
788
|
+
return false;
|
|
789
|
+
const second = await input.probe();
|
|
790
|
+
return (second === "silent" || second === "unreachable") && input.isSameOwner();
|
|
791
|
+
}
|
|
792
|
+
/** Composition-ready takeover proof for one profile's leader lock and endpoint. */
|
|
793
|
+
export function createTelegramBusLeaderUnresponsivenessProof(deps) {
|
|
794
|
+
const probe = deps.probe ?? probeTelegramBusLeader;
|
|
795
|
+
const sleep = deps.sleep ??
|
|
796
|
+
((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
797
|
+
return (owner) => proveTelegramBusLeaderUnresponsive({
|
|
798
|
+
probe: () => probe({
|
|
799
|
+
socketPath: owner.busSocketPath ?? deps.getLeaderSocketPath(),
|
|
800
|
+
secret: owner.busSecret,
|
|
801
|
+
}),
|
|
802
|
+
isSameOwner: () => {
|
|
803
|
+
const state = deps.getLeaderState();
|
|
804
|
+
return (state.kind === "active-elsewhere" &&
|
|
805
|
+
isSameTelegramLockOwner(state.lock, owner));
|
|
806
|
+
},
|
|
807
|
+
sleep,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
const TELEGRAM_ACTIVE_LOCAL_SERVERS = Symbol.for("@llblab/pi-telegram/active-local-servers");
|
|
811
|
+
function getActiveTelegramBusLocalServers() {
|
|
812
|
+
const root = globalThis;
|
|
813
|
+
return (root[TELEGRAM_ACTIVE_LOCAL_SERVERS] ??= new Map());
|
|
814
|
+
}
|
|
815
|
+
export function resolveTelegramBusSocketPath(source, platform = getPlatform()) {
|
|
816
|
+
const endpoint = typeof source === "function" ? source() : source;
|
|
817
|
+
if (platform === "win32") {
|
|
818
|
+
if (isTelegramBusPipePath(endpoint))
|
|
819
|
+
return endpoint;
|
|
820
|
+
return getTelegramBusPipePath({
|
|
821
|
+
agentDir: dirname(endpoint),
|
|
822
|
+
scope: basename(endpoint),
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
const ownerScope = process.getuid?.() ?? "user";
|
|
826
|
+
const fallbackDir = join(tmpdir(), `pi-telegram-${ownerScope}`);
|
|
827
|
+
if (dirname(endpoint) === fallbackDir &&
|
|
828
|
+
/^[0-9a-f]{16}\.sock$/u.test(basename(endpoint))) {
|
|
829
|
+
return endpoint;
|
|
830
|
+
}
|
|
831
|
+
if (Buffer.byteLength(endpoint) <= TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES) {
|
|
832
|
+
return endpoint;
|
|
833
|
+
}
|
|
834
|
+
const digest = createHash("sha256")
|
|
835
|
+
.update(endpoint)
|
|
836
|
+
.digest("hex")
|
|
837
|
+
.slice(0, 16);
|
|
838
|
+
return join(fallbackDir, `${digest}.sock`);
|
|
839
|
+
}
|
|
840
|
+
/** Local IPC issuance proof only: this callback/error never enters the envelope or certifies a remote API effect. */
|
|
841
|
+
export class TelegramBusLocalAuthorityError extends Error {
|
|
842
|
+
requestIssued;
|
|
843
|
+
constructor(requestIssued, reason) {
|
|
844
|
+
super(reason
|
|
845
|
+
? `Telegram bus local authority is unavailable: ${reason}`
|
|
846
|
+
: "Telegram bus local authority is unavailable.");
|
|
847
|
+
this.name = "TelegramBusLocalAuthorityError";
|
|
848
|
+
this.requestIssued = requestIssued;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
export function isTelegramBusForwardOwnershipCurrent(expected, current) {
|
|
852
|
+
return Boolean(current &&
|
|
853
|
+
current.instanceId === expected.instanceId &&
|
|
854
|
+
current.ownerGeneration === expected.ownerGeneration &&
|
|
855
|
+
current.recipientBindingKey === expected.recipientBindingKey &&
|
|
856
|
+
JSON.stringify(current.protocolIdentity) ===
|
|
857
|
+
JSON.stringify(expected.protocolIdentity));
|
|
858
|
+
}
|
|
859
|
+
function getTelegramBusForwardSourceUpdateId(value) {
|
|
860
|
+
if (!isRecord(value))
|
|
861
|
+
return undefined;
|
|
862
|
+
const updateId = value.pi_telegram_source_update_id;
|
|
863
|
+
return Number.isSafeInteger(updateId) && updateId >= 0
|
|
864
|
+
? updateId
|
|
865
|
+
: undefined;
|
|
866
|
+
}
|
|
867
|
+
function createTelegramBusForwardDelivery(kind, sourceUpdateId, recipientBindingKey) {
|
|
868
|
+
return createTelegramBusFollowerDeliveryIdentity({
|
|
869
|
+
kind,
|
|
870
|
+
recipientBindingKey,
|
|
871
|
+
sourceUpdateId,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Leader-to-follower forwards and control replies share the stale-liveness window: a follower doing journal work or stalled
|
|
876
|
+
* by an ordinary multi-second Pi/TUI turn, or a slower Windows named pipe, is not a lost recipient.
|
|
877
|
+
*/
|
|
878
|
+
const TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS = TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
|
|
879
|
+
export function createTelegramBusForeignOwnedUpdateForwarder(deps) {
|
|
880
|
+
const getNowMs = deps.getNowMs ?? Date.now;
|
|
881
|
+
const reject = (input) => {
|
|
882
|
+
const settlement = {
|
|
883
|
+
status: input.status,
|
|
884
|
+
failureClass: input.failureClass,
|
|
885
|
+
message: input.message,
|
|
886
|
+
...(input.delivery ? { delivery: input.delivery } : {}),
|
|
887
|
+
...(input.sourceUpdateId !== undefined
|
|
888
|
+
? { sourceUpdateId: input.sourceUpdateId }
|
|
889
|
+
: {}),
|
|
890
|
+
};
|
|
891
|
+
deps.recordRuntimeEvent?.("bus", input.message, {
|
|
892
|
+
phase: "foreign-update-forward-rejected",
|
|
893
|
+
settlement: input.status,
|
|
894
|
+
failureClass: input.failureClass,
|
|
895
|
+
envelopeKind: input.envelopeKind,
|
|
896
|
+
recipientInstanceId: input.ownership.instanceId,
|
|
897
|
+
deliveryId: input.delivery?.deliveryId,
|
|
898
|
+
sourceUpdateId: input.delivery?.sourceUpdateId ?? input.sourceUpdateId,
|
|
899
|
+
});
|
|
900
|
+
return settlement;
|
|
901
|
+
};
|
|
902
|
+
const prepare = (kind, value, ownership) => {
|
|
903
|
+
const sourceUpdateId = getTelegramBusForwardSourceUpdateId(value);
|
|
904
|
+
if (sourceUpdateId === undefined) {
|
|
905
|
+
return {
|
|
906
|
+
settlement: reject({
|
|
907
|
+
status: "terminal-rejected",
|
|
908
|
+
failureClass: "source-update-identity-missing",
|
|
909
|
+
message: "Forwarded Telegram update has no durable source identity.",
|
|
910
|
+
envelopeKind: kind,
|
|
911
|
+
ownership,
|
|
912
|
+
}),
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
if (!ownership.recipientBindingKey) {
|
|
916
|
+
return {
|
|
917
|
+
settlement: reject({
|
|
918
|
+
status: "terminal-rejected",
|
|
919
|
+
failureClass: "recipient-binding-missing",
|
|
920
|
+
message: "Forwarded Telegram update has no stable recipient binding.",
|
|
921
|
+
envelopeKind: kind,
|
|
922
|
+
ownership,
|
|
923
|
+
sourceUpdateId,
|
|
924
|
+
}),
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
const sourceReference = canUseTelegramBusInputCustodyReference({
|
|
928
|
+
local: deps.localProtocolIdentity,
|
|
929
|
+
remote: ownership.protocolIdentity,
|
|
930
|
+
});
|
|
931
|
+
const reference = sourceReference
|
|
932
|
+
? deps.resolveInputCustodyReference?.({
|
|
933
|
+
sourceUpdateId,
|
|
934
|
+
recipientBindingKey: ownership.recipientBindingKey,
|
|
935
|
+
})
|
|
936
|
+
: undefined;
|
|
937
|
+
if (sourceReference && !reference)
|
|
938
|
+
return {
|
|
939
|
+
settlement: reject({
|
|
940
|
+
status: "retryable",
|
|
941
|
+
failureClass: "source-reference-missing",
|
|
942
|
+
message: "Forwarded Telegram update has no exact custody reference.",
|
|
943
|
+
envelopeKind: kind,
|
|
944
|
+
ownership,
|
|
945
|
+
sourceUpdateId,
|
|
946
|
+
}),
|
|
947
|
+
};
|
|
948
|
+
const delivery = reference
|
|
949
|
+
? createTelegramBusFollowerSourceReferenceDeliveryIdentity({
|
|
950
|
+
kind: "leader.wakeInputCustody",
|
|
951
|
+
recipientBindingKey: ownership.recipientBindingKey,
|
|
952
|
+
sourceRecoveryKey: reference.sourceRecoveryKey,
|
|
953
|
+
source: reference.source,
|
|
954
|
+
})
|
|
955
|
+
: createTelegramBusForwardDelivery(kind, sourceUpdateId, ownership.recipientBindingKey);
|
|
956
|
+
if (!ownership.ownerGeneration) {
|
|
957
|
+
return {
|
|
958
|
+
settlement: reject({
|
|
959
|
+
status: "retryable",
|
|
960
|
+
failureClass: "recipient-generation-missing",
|
|
961
|
+
message: "Forwarded Telegram update has no live recipient generation.",
|
|
962
|
+
envelopeKind: kind,
|
|
963
|
+
ownership,
|
|
964
|
+
delivery,
|
|
965
|
+
}),
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
delivery,
|
|
970
|
+
recipientRegistrationGeneration: ownership.ownerGeneration,
|
|
971
|
+
sourceReference,
|
|
972
|
+
};
|
|
973
|
+
};
|
|
974
|
+
const send = async (envelope, ownership) => {
|
|
975
|
+
if (deps.validateForwardOwnership &&
|
|
976
|
+
!deps.validateForwardOwnership(ownership))
|
|
977
|
+
return reject({
|
|
978
|
+
status: "retryable",
|
|
979
|
+
failureClass: "recipient-ownership-stale",
|
|
980
|
+
message: "Telegram follower ownership changed before forwarding.",
|
|
981
|
+
envelopeKind: envelope.kind,
|
|
982
|
+
ownership,
|
|
983
|
+
delivery: envelope.delivery,
|
|
984
|
+
});
|
|
985
|
+
if (deps.getAuthSecret)
|
|
986
|
+
envelope.auth = deps.getAuthSecret();
|
|
987
|
+
const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
|
|
988
|
+
let response;
|
|
989
|
+
try {
|
|
990
|
+
response = await sendTelegramBusLocalEnvelope({
|
|
991
|
+
socketPath,
|
|
992
|
+
envelope,
|
|
993
|
+
timeoutMs: deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS,
|
|
994
|
+
retry: getTelegramBusTransportRetryPolicy({
|
|
995
|
+
endpoint: socketPath,
|
|
996
|
+
operation: "operation",
|
|
997
|
+
}),
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
return reject({
|
|
1002
|
+
status: "retryable",
|
|
1003
|
+
failureClass: "transport-failed",
|
|
1004
|
+
message: error instanceof Error
|
|
1005
|
+
? error.message
|
|
1006
|
+
: "Telegram follower forwarding transport failed.",
|
|
1007
|
+
envelopeKind: envelope.kind,
|
|
1008
|
+
ownership,
|
|
1009
|
+
delivery: envelope.delivery,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
if (response?.kind !== "bus.ack") {
|
|
1013
|
+
return reject({
|
|
1014
|
+
status: "retryable",
|
|
1015
|
+
failureClass: "acknowledgement-missing",
|
|
1016
|
+
message: "Follower returned no forwarding acknowledgement.",
|
|
1017
|
+
envelopeKind: envelope.kind,
|
|
1018
|
+
ownership,
|
|
1019
|
+
delivery: envelope.delivery,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
if (response.requestId !== envelope.requestId) {
|
|
1023
|
+
return reject({
|
|
1024
|
+
status: "terminal-rejected",
|
|
1025
|
+
failureClass: "acknowledgement-mismatched",
|
|
1026
|
+
message: "Follower returned a mismatched forwarding acknowledgement.",
|
|
1027
|
+
envelopeKind: envelope.kind,
|
|
1028
|
+
ownership,
|
|
1029
|
+
delivery: envelope.delivery,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
if (!response.ok) {
|
|
1033
|
+
return reject({
|
|
1034
|
+
status: "retryable",
|
|
1035
|
+
failureClass: "acknowledgement-rejected",
|
|
1036
|
+
message: response.message ?? "Follower rejected forwarded Telegram update.",
|
|
1037
|
+
envelopeKind: envelope.kind,
|
|
1038
|
+
ownership,
|
|
1039
|
+
delivery: envelope.delivery,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
const receipt = response.result;
|
|
1043
|
+
if (!isRecord(receipt) ||
|
|
1044
|
+
typeof receipt.deliveryId !== "string" ||
|
|
1045
|
+
!Number.isSafeInteger(receipt.sourceUpdateId)) {
|
|
1046
|
+
return reject({
|
|
1047
|
+
status: "terminal-rejected",
|
|
1048
|
+
failureClass: "durable-receipt-missing",
|
|
1049
|
+
message: "Follower acknowledgement omitted the durable receipt.",
|
|
1050
|
+
envelopeKind: envelope.kind,
|
|
1051
|
+
ownership,
|
|
1052
|
+
delivery: envelope.delivery,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
if (receipt.deliveryId !== envelope.delivery.deliveryId ||
|
|
1056
|
+
receipt.sourceUpdateId !== envelope.delivery.sourceUpdateId) {
|
|
1057
|
+
return reject({
|
|
1058
|
+
status: "terminal-rejected",
|
|
1059
|
+
failureClass: "durable-receipt-mismatched",
|
|
1060
|
+
message: "Follower acknowledgement returned a mismatched durable receipt.",
|
|
1061
|
+
envelopeKind: envelope.kind,
|
|
1062
|
+
ownership,
|
|
1063
|
+
delivery: envelope.delivery,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return { status: "accepted", delivery: envelope.delivery };
|
|
1067
|
+
};
|
|
1068
|
+
// A recipient already holding the source reference is only woken; it never receives a second payload copy.
|
|
1069
|
+
const forward = (kind, value, ownership, build) => {
|
|
1070
|
+
const prepared = prepare(kind, value, ownership);
|
|
1071
|
+
if ("settlement" in prepared)
|
|
1072
|
+
return Promise.resolve(prepared.settlement);
|
|
1073
|
+
const base = {
|
|
1074
|
+
requestId: deps.createRequestId(),
|
|
1075
|
+
recipientInstanceId: ownership.instanceId,
|
|
1076
|
+
recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
|
|
1077
|
+
delivery: prepared.delivery,
|
|
1078
|
+
};
|
|
1079
|
+
return send(prepared.sourceReference
|
|
1080
|
+
? { kind: "leader.wakeInputCustody", ...base, sentAtMs: getNowMs() }
|
|
1081
|
+
: build(base, getNowMs()), ownership);
|
|
1082
|
+
};
|
|
1083
|
+
return {
|
|
1084
|
+
forwardCallback: ({ query, ownership }) => forward("leader.forwardCallback", query, ownership, (base, sentAtMs) => ({
|
|
1085
|
+
kind: "leader.forwardCallback",
|
|
1086
|
+
...base,
|
|
1087
|
+
query,
|
|
1088
|
+
sentAtMs,
|
|
1089
|
+
})),
|
|
1090
|
+
forwardReaction: ({ reactionUpdate, ownership }) => forward("leader.forwardReaction", reactionUpdate, ownership, (base, sentAtMs) => ({
|
|
1091
|
+
kind: "leader.forwardReaction",
|
|
1092
|
+
...base,
|
|
1093
|
+
reactionUpdate,
|
|
1094
|
+
sentAtMs,
|
|
1095
|
+
})),
|
|
1096
|
+
forwardMessage: ({ message, ownership }) => forward("leader.forwardMessage", message, ownership, (base, sentAtMs) => {
|
|
1097
|
+
const forwardCommentBatchPosition = deps.getForwardCommentBatchPosition?.(message);
|
|
1098
|
+
return {
|
|
1099
|
+
kind: "leader.forwardMessage",
|
|
1100
|
+
...base,
|
|
1101
|
+
message,
|
|
1102
|
+
...(forwardCommentBatchPosition !== undefined
|
|
1103
|
+
? { forwardCommentBatchPosition }
|
|
1104
|
+
: {}),
|
|
1105
|
+
sentAtMs,
|
|
1106
|
+
};
|
|
1107
|
+
}),
|
|
1108
|
+
forwardEditedMessage: ({ message, ownership }) => forward("leader.forwardEditedMessage", message, ownership, (base, sentAtMs) => ({
|
|
1109
|
+
kind: "leader.forwardEditedMessage",
|
|
1110
|
+
...base,
|
|
1111
|
+
message,
|
|
1112
|
+
sentAtMs,
|
|
1113
|
+
})),
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
export function listTelegramBusLiveThreadTargets(input) {
|
|
1117
|
+
const targets = [];
|
|
1118
|
+
if (input.leaderTarget?.threadId !== undefined) {
|
|
1119
|
+
targets.push(input.leaderTarget);
|
|
1120
|
+
}
|
|
1121
|
+
for (const follower of input.followers) {
|
|
1122
|
+
if (follower.target?.threadId !== undefined)
|
|
1123
|
+
targets.push(follower.target);
|
|
1124
|
+
}
|
|
1125
|
+
return targets;
|
|
1126
|
+
}
|
|
1127
|
+
/** Caller owns the durable one-shot apply grant. Inspections never grant another apply. */
|
|
1128
|
+
export function createTelegramBusWorkspaceRestoreController(deps) {
|
|
1129
|
+
return async (input) => {
|
|
1130
|
+
const isCurrent = input.isCurrent.bind(input);
|
|
1131
|
+
if (!isCurrent())
|
|
1132
|
+
return undefined;
|
|
1133
|
+
const follower = deps.getFollower(input.instanceId);
|
|
1134
|
+
const secret = deps.getAuthSecret();
|
|
1135
|
+
if (!secret ||
|
|
1136
|
+
!follower?.registrationGeneration ||
|
|
1137
|
+
!follower.busSocketPath ||
|
|
1138
|
+
follower.sessionId !== input.sessionId ||
|
|
1139
|
+
follower.slot !== input.slot ||
|
|
1140
|
+
!hasTelegramBusSharedCapabilities(deps.localProtocolIdentity, follower.protocol, [TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE]))
|
|
1141
|
+
return undefined;
|
|
1142
|
+
const captured = structuredClone(follower);
|
|
1143
|
+
const expected = {
|
|
1144
|
+
operationId: input.operationId,
|
|
1145
|
+
sessionId: input.sessionId,
|
|
1146
|
+
slot: input.slot,
|
|
1147
|
+
target: { ...input.target },
|
|
1148
|
+
oldTarget: { ...input.oldTarget },
|
|
1149
|
+
mode: input.mode,
|
|
1150
|
+
};
|
|
1151
|
+
const current = () => {
|
|
1152
|
+
if (!isCurrent() ||
|
|
1153
|
+
deps.getAuthSecret() !== secret ||
|
|
1154
|
+
!hasTelegramBusCapability(deps.localProtocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE))
|
|
1155
|
+
return false;
|
|
1156
|
+
const live = deps.getFollower(captured.instanceId);
|
|
1157
|
+
return (!!live &&
|
|
1158
|
+
live.registrationGeneration === captured.registrationGeneration &&
|
|
1159
|
+
live.sessionId === captured.sessionId &&
|
|
1160
|
+
live.cwd === captured.cwd &&
|
|
1161
|
+
live.slot === captured.slot &&
|
|
1162
|
+
live.busSocketPath === captured.busSocketPath &&
|
|
1163
|
+
live.target?.chatId === captured.target?.chatId &&
|
|
1164
|
+
live.target?.threadId === captured.target?.threadId &&
|
|
1165
|
+
hasTelegramBusCapability(live.protocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
|
|
1166
|
+
getTelegramBusProtocolCompatibility({
|
|
1167
|
+
local: deps.localProtocolIdentity,
|
|
1168
|
+
remote: live.protocol,
|
|
1169
|
+
}).compatible);
|
|
1170
|
+
};
|
|
1171
|
+
if (!current())
|
|
1172
|
+
return undefined;
|
|
1173
|
+
const requestId = deps.createRequestId();
|
|
1174
|
+
const response = await sendTelegramBusLocalEnvelope({
|
|
1175
|
+
socketPath: captured.busSocketPath,
|
|
1176
|
+
timeoutMs: deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS,
|
|
1177
|
+
retry: { attempts: 1, delayMs: 0 },
|
|
1178
|
+
envelope: {
|
|
1179
|
+
kind: "leader.workspaceRestore",
|
|
1180
|
+
requestId,
|
|
1181
|
+
recipientInstanceId: captured.instanceId,
|
|
1182
|
+
recipientRegistrationGeneration: captured.registrationGeneration,
|
|
1183
|
+
operationId: expected.operationId,
|
|
1184
|
+
mode: expected.mode,
|
|
1185
|
+
auth: secret,
|
|
1186
|
+
sentAtMs: Date.now(),
|
|
1187
|
+
},
|
|
1188
|
+
});
|
|
1189
|
+
if (!current() ||
|
|
1190
|
+
response?.kind !== "bus.ack" ||
|
|
1191
|
+
response.requestId !== requestId ||
|
|
1192
|
+
response.ok !== true ||
|
|
1193
|
+
!response.result ||
|
|
1194
|
+
typeof response.result !== "object")
|
|
1195
|
+
return undefined;
|
|
1196
|
+
const result = response.result;
|
|
1197
|
+
const recipient = result.recipient;
|
|
1198
|
+
const target = parseThreadTarget(result.target);
|
|
1199
|
+
const wanted = result.ready === true ? expected.target : expected.oldTarget;
|
|
1200
|
+
if (result.operationId !== expected.operationId ||
|
|
1201
|
+
typeof result.ready !== "boolean" ||
|
|
1202
|
+
result.slot !== expected.slot ||
|
|
1203
|
+
!target ||
|
|
1204
|
+
target.chatId !== wanted.chatId ||
|
|
1205
|
+
target.threadId !== wanted.threadId ||
|
|
1206
|
+
recipient?.kind !== "follower" ||
|
|
1207
|
+
recipient.instanceId !== captured.instanceId ||
|
|
1208
|
+
recipient.sessionId !== expected.sessionId ||
|
|
1209
|
+
recipient.generation !== captured.registrationGeneration)
|
|
1210
|
+
return undefined;
|
|
1211
|
+
return {
|
|
1212
|
+
operationId: expected.operationId,
|
|
1213
|
+
recipient: {
|
|
1214
|
+
kind: "follower",
|
|
1215
|
+
instanceId: captured.instanceId,
|
|
1216
|
+
sessionId: expected.sessionId,
|
|
1217
|
+
generation: captured.registrationGeneration,
|
|
1218
|
+
},
|
|
1219
|
+
target,
|
|
1220
|
+
slot: expected.slot,
|
|
1221
|
+
ready: result.ready,
|
|
1222
|
+
};
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
/** Each capability has its own effect boundary; transport retries never replay save/dispatch/disposal. */
|
|
1226
|
+
export function createTelegramBusLiveRebindController(deps) {
|
|
1227
|
+
return async (input) => {
|
|
1228
|
+
const isCurrent = input.isCurrent.bind(input), localProtocol = structuredClone(deps.localProtocolIdentity);
|
|
1229
|
+
const selectedCommand = parseSelectedCommandInput(input.selectedCommand), held = selectedCommand, preparedSource = parsePreparedCommandSource(input.preparedSource);
|
|
1230
|
+
if (!hasOnlyWireKeys(input, [
|
|
1231
|
+
"operationId",
|
|
1232
|
+
"instanceId",
|
|
1233
|
+
"sessionId",
|
|
1234
|
+
"recipientBindingKey",
|
|
1235
|
+
"isCurrent",
|
|
1236
|
+
"selectedCommand",
|
|
1237
|
+
"preparedSource",
|
|
1238
|
+
"updates",
|
|
1239
|
+
"mode",
|
|
1240
|
+
"sourceUpdateIds",
|
|
1241
|
+
"slot",
|
|
1242
|
+
"target",
|
|
1243
|
+
"oldTarget",
|
|
1244
|
+
]) ||
|
|
1245
|
+
("mode" in input &&
|
|
1246
|
+
(![
|
|
1247
|
+
"apply",
|
|
1248
|
+
"inspect",
|
|
1249
|
+
"release",
|
|
1250
|
+
"discard",
|
|
1251
|
+
"observe",
|
|
1252
|
+
"observe-command",
|
|
1253
|
+
].includes(input.mode) ||
|
|
1254
|
+
(input.mode === "observe-command" && !selectedCommand))))
|
|
1255
|
+
return undefined;
|
|
1256
|
+
if ((input.preparedSource !== undefined &&
|
|
1257
|
+
(!held ||
|
|
1258
|
+
!preparedSource ||
|
|
1259
|
+
preparedSource.journalBindingKey !== input.recipientBindingKey ||
|
|
1260
|
+
preparedSource.updateId !==
|
|
1261
|
+
("mode" in input
|
|
1262
|
+
? input.sourceUpdateIds[0]
|
|
1263
|
+
: input.updates[0]?.update_id))) ||
|
|
1264
|
+
(held && "mode" in input && !preparedSource))
|
|
1265
|
+
return undefined;
|
|
1266
|
+
if (input.selectedCommand !== undefined &&
|
|
1267
|
+
(!held ||
|
|
1268
|
+
("mode" in input
|
|
1269
|
+
? input.sourceUpdateIds.length !== 1 ||
|
|
1270
|
+
!areTelegramTargetsEqual(input.target, held.target)
|
|
1271
|
+
: input.updates.length !== 1)))
|
|
1272
|
+
return undefined;
|
|
1273
|
+
const marker = selectedCommand ? { selectedCommand } : {};
|
|
1274
|
+
const expected = structuredClone({
|
|
1275
|
+
operationId: input.operationId,
|
|
1276
|
+
instanceId: input.instanceId,
|
|
1277
|
+
sessionId: input.sessionId,
|
|
1278
|
+
recipientBindingKey: input.recipientBindingKey,
|
|
1279
|
+
...(held
|
|
1280
|
+
? { ...marker, ...(preparedSource ? { preparedSource } : {}) }
|
|
1281
|
+
: {}),
|
|
1282
|
+
...("mode" in input
|
|
1283
|
+
? {
|
|
1284
|
+
mode: input.mode,
|
|
1285
|
+
sourceUpdateIds: input.sourceUpdateIds,
|
|
1286
|
+
slot: input.slot,
|
|
1287
|
+
target: input.target,
|
|
1288
|
+
oldTarget: input.oldTarget,
|
|
1289
|
+
}
|
|
1290
|
+
: { updates: input.updates }),
|
|
1291
|
+
});
|
|
1292
|
+
const observing = "mode" in expected && expected.mode === "observe";
|
|
1293
|
+
const commandObserving = "mode" in expected && expected.mode === "observe-command";
|
|
1294
|
+
const settling = "mode" in expected &&
|
|
1295
|
+
(expected.mode === "release" ||
|
|
1296
|
+
expected.mode === "discard" ||
|
|
1297
|
+
observing ||
|
|
1298
|
+
commandObserving);
|
|
1299
|
+
const required = [
|
|
1300
|
+
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
|
|
1301
|
+
...("mode" in expected || held
|
|
1302
|
+
? [TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY]
|
|
1303
|
+
: []),
|
|
1304
|
+
...(settling || held ? [TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE] : []),
|
|
1305
|
+
...(held
|
|
1306
|
+
? [
|
|
1307
|
+
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
|
|
1308
|
+
TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
|
|
1309
|
+
]
|
|
1310
|
+
: []),
|
|
1311
|
+
];
|
|
1312
|
+
const follower = deps.getFollower(expected.instanceId), secret = deps.getAuthSecret();
|
|
1313
|
+
if (!isCurrent() ||
|
|
1314
|
+
!secret ||
|
|
1315
|
+
!follower?.registrationGeneration ||
|
|
1316
|
+
!follower.busSocketPath ||
|
|
1317
|
+
follower.sessionId !== expected.sessionId ||
|
|
1318
|
+
!hasTelegramBusSharedCapabilities(deps.localProtocolIdentity, follower.protocol, required))
|
|
1319
|
+
return undefined;
|
|
1320
|
+
const captured = structuredClone(follower);
|
|
1321
|
+
/** The first failed authority condition, by fixed label; undefined while the captured recipient is current. */
|
|
1322
|
+
const staleReason = () => {
|
|
1323
|
+
if (!isCurrent())
|
|
1324
|
+
return "caller authority";
|
|
1325
|
+
if (deps.getAuthSecret() !== secret)
|
|
1326
|
+
return "auth secret";
|
|
1327
|
+
if (!isDeepStrictEqual(deps.localProtocolIdentity, localProtocol) ||
|
|
1328
|
+
!required.every((cap) => hasTelegramBusCapability(deps.localProtocolIdentity, cap)))
|
|
1329
|
+
return "local protocol";
|
|
1330
|
+
const live = deps.getFollower(expected.instanceId);
|
|
1331
|
+
if (!live)
|
|
1332
|
+
return "follower registration";
|
|
1333
|
+
if (live.sessionId !== captured.sessionId ||
|
|
1334
|
+
live.registrationGeneration !== captured.registrationGeneration ||
|
|
1335
|
+
live.sessionGeneration !== captured.sessionGeneration)
|
|
1336
|
+
return "follower generation";
|
|
1337
|
+
if (live.pid !== captured.pid ||
|
|
1338
|
+
live.processBirthId !== captured.processBirthId)
|
|
1339
|
+
return "follower process";
|
|
1340
|
+
if (live.busSocketPath !== captured.busSocketPath ||
|
|
1341
|
+
live.slot !== captured.slot ||
|
|
1342
|
+
live.cwd !== captured.cwd)
|
|
1343
|
+
return "follower endpoint";
|
|
1344
|
+
if ("mode" in expected
|
|
1345
|
+
? live.slot !== expected.slot ||
|
|
1346
|
+
!live.target ||
|
|
1347
|
+
(!areTelegramTargetsEqual(live.target, expected.target) &&
|
|
1348
|
+
!areTelegramTargetsEqual(live.target, expected.oldTarget))
|
|
1349
|
+
: !isDeepStrictEqual(live.target, captured.target))
|
|
1350
|
+
return "follower target";
|
|
1351
|
+
if (!required.every((cap) => hasTelegramBusCapability(live.protocol, cap)) ||
|
|
1352
|
+
!isDeepStrictEqual(live.protocol, captured.protocol) ||
|
|
1353
|
+
!getTelegramBusProtocolCompatibility({
|
|
1354
|
+
local: deps.localProtocolIdentity,
|
|
1355
|
+
remote: live.protocol,
|
|
1356
|
+
}).compatible)
|
|
1357
|
+
return "follower protocol";
|
|
1358
|
+
return undefined;
|
|
1359
|
+
};
|
|
1360
|
+
const current = () => staleReason() === undefined;
|
|
1361
|
+
if (!current())
|
|
1362
|
+
return undefined;
|
|
1363
|
+
const requestId = deps.createRequestId();
|
|
1364
|
+
const response = await sendTelegramBusLocalEnvelope({
|
|
1365
|
+
socketPath: captured.busSocketPath,
|
|
1366
|
+
timeoutMs: deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS,
|
|
1367
|
+
retry: { attempts: 1, delayMs: 0 },
|
|
1368
|
+
...(held
|
|
1369
|
+
? {
|
|
1370
|
+
assertAuthority: () => {
|
|
1371
|
+
const reason = staleReason();
|
|
1372
|
+
if (reason)
|
|
1373
|
+
throw new Error(`Selected held input sender authority changed (${reason}).`);
|
|
1374
|
+
},
|
|
1375
|
+
}
|
|
1376
|
+
: {}),
|
|
1377
|
+
envelope: {
|
|
1378
|
+
requestId,
|
|
1379
|
+
auth: secret,
|
|
1380
|
+
...(held
|
|
1381
|
+
? {
|
|
1382
|
+
...marker,
|
|
1383
|
+
...("mode" in expected
|
|
1384
|
+
? { preparedSource: expected.preparedSource }
|
|
1385
|
+
: {}),
|
|
1386
|
+
}
|
|
1387
|
+
: {}),
|
|
1388
|
+
recipientInstanceId: expected.instanceId,
|
|
1389
|
+
recipientRegistrationGeneration: captured.registrationGeneration,
|
|
1390
|
+
recipientSessionId: expected.sessionId,
|
|
1391
|
+
recipientBindingKey: expected.recipientBindingKey,
|
|
1392
|
+
operationId: expected.operationId,
|
|
1393
|
+
sentAtMs: Date.now(),
|
|
1394
|
+
...("mode" in expected
|
|
1395
|
+
? settling
|
|
1396
|
+
? {
|
|
1397
|
+
kind: "leader.settleLiveRebind",
|
|
1398
|
+
mode: expected.mode,
|
|
1399
|
+
sourceUpdateIds: expected.sourceUpdateIds,
|
|
1400
|
+
...(observing ? { oldTarget: expected.oldTarget } : {}),
|
|
1401
|
+
}
|
|
1402
|
+
: {
|
|
1403
|
+
kind: "leader.applyLiveRebind",
|
|
1404
|
+
mode: expected.mode,
|
|
1405
|
+
sourceUpdateIds: expected.sourceUpdateIds,
|
|
1406
|
+
}
|
|
1407
|
+
: {
|
|
1408
|
+
kind: "leader.prepareLiveRebind",
|
|
1409
|
+
updates: expected.updates,
|
|
1410
|
+
}),
|
|
1411
|
+
},
|
|
1412
|
+
});
|
|
1413
|
+
if (!current() ||
|
|
1414
|
+
response?.kind !== "bus.ack" ||
|
|
1415
|
+
response.requestId !== requestId ||
|
|
1416
|
+
response.ok !== true ||
|
|
1417
|
+
!isRecord(response.result))
|
|
1418
|
+
return undefined;
|
|
1419
|
+
const result = response.result, recipient = result.recipient;
|
|
1420
|
+
if (!hasOnlyWireKeys(result, [
|
|
1421
|
+
"operationId",
|
|
1422
|
+
"recipient",
|
|
1423
|
+
"sourceUpdateIds",
|
|
1424
|
+
"selectedCommand",
|
|
1425
|
+
"preparedSource",
|
|
1426
|
+
"status",
|
|
1427
|
+
"command",
|
|
1428
|
+
"sourceAck",
|
|
1429
|
+
"target",
|
|
1430
|
+
"slot",
|
|
1431
|
+
"action",
|
|
1432
|
+
"oldTarget",
|
|
1433
|
+
"work",
|
|
1434
|
+
]))
|
|
1435
|
+
return undefined;
|
|
1436
|
+
if (selectedCommand
|
|
1437
|
+
? !isDeepStrictEqual(parseSelectedCommandInput(result.selectedCommand), selectedCommand)
|
|
1438
|
+
: Object.hasOwn(result, "selectedCommand"))
|
|
1439
|
+
return undefined;
|
|
1440
|
+
const ids = ("mode" in expected
|
|
1441
|
+
? [...expected.sourceUpdateIds]
|
|
1442
|
+
: expected.updates.map((update) => update.update_id)).sort((a, b) => a - b);
|
|
1443
|
+
const observedSource = parsePreparedCommandSource(result.preparedSource);
|
|
1444
|
+
if (held
|
|
1445
|
+
? !observedSource ||
|
|
1446
|
+
observedSource.updateId !== ids[0] ||
|
|
1447
|
+
observedSource.journalBindingKey !== expected.recipientBindingKey ||
|
|
1448
|
+
(expected.preparedSource &&
|
|
1449
|
+
!isDeepStrictEqual(observedSource, expected.preparedSource))
|
|
1450
|
+
: Object.hasOwn(result, "preparedSource"))
|
|
1451
|
+
return undefined;
|
|
1452
|
+
if (result.operationId !== expected.operationId ||
|
|
1453
|
+
!(commandObserving
|
|
1454
|
+
? result.status === "command-observed"
|
|
1455
|
+
: observing
|
|
1456
|
+
? result.status === "observed"
|
|
1457
|
+
: settling
|
|
1458
|
+
? result.status === "protected" ||
|
|
1459
|
+
result.status === "unknown" ||
|
|
1460
|
+
result.status ===
|
|
1461
|
+
(expected.mode === "release" ? "released" : "discarded")
|
|
1462
|
+
: "mode" in expected
|
|
1463
|
+
? result.status === "saved" || result.status === "applied"
|
|
1464
|
+
: result.status === "saved") ||
|
|
1465
|
+
!isRecord(recipient) ||
|
|
1466
|
+
recipient.instanceId !== expected.instanceId ||
|
|
1467
|
+
recipient.sessionId !== expected.sessionId ||
|
|
1468
|
+
recipient.generation !== captured.registrationGeneration ||
|
|
1469
|
+
recipient.bindingKey !== expected.recipientBindingKey ||
|
|
1470
|
+
!Array.isArray(result.sourceUpdateIds) ||
|
|
1471
|
+
result.sourceUpdateIds.length !== ids.length ||
|
|
1472
|
+
result.sourceUpdateIds.some((id, index) => id !== ids[index]))
|
|
1473
|
+
return undefined;
|
|
1474
|
+
const observation = {
|
|
1475
|
+
operationId: expected.operationId,
|
|
1476
|
+
recipient: {
|
|
1477
|
+
instanceId: expected.instanceId,
|
|
1478
|
+
sessionId: expected.sessionId,
|
|
1479
|
+
generation: captured.registrationGeneration,
|
|
1480
|
+
bindingKey: expected.recipientBindingKey,
|
|
1481
|
+
},
|
|
1482
|
+
sourceUpdateIds: ids,
|
|
1483
|
+
...(held
|
|
1484
|
+
? { ...structuredClone(marker), preparedSource: { ...observedSource } }
|
|
1485
|
+
: {}),
|
|
1486
|
+
status: "saved",
|
|
1487
|
+
};
|
|
1488
|
+
if (!("mode" in expected))
|
|
1489
|
+
return observation;
|
|
1490
|
+
if (commandObserving) {
|
|
1491
|
+
const sourceAck = parsePreparedCommandSource(result.sourceAck);
|
|
1492
|
+
if (!hasOnlyWireKeys(result, [
|
|
1493
|
+
"operationId",
|
|
1494
|
+
"recipient",
|
|
1495
|
+
"sourceUpdateIds",
|
|
1496
|
+
"selectedCommand",
|
|
1497
|
+
"preparedSource",
|
|
1498
|
+
"status",
|
|
1499
|
+
"command",
|
|
1500
|
+
"sourceAck",
|
|
1501
|
+
]) ||
|
|
1502
|
+
!hasOnlyWireKeys(recipient, [
|
|
1503
|
+
"instanceId",
|
|
1504
|
+
"sessionId",
|
|
1505
|
+
"generation",
|
|
1506
|
+
"bindingKey",
|
|
1507
|
+
]) ||
|
|
1508
|
+
!["completed", "unknown", "not-issued"].includes(result.command) ||
|
|
1509
|
+
(Object.hasOwn(result, "sourceAck") &&
|
|
1510
|
+
(!sourceAck ||
|
|
1511
|
+
!isDeepStrictEqual(sourceAck, expected.preparedSource) ||
|
|
1512
|
+
result.command === "not-issued")))
|
|
1513
|
+
return undefined;
|
|
1514
|
+
const settled = {
|
|
1515
|
+
command: result.command,
|
|
1516
|
+
...(sourceAck ? { sourceAck: { ...sourceAck } } : {}),
|
|
1517
|
+
};
|
|
1518
|
+
return {
|
|
1519
|
+
...observation,
|
|
1520
|
+
status: "command-observed",
|
|
1521
|
+
selectedCommand: structuredClone(selectedCommand),
|
|
1522
|
+
preparedSource: { ...observedSource },
|
|
1523
|
+
...settled,
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
if (observing) {
|
|
1527
|
+
const work = result.work, oldTarget = parseThreadTarget(result.oldTarget);
|
|
1528
|
+
if (!oldTarget ||
|
|
1529
|
+
!isDeepStrictEqual(oldTarget, expected.oldTarget) ||
|
|
1530
|
+
!isRecord(work) ||
|
|
1531
|
+
[
|
|
1532
|
+
work.sessionBusy,
|
|
1533
|
+
work.targetWork,
|
|
1534
|
+
work.deliveryPending,
|
|
1535
|
+
work.unknown,
|
|
1536
|
+
].some((flag) => typeof flag !== "boolean"))
|
|
1537
|
+
return undefined;
|
|
1538
|
+
return {
|
|
1539
|
+
...observation,
|
|
1540
|
+
status: "observed",
|
|
1541
|
+
oldTarget,
|
|
1542
|
+
work: {
|
|
1543
|
+
sessionBusy: work.sessionBusy,
|
|
1544
|
+
targetWork: work.targetWork,
|
|
1545
|
+
deliveryPending: work.deliveryPending,
|
|
1546
|
+
unknown: work.unknown,
|
|
1547
|
+
},
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
if (settling) {
|
|
1551
|
+
if (result.action !== expected.mode)
|
|
1552
|
+
return undefined;
|
|
1553
|
+
return {
|
|
1554
|
+
...observation,
|
|
1555
|
+
action: expected.mode,
|
|
1556
|
+
status: result.status,
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
const target = parseThreadTarget(result.target), wanted = result.status === "applied" ? expected.target : expected.oldTarget;
|
|
1560
|
+
if (!target ||
|
|
1561
|
+
!isDeepStrictEqual(target, wanted) ||
|
|
1562
|
+
result.slot !== expected.slot)
|
|
1563
|
+
return undefined;
|
|
1564
|
+
return {
|
|
1565
|
+
...observation,
|
|
1566
|
+
status: result.status,
|
|
1567
|
+
target,
|
|
1568
|
+
slot: expected.slot,
|
|
1569
|
+
};
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
export function isTelegramBusEnvelopeAuthorized(envelope, secret) {
|
|
1573
|
+
if (!secret)
|
|
1574
|
+
return true;
|
|
1575
|
+
if (typeof envelope.auth !== "string")
|
|
1576
|
+
return false;
|
|
1577
|
+
const auth = Buffer.from(envelope.auth);
|
|
1578
|
+
const expected = Buffer.from(secret);
|
|
1579
|
+
return auth.length === expected.length && timingSafeEqual(auth, expected);
|
|
1580
|
+
}
|
|
1581
|
+
/** Negative acknowledgement carrying only a diagnostic message. */
|
|
1582
|
+
export function rejectTelegramBusRequest(requestId, message) {
|
|
1583
|
+
return { kind: "bus.ack", requestId, ok: false, message };
|
|
1584
|
+
}
|
|
1585
|
+
export function createUnauthorizedBusAck(requestId) {
|
|
1586
|
+
return rejectTelegramBusRequest(requestId, "Unauthorized Telegram bus envelope.");
|
|
1587
|
+
}
|
|
1588
|
+
export function createTelegramBusLocalServer(deps) {
|
|
1589
|
+
const requestLedger = new Map();
|
|
1590
|
+
const requestLedgerMaxEntries = Math.max(1, deps.requestLedgerMaxEntries ?? 4096);
|
|
1591
|
+
const getRequestLedgerKey = (envelope) => {
|
|
1592
|
+
const identity = envelope.kind === "follower.register" ||
|
|
1593
|
+
envelope.kind === "follower.restoreWorkspace"
|
|
1594
|
+
? envelope.registration.instanceId
|
|
1595
|
+
: "instanceId" in envelope
|
|
1596
|
+
? envelope.instanceId
|
|
1597
|
+
: "recipientInstanceId" in envelope
|
|
1598
|
+
? envelope.recipientInstanceId
|
|
1599
|
+
: "ack";
|
|
1600
|
+
return `${envelope.auth ?? ""}:${identity}:${envelope.requestId}`;
|
|
1601
|
+
};
|
|
1602
|
+
const handleEnvelopeOnce = (envelope) => {
|
|
1603
|
+
const key = getRequestLedgerKey(envelope);
|
|
1604
|
+
const fingerprint = JSON.stringify(envelope);
|
|
1605
|
+
const existing = requestLedger.get(key);
|
|
1606
|
+
if (existing) {
|
|
1607
|
+
if (existing.fingerprint === fingerprint)
|
|
1608
|
+
return existing.result;
|
|
1609
|
+
return Promise.resolve({
|
|
1610
|
+
kind: "bus.ack",
|
|
1611
|
+
requestId: envelope.requestId,
|
|
1612
|
+
ok: false,
|
|
1613
|
+
message: "Telegram bus request id was reused with a different payload.",
|
|
1614
|
+
error: { code: "request-id-collision" },
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
if (requestLedger.size >= requestLedgerMaxEntries) {
|
|
1618
|
+
const settledKey = Array.from(requestLedger.entries()).find(([, entry]) => entry.settled)?.[0];
|
|
1619
|
+
if (settledKey)
|
|
1620
|
+
requestLedger.delete(settledKey);
|
|
1621
|
+
}
|
|
1622
|
+
if (requestLedger.size >= requestLedgerMaxEntries) {
|
|
1623
|
+
return Promise.resolve({
|
|
1624
|
+
kind: "bus.ack",
|
|
1625
|
+
requestId: envelope.requestId,
|
|
1626
|
+
ok: false,
|
|
1627
|
+
message: "Telegram bus request ledger is full.",
|
|
1628
|
+
error: { code: "ledger-overloaded" },
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
const entry = {
|
|
1632
|
+
fingerprint,
|
|
1633
|
+
settled: false,
|
|
1634
|
+
result: Promise.resolve().then(() => deps.handleEnvelope(envelope)),
|
|
1635
|
+
};
|
|
1636
|
+
requestLedger.set(key, entry);
|
|
1637
|
+
const settled = () => {
|
|
1638
|
+
entry.settled = true;
|
|
1639
|
+
};
|
|
1640
|
+
void entry.result.then(settled, settled);
|
|
1641
|
+
return entry.result;
|
|
1642
|
+
};
|
|
1643
|
+
let server;
|
|
1644
|
+
let activeSocketPath;
|
|
1645
|
+
let activeListenPath;
|
|
1646
|
+
let endpointRecovery;
|
|
1647
|
+
let stopGeneration = 0;
|
|
1648
|
+
const sockets = new Set();
|
|
1649
|
+
const closeSocket = (socket) => {
|
|
1650
|
+
sockets.delete(socket);
|
|
1651
|
+
socket.destroy();
|
|
1652
|
+
};
|
|
1653
|
+
const runtime = {
|
|
1654
|
+
start: async () => {
|
|
1655
|
+
if (server)
|
|
1656
|
+
return;
|
|
1657
|
+
const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
|
|
1658
|
+
const activeServers = getActiveTelegramBusLocalServers();
|
|
1659
|
+
const replacedServer = activeServers.get(socketPath);
|
|
1660
|
+
if (replacedServer && replacedServer !== runtime) {
|
|
1661
|
+
await replacedServer.stop();
|
|
1662
|
+
}
|
|
1663
|
+
const usesWindowsPipe = isTelegramBusPipePath(socketPath);
|
|
1664
|
+
const endpointGeneration = randomBytes(8).toString("hex");
|
|
1665
|
+
const listenPath = usesWindowsPipe
|
|
1666
|
+
? socketPath
|
|
1667
|
+
: join(dirname(socketPath), `.pt-${endpointGeneration}.sock`);
|
|
1668
|
+
activeSocketPath = socketPath;
|
|
1669
|
+
activeListenPath = listenPath;
|
|
1670
|
+
deps.recordTransportEvent?.("server-start", getTelegramBusEndpointDiagnostics(socketPath));
|
|
1671
|
+
if (!usesWindowsPipe) {
|
|
1672
|
+
const socketDir = dirname(socketPath);
|
|
1673
|
+
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
1674
|
+
chmodSync(socketDir, 0o700);
|
|
1675
|
+
if (existsSync(listenPath))
|
|
1676
|
+
unlinkSync(listenPath);
|
|
1677
|
+
const legacyDeadlineMs = Date.now() + 2000;
|
|
1678
|
+
while (true) {
|
|
1679
|
+
let isLegacySocket = false;
|
|
1680
|
+
try {
|
|
1681
|
+
isLegacySocket = lstatSync(socketPath).isSocket();
|
|
1682
|
+
}
|
|
1683
|
+
catch {
|
|
1684
|
+
/* endpoint does not exist */
|
|
1685
|
+
}
|
|
1686
|
+
if (!isLegacySocket)
|
|
1687
|
+
break;
|
|
1688
|
+
const probe = await probeTelegramBusEndpoint({
|
|
1689
|
+
endpoint: socketPath,
|
|
1690
|
+
timeoutMs: 50,
|
|
1691
|
+
});
|
|
1692
|
+
if (!probe.reachable)
|
|
1693
|
+
break;
|
|
1694
|
+
if (Date.now() >= legacyDeadlineMs) {
|
|
1695
|
+
throw new Error(`Timed out waiting for legacy Telegram bus endpoint: ${socketPath}`);
|
|
1696
|
+
}
|
|
1697
|
+
await delayTelegramBusTransportRetry(25);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
if (usesWindowsPipe) {
|
|
1701
|
+
await deps.beforeEndpointPublication?.();
|
|
1702
|
+
const committed = deps.commitEndpointPublication
|
|
1703
|
+
? deps.commitEndpointPublication(() => { })
|
|
1704
|
+
: true;
|
|
1705
|
+
if (!committed) {
|
|
1706
|
+
activeSocketPath = undefined;
|
|
1707
|
+
activeListenPath = undefined;
|
|
1708
|
+
throw new Error("Telegram bus endpoint publication lost transport ownership.");
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
server = createServer((socket) => {
|
|
1712
|
+
sockets.add(socket);
|
|
1713
|
+
let buffer = "";
|
|
1714
|
+
socket.setEncoding("utf8");
|
|
1715
|
+
socket.on("data", (chunk) => {
|
|
1716
|
+
buffer += chunk;
|
|
1717
|
+
const lines = buffer.split("\n");
|
|
1718
|
+
buffer = lines.pop() ?? "";
|
|
1719
|
+
for (const line of lines) {
|
|
1720
|
+
void handleTelegramBusSocketLine(line, socket, handleEnvelopeOnce, deps.recordTransportEvent, socketPath, deps.shouldDropResponse);
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
socket.on("close", () => sockets.delete(socket));
|
|
1724
|
+
socket.on("error", (error) => {
|
|
1725
|
+
deps.recordTransportEvent?.("server-socket-error", {
|
|
1726
|
+
...getTelegramBusEndpointDiagnostics(socketPath),
|
|
1727
|
+
...classifyTelegramBusTransportError(error),
|
|
1728
|
+
});
|
|
1729
|
+
closeSocket(socket);
|
|
1730
|
+
});
|
|
1731
|
+
});
|
|
1732
|
+
try {
|
|
1733
|
+
await new Promise((resolve, reject) => {
|
|
1734
|
+
server?.once("error", reject);
|
|
1735
|
+
server?.listen(listenPath, resolve);
|
|
1736
|
+
});
|
|
1737
|
+
activeServers.set(socketPath, runtime);
|
|
1738
|
+
deps.recordTransportEvent?.("server-started", getTelegramBusEndpointDiagnostics(socketPath));
|
|
1739
|
+
}
|
|
1740
|
+
catch (error) {
|
|
1741
|
+
server = undefined;
|
|
1742
|
+
activeSocketPath = undefined;
|
|
1743
|
+
activeListenPath = undefined;
|
|
1744
|
+
deps.recordTransportEvent?.("server-start-failed", {
|
|
1745
|
+
...getTelegramBusEndpointDiagnostics(socketPath),
|
|
1746
|
+
...classifyTelegramBusTransportError(error),
|
|
1747
|
+
});
|
|
1748
|
+
throw error;
|
|
1749
|
+
}
|
|
1750
|
+
if (!usesWindowsPipe) {
|
|
1751
|
+
chmodSync(listenPath, 0o600);
|
|
1752
|
+
const linkPath = `${socketPath}.link.${endpointGeneration}`;
|
|
1753
|
+
try {
|
|
1754
|
+
symlinkSync(basename(listenPath), linkPath);
|
|
1755
|
+
await deps.beforeEndpointPublication?.();
|
|
1756
|
+
const committed = deps.commitEndpointPublication
|
|
1757
|
+
? deps.commitEndpointPublication(() => renameSync(linkPath, socketPath))
|
|
1758
|
+
: (renameSync(linkPath, socketPath), true);
|
|
1759
|
+
if (!committed) {
|
|
1760
|
+
throw new Error("Telegram bus endpoint publication lost transport ownership.");
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
catch (error) {
|
|
1764
|
+
try {
|
|
1765
|
+
if (lstatSync(linkPath).isSymbolicLink())
|
|
1766
|
+
unlinkSync(linkPath);
|
|
1767
|
+
}
|
|
1768
|
+
catch {
|
|
1769
|
+
/* no unpublished link to remove */
|
|
1770
|
+
}
|
|
1771
|
+
const failedServer = server;
|
|
1772
|
+
server = undefined;
|
|
1773
|
+
activeSocketPath = undefined;
|
|
1774
|
+
activeListenPath = undefined;
|
|
1775
|
+
if (activeServers.get(socketPath) === runtime) {
|
|
1776
|
+
activeServers.delete(socketPath);
|
|
1777
|
+
}
|
|
1778
|
+
if (failedServer) {
|
|
1779
|
+
await new Promise((resolve) => failedServer.close(() => resolve()));
|
|
1780
|
+
}
|
|
1781
|
+
throw error;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
},
|
|
1785
|
+
stop: async () => {
|
|
1786
|
+
stopGeneration += 1;
|
|
1787
|
+
requestLedger.clear();
|
|
1788
|
+
const activeServer = server;
|
|
1789
|
+
const socketPath = activeSocketPath;
|
|
1790
|
+
const listenPath = activeListenPath;
|
|
1791
|
+
if (socketPath &&
|
|
1792
|
+
getActiveTelegramBusLocalServers().get(socketPath) === runtime) {
|
|
1793
|
+
getActiveTelegramBusLocalServers().delete(socketPath);
|
|
1794
|
+
}
|
|
1795
|
+
server = undefined;
|
|
1796
|
+
activeSocketPath = undefined;
|
|
1797
|
+
activeListenPath = undefined;
|
|
1798
|
+
for (const socket of sockets)
|
|
1799
|
+
closeSocket(socket);
|
|
1800
|
+
if (activeServer) {
|
|
1801
|
+
await new Promise((resolve) => activeServer.close(() => resolve()));
|
|
1802
|
+
}
|
|
1803
|
+
if (socketPath && listenPath && !isTelegramBusPipePath(socketPath)) {
|
|
1804
|
+
try {
|
|
1805
|
+
if (lstatSync(socketPath).isSymbolicLink() &&
|
|
1806
|
+
readlinkSync(socketPath) === basename(listenPath)) {
|
|
1807
|
+
// Leave the generation link in place. Node removes only the unique
|
|
1808
|
+
// listen path on close; a later generation atomically replaces this
|
|
1809
|
+
// link, and existsSync() treats the stopped dangling link as missing.
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
catch {
|
|
1813
|
+
/* endpoint already moved or removed */
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (socketPath) {
|
|
1817
|
+
deps.recordTransportEvent?.("server-stopped", getTelegramBusEndpointDiagnostics(socketPath));
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
ensureEndpoint: async () => {
|
|
1821
|
+
const socketPath = activeSocketPath;
|
|
1822
|
+
if (!server ||
|
|
1823
|
+
!socketPath ||
|
|
1824
|
+
isTelegramBusPipePath(socketPath) ||
|
|
1825
|
+
existsSync(socketPath)) {
|
|
1826
|
+
return false;
|
|
1827
|
+
}
|
|
1828
|
+
if (endpointRecovery)
|
|
1829
|
+
return endpointRecovery;
|
|
1830
|
+
endpointRecovery = (async () => {
|
|
1831
|
+
deps.recordTransportEvent?.("server-endpoint-missing", getTelegramBusEndpointDiagnostics(socketPath));
|
|
1832
|
+
const recoveryStopGeneration = stopGeneration + 1;
|
|
1833
|
+
await runtime.stop();
|
|
1834
|
+
if (stopGeneration !== recoveryStopGeneration)
|
|
1835
|
+
return false;
|
|
1836
|
+
await runtime.start();
|
|
1837
|
+
if (stopGeneration !== recoveryStopGeneration) {
|
|
1838
|
+
await runtime.stop();
|
|
1839
|
+
return false;
|
|
1840
|
+
}
|
|
1841
|
+
deps.recordTransportEvent?.("server-endpoint-recovered", getTelegramBusEndpointDiagnostics(socketPath));
|
|
1842
|
+
return true;
|
|
1843
|
+
})();
|
|
1844
|
+
try {
|
|
1845
|
+
return await endpointRecovery;
|
|
1846
|
+
}
|
|
1847
|
+
finally {
|
|
1848
|
+
endpointRecovery = undefined;
|
|
1849
|
+
}
|
|
1850
|
+
},
|
|
1851
|
+
};
|
|
1852
|
+
return runtime;
|
|
1853
|
+
}
|
|
1854
|
+
function getTelegramBusEnvelopeDiagnostics(envelope) {
|
|
1855
|
+
return {
|
|
1856
|
+
envelopeKind: envelope.kind,
|
|
1857
|
+
requestId: envelope.requestId,
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function sendTelegramBusLocalEnvelopeOnce(options) {
|
|
1861
|
+
const timeoutMs = options.timeoutMs ?? 1000;
|
|
1862
|
+
return new Promise((resolve, reject) => {
|
|
1863
|
+
const assertAuthority = options.assertAuthority;
|
|
1864
|
+
let issued = false;
|
|
1865
|
+
const assertCurrent = () => {
|
|
1866
|
+
try {
|
|
1867
|
+
assertAuthority?.();
|
|
1868
|
+
}
|
|
1869
|
+
catch (error) {
|
|
1870
|
+
// The caller's reason is a fixed condition label, never request or credential content.
|
|
1871
|
+
throw new TelegramBusLocalAuthorityError(issued, error instanceof Error ? error.message : undefined);
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
try {
|
|
1875
|
+
assertCurrent();
|
|
1876
|
+
}
|
|
1877
|
+
catch (error) {
|
|
1878
|
+
reject(error);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const socket = createConnection(options.socketPath);
|
|
1882
|
+
let settled = false;
|
|
1883
|
+
let buffer = "";
|
|
1884
|
+
let timeoutFinalizer;
|
|
1885
|
+
const settle = (callback) => {
|
|
1886
|
+
if (settled)
|
|
1887
|
+
return;
|
|
1888
|
+
settled = true;
|
|
1889
|
+
clearTimeout(timeout);
|
|
1890
|
+
if (timeoutFinalizer)
|
|
1891
|
+
clearImmediate(timeoutFinalizer);
|
|
1892
|
+
socket.destroy();
|
|
1893
|
+
callback();
|
|
1894
|
+
};
|
|
1895
|
+
const timeout = setTimeout(() => {
|
|
1896
|
+
// A long synchronous Pi/TUI turn can resume in the timers phase after
|
|
1897
|
+
// the peer acknowledgement is already buffered. Give pending socket I/O
|
|
1898
|
+
// two poll phases before converting elapsed wall time into a transport
|
|
1899
|
+
// failure; Darwin can surface buffered Unix-socket input only on the
|
|
1900
|
+
// second cycle after a long stall, while a silent peer stays bounded.
|
|
1901
|
+
timeoutFinalizer = setImmediate(() => {
|
|
1902
|
+
timeoutFinalizer = setImmediate(() => {
|
|
1903
|
+
settle(() => reject(createTelegramBusTransportTimeoutError("Timed out waiting for Telegram bus response")));
|
|
1904
|
+
});
|
|
1905
|
+
});
|
|
1906
|
+
}, timeoutMs);
|
|
1907
|
+
socket.setEncoding("utf8");
|
|
1908
|
+
socket.once("connect", () => {
|
|
1909
|
+
try {
|
|
1910
|
+
assertCurrent();
|
|
1911
|
+
const encoded = encodeTelegramBusEnvelope(options.envelope);
|
|
1912
|
+
assertCurrent();
|
|
1913
|
+
issued = true;
|
|
1914
|
+
socket.write(encoded);
|
|
1915
|
+
}
|
|
1916
|
+
catch (error) {
|
|
1917
|
+
settle(() => reject(error));
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
socket.on("data", (chunk) => {
|
|
1921
|
+
buffer += chunk;
|
|
1922
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
1923
|
+
if (newlineIndex < 0)
|
|
1924
|
+
return;
|
|
1925
|
+
const line = buffer.slice(0, newlineIndex);
|
|
1926
|
+
try {
|
|
1927
|
+
const response = parseTelegramBusEnvelope(line);
|
|
1928
|
+
assertCurrent();
|
|
1929
|
+
settle(() => resolve(response));
|
|
1930
|
+
}
|
|
1931
|
+
catch (error) {
|
|
1932
|
+
settle(() => reject(error));
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
socket.once("error", (error) => settle(() => reject(error)));
|
|
1936
|
+
socket.once("end", () => settle(() => resolve(undefined)));
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
export async function sendTelegramBusLocalEnvelope(options) {
|
|
1940
|
+
const resolvedOptions = {
|
|
1941
|
+
...options,
|
|
1942
|
+
socketPath: resolveTelegramBusSocketPath(options.socketPath),
|
|
1943
|
+
};
|
|
1944
|
+
const attempts = Math.max(1, resolvedOptions.retry?.attempts ?? 1);
|
|
1945
|
+
const delayMs = Math.max(0, resolvedOptions.retry?.delayMs ?? 0);
|
|
1946
|
+
for (let attempt = 1;; attempt += 1) {
|
|
1947
|
+
try {
|
|
1948
|
+
return await sendTelegramBusLocalEnvelopeOnce(resolvedOptions);
|
|
1949
|
+
}
|
|
1950
|
+
catch (error) {
|
|
1951
|
+
const info = classifyTelegramBusTransportError(error);
|
|
1952
|
+
resolvedOptions.recordTransportEvent?.("client-failed", {
|
|
1953
|
+
...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
|
|
1954
|
+
...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
|
|
1955
|
+
attempt,
|
|
1956
|
+
attempts,
|
|
1957
|
+
...info,
|
|
1958
|
+
});
|
|
1959
|
+
if (error instanceof TelegramBusLocalAuthorityError ||
|
|
1960
|
+
attempt >= attempts ||
|
|
1961
|
+
!isRetryableTelegramBusTransportError(error)) {
|
|
1962
|
+
throw error;
|
|
1963
|
+
}
|
|
1964
|
+
resolvedOptions.recordTransportEvent?.("client-retry", {
|
|
1965
|
+
...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
|
|
1966
|
+
...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
|
|
1967
|
+
attempt,
|
|
1968
|
+
attempts,
|
|
1969
|
+
delayMs,
|
|
1970
|
+
...info,
|
|
1971
|
+
});
|
|
1972
|
+
await delayTelegramBusTransportRetry(delayMs);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
export function createTelegramBusForwardOwnershipValidator(registry) {
|
|
1977
|
+
return (ownership) => {
|
|
1978
|
+
const follower = registry.get(ownership.instanceId);
|
|
1979
|
+
return isTelegramBusForwardOwnershipCurrent(ownership, follower?.registrationGeneration &&
|
|
1980
|
+
follower.profileKey &&
|
|
1981
|
+
follower.protocol
|
|
1982
|
+
? {
|
|
1983
|
+
instanceId: follower.instanceId,
|
|
1984
|
+
ownerGeneration: follower.registrationGeneration,
|
|
1985
|
+
recipientBindingKey: follower.profileKey,
|
|
1986
|
+
protocolIdentity: follower.protocol,
|
|
1987
|
+
}
|
|
1988
|
+
: undefined);
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
function hasTelegramBusFollowerIdentityOverlap(first, second) {
|
|
1992
|
+
return (first.instanceId === second.instanceId ||
|
|
1993
|
+
(first.profileKey !== undefined &&
|
|
1994
|
+
first.profileKey === second.profileKey) ||
|
|
1995
|
+
(first.target !== undefined &&
|
|
1996
|
+
first.target.chatId === second.target?.chatId &&
|
|
1997
|
+
first.target.threadId === second.target.threadId));
|
|
1998
|
+
}
|
|
1999
|
+
export function createTelegramBusFollowerRegistry() {
|
|
2000
|
+
const followers = new Map();
|
|
2001
|
+
const observations = new Set();
|
|
2002
|
+
const clone = (follower) => ({
|
|
2003
|
+
...follower,
|
|
2004
|
+
target: follower.target ? { ...follower.target } : undefined,
|
|
2005
|
+
...(follower.protocol
|
|
2006
|
+
? {
|
|
2007
|
+
protocol: {
|
|
2008
|
+
...follower.protocol,
|
|
2009
|
+
capabilities: [...follower.protocol.capabilities],
|
|
2010
|
+
},
|
|
2011
|
+
}
|
|
2012
|
+
: {}),
|
|
2013
|
+
});
|
|
2014
|
+
return {
|
|
2015
|
+
register: (registration) => {
|
|
2016
|
+
const existing = followers.get(registration.instanceId);
|
|
2017
|
+
for (const [instanceId, follower] of followers.entries()) {
|
|
2018
|
+
if (instanceId === registration.instanceId)
|
|
2019
|
+
continue;
|
|
2020
|
+
if (hasTelegramBusFollowerIdentityOverlap(registration, follower))
|
|
2021
|
+
followers.delete(instanceId);
|
|
2022
|
+
}
|
|
2023
|
+
const next = {
|
|
2024
|
+
...registration,
|
|
2025
|
+
target: registration.target ? { ...registration.target } : undefined,
|
|
2026
|
+
...(registration.protocol
|
|
2027
|
+
? {
|
|
2028
|
+
protocol: {
|
|
2029
|
+
...registration.protocol,
|
|
2030
|
+
capabilities: [...registration.protocol.capabilities],
|
|
2031
|
+
},
|
|
2032
|
+
}
|
|
2033
|
+
: {}),
|
|
2034
|
+
lastHeartbeatMs: existing?.lastHeartbeatMs ?? registration.connectedAtMs,
|
|
2035
|
+
};
|
|
2036
|
+
followers.set(registration.instanceId, next);
|
|
2037
|
+
for (const observation of observations) {
|
|
2038
|
+
if (!hasTelegramBusFollowerIdentityOverlap(next, observation.follower))
|
|
2039
|
+
continue;
|
|
2040
|
+
observation.current = false;
|
|
2041
|
+
observations.delete(observation);
|
|
2042
|
+
}
|
|
2043
|
+
return clone(next);
|
|
2044
|
+
},
|
|
2045
|
+
heartbeat: (instanceId, nowMs) => {
|
|
2046
|
+
const existing = followers.get(instanceId);
|
|
2047
|
+
if (!existing)
|
|
2048
|
+
return undefined;
|
|
2049
|
+
const next = { ...existing, lastHeartbeatMs: nowMs };
|
|
2050
|
+
followers.set(instanceId, next);
|
|
2051
|
+
return clone(next);
|
|
2052
|
+
},
|
|
2053
|
+
get: (instanceId) => {
|
|
2054
|
+
const existing = followers.get(instanceId);
|
|
2055
|
+
return existing ? clone(existing) : undefined;
|
|
2056
|
+
},
|
|
2057
|
+
getByTarget: (target) => {
|
|
2058
|
+
for (const follower of followers.values()) {
|
|
2059
|
+
if (follower.target?.chatId === target.chatId &&
|
|
2060
|
+
follower.target.threadId === target.threadId) {
|
|
2061
|
+
return clone(follower);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return undefined;
|
|
2065
|
+
},
|
|
2066
|
+
list: () => [...followers.values()].map(clone),
|
|
2067
|
+
remove: (instanceId) => followers.delete(instanceId),
|
|
2068
|
+
clear: () => {
|
|
2069
|
+
followers.clear();
|
|
2070
|
+
for (const observation of observations)
|
|
2071
|
+
observation.current = false;
|
|
2072
|
+
observations.clear();
|
|
2073
|
+
},
|
|
2074
|
+
observeUnregistered: (follower) => {
|
|
2075
|
+
// This watches replacement, not process death or delivery authority.
|
|
2076
|
+
const observation = {
|
|
2077
|
+
follower: clone(follower),
|
|
2078
|
+
current: ![...followers.values()].some((current) => hasTelegramBusFollowerIdentityOverlap(current, follower)),
|
|
2079
|
+
};
|
|
2080
|
+
if (observation.current)
|
|
2081
|
+
observations.add(observation);
|
|
2082
|
+
return {
|
|
2083
|
+
isCurrent: () => observation.current,
|
|
2084
|
+
release() {
|
|
2085
|
+
observation.current = false;
|
|
2086
|
+
observations.delete(observation);
|
|
2087
|
+
},
|
|
2088
|
+
};
|
|
2089
|
+
},
|
|
2090
|
+
pruneStale: (nowMs, staleAfterMs) => {
|
|
2091
|
+
const removed = [];
|
|
2092
|
+
for (const [instanceId, follower] of followers.entries()) {
|
|
2093
|
+
if (nowMs - follower.lastHeartbeatMs <= staleAfterMs)
|
|
2094
|
+
continue;
|
|
2095
|
+
followers.delete(instanceId);
|
|
2096
|
+
removed.push(clone(follower));
|
|
2097
|
+
}
|
|
2098
|
+
return removed;
|
|
2099
|
+
},
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
async function handleTelegramBusSocketLine(line, socket, handleEnvelope, recordTransportEvent, socketPath, shouldDropResponse) {
|
|
2103
|
+
const envelope = parseTelegramBusEnvelope(line);
|
|
2104
|
+
if (!envelope) {
|
|
2105
|
+
recordTransportEvent?.("server-invalid-envelope", {
|
|
2106
|
+
...getTelegramBusEndpointDiagnostics(socketPath),
|
|
2107
|
+
byteLength: Buffer.byteLength(line),
|
|
2108
|
+
});
|
|
2109
|
+
socket.write(encodeTelegramBusEnvelope({
|
|
2110
|
+
kind: "bus.ack",
|
|
2111
|
+
requestId: "invalid",
|
|
2112
|
+
ok: false,
|
|
2113
|
+
message: "Invalid Telegram bus envelope.",
|
|
2114
|
+
}));
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
try {
|
|
2118
|
+
const response = await handleEnvelope(envelope);
|
|
2119
|
+
if (response && !shouldDropResponse?.(envelope, response)) {
|
|
2120
|
+
socket.write(encodeTelegramBusEnvelope(response));
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
catch (error) {
|
|
2124
|
+
recordTransportEvent?.("server-handler-failed", {
|
|
2125
|
+
...getTelegramBusEndpointDiagnostics(socketPath),
|
|
2126
|
+
...getTelegramBusEnvelopeDiagnostics(envelope),
|
|
2127
|
+
...classifyTelegramBusTransportError(error),
|
|
2128
|
+
});
|
|
2129
|
+
socket.write(encodeTelegramBusEnvelope(rejectTelegramBusRequest(envelope.requestId, "Telegram bus handler failed.")));
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function parseRegisterEnvelope(value, requestId, kind) {
|
|
2133
|
+
const registration = parseRegistration(value.registration);
|
|
2134
|
+
return registration ? { kind, requestId, registration } : undefined;
|
|
2135
|
+
}
|
|
2136
|
+
function parseFollowerPresenceEnvelope(value, requestId, kind) {
|
|
2137
|
+
return typeof value.instanceId === "string" &&
|
|
2138
|
+
typeof value.sentAtMs === "number"
|
|
2139
|
+
? {
|
|
2140
|
+
kind,
|
|
2141
|
+
requestId,
|
|
2142
|
+
instanceId: value.instanceId,
|
|
2143
|
+
...(typeof value.registrationGeneration === "string"
|
|
2144
|
+
? { registrationGeneration: value.registrationGeneration }
|
|
2145
|
+
: {}),
|
|
2146
|
+
sentAtMs: value.sentAtMs,
|
|
2147
|
+
}
|
|
2148
|
+
: undefined;
|
|
2149
|
+
}
|
|
2150
|
+
function parseRenameThreadEnvelope(value, requestId) {
|
|
2151
|
+
const target = parseTarget(value.target);
|
|
2152
|
+
return typeof value.instanceId === "string" &&
|
|
2153
|
+
typeof value.registrationGeneration === "string" &&
|
|
2154
|
+
target?.threadId !== undefined &&
|
|
2155
|
+
typeof value.threadName === "string" &&
|
|
2156
|
+
typeof value.sentAtMs === "number"
|
|
2157
|
+
? {
|
|
2158
|
+
kind: "follower.renameThread",
|
|
2159
|
+
requestId,
|
|
2160
|
+
instanceId: value.instanceId,
|
|
2161
|
+
registrationGeneration: value.registrationGeneration,
|
|
2162
|
+
target: { chatId: target.chatId, threadId: target.threadId },
|
|
2163
|
+
threadName: value.threadName,
|
|
2164
|
+
sentAtMs: value.sentAtMs,
|
|
2165
|
+
}
|
|
2166
|
+
: undefined;
|
|
2167
|
+
}
|
|
2168
|
+
function parseTelegramBusFollowerDeliveryIdentity(value) {
|
|
2169
|
+
if (!isRecord(value))
|
|
2170
|
+
return undefined;
|
|
2171
|
+
if (typeof value.deliveryId !== "string" ||
|
|
2172
|
+
!/^telegram-follower-v1-[a-f0-9]{64}$/u.test(value.deliveryId) ||
|
|
2173
|
+
!Number.isSafeInteger(value.sourceUpdateId) ||
|
|
2174
|
+
value.sourceUpdateId < 0 ||
|
|
2175
|
+
typeof value.recipientBindingKey !== "string" ||
|
|
2176
|
+
!value.recipientBindingKey ||
|
|
2177
|
+
(value.sourceRecoveryKey !== undefined &&
|
|
2178
|
+
(typeof value.sourceRecoveryKey !== "string" ||
|
|
2179
|
+
!value.sourceRecoveryKey ||
|
|
2180
|
+
value.sourceRecoveryKey.length > 1_024)) ||
|
|
2181
|
+
(value.sourceClaim !== undefined &&
|
|
2182
|
+
(!isRecord(value.sourceClaim) ||
|
|
2183
|
+
typeof value.sourceClaim.acquisitionId !== "string" ||
|
|
2184
|
+
!value.sourceClaim.acquisitionId ||
|
|
2185
|
+
value.sourceClaim.acquisitionId.length > 256 ||
|
|
2186
|
+
typeof value.sourceClaim.handoffId !== "string" ||
|
|
2187
|
+
!value.sourceClaim.handoffId ||
|
|
2188
|
+
value.sourceClaim.handoffId.length > 256))) {
|
|
2189
|
+
return undefined;
|
|
2190
|
+
}
|
|
2191
|
+
return {
|
|
2192
|
+
deliveryId: value.deliveryId,
|
|
2193
|
+
sourceUpdateId: value.sourceUpdateId,
|
|
2194
|
+
recipientBindingKey: value.recipientBindingKey,
|
|
2195
|
+
...(typeof value.sourceRecoveryKey === "string"
|
|
2196
|
+
? { sourceRecoveryKey: value.sourceRecoveryKey }
|
|
2197
|
+
: {}),
|
|
2198
|
+
...(isRecord(value.sourceClaim)
|
|
2199
|
+
? {
|
|
2200
|
+
sourceClaim: {
|
|
2201
|
+
acquisitionId: value.sourceClaim.acquisitionId,
|
|
2202
|
+
handoffId: value.sourceClaim.handoffId,
|
|
2203
|
+
},
|
|
2204
|
+
}
|
|
2205
|
+
: {}),
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
function parseForwardCallbackEnvelope(value, requestId) {
|
|
2209
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2210
|
+
return delivery &&
|
|
2211
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2212
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
2213
|
+
typeof value.sentAtMs === "number"
|
|
2214
|
+
? {
|
|
2215
|
+
kind: "leader.forwardCallback",
|
|
2216
|
+
requestId,
|
|
2217
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2218
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2219
|
+
delivery,
|
|
2220
|
+
query: value.query,
|
|
2221
|
+
sentAtMs: value.sentAtMs,
|
|
2222
|
+
}
|
|
2223
|
+
: undefined;
|
|
2224
|
+
}
|
|
2225
|
+
function parseForwardReactionEnvelope(value, requestId) {
|
|
2226
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2227
|
+
return delivery &&
|
|
2228
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2229
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
2230
|
+
typeof value.sentAtMs === "number"
|
|
2231
|
+
? {
|
|
2232
|
+
kind: "leader.forwardReaction",
|
|
2233
|
+
requestId,
|
|
2234
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2235
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2236
|
+
delivery,
|
|
2237
|
+
reactionUpdate: value.reactionUpdate,
|
|
2238
|
+
sentAtMs: value.sentAtMs,
|
|
2239
|
+
}
|
|
2240
|
+
: undefined;
|
|
2241
|
+
}
|
|
2242
|
+
function parseForwardMessageEnvelope(value, requestId, kind) {
|
|
2243
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2244
|
+
return delivery &&
|
|
2245
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2246
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
2247
|
+
typeof value.sentAtMs === "number"
|
|
2248
|
+
? {
|
|
2249
|
+
kind,
|
|
2250
|
+
requestId,
|
|
2251
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2252
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2253
|
+
delivery,
|
|
2254
|
+
message: value.message,
|
|
2255
|
+
...(kind === "leader.forwardMessage" &&
|
|
2256
|
+
(value.forwardCommentBatchPosition === "comment" ||
|
|
2257
|
+
value.forwardCommentBatchPosition === "forward")
|
|
2258
|
+
? {
|
|
2259
|
+
forwardCommentBatchPosition: value.forwardCommentBatchPosition,
|
|
2260
|
+
}
|
|
2261
|
+
: {}),
|
|
2262
|
+
sentAtMs: value.sentAtMs,
|
|
2263
|
+
}
|
|
2264
|
+
: undefined;
|
|
2265
|
+
}
|
|
2266
|
+
function parseOfferInputCustodyHandoffEnvelope(value, requestId) {
|
|
2267
|
+
const source = value.source;
|
|
2268
|
+
if (!isRecord(source) ||
|
|
2269
|
+
typeof value.recipientInstanceId !== "string" ||
|
|
2270
|
+
typeof value.recipientRegistrationGeneration !== "string" ||
|
|
2271
|
+
typeof value.recipientBindingKey !== "string" ||
|
|
2272
|
+
!value.recipientBindingKey ||
|
|
2273
|
+
typeof value.sourceRecoveryKey !== "string" ||
|
|
2274
|
+
!value.sourceRecoveryKey ||
|
|
2275
|
+
value.sourceRecoveryKey.length > 1_024 ||
|
|
2276
|
+
typeof source.journalBindingKey !== "string" ||
|
|
2277
|
+
!source.journalBindingKey ||
|
|
2278
|
+
source.journalBindingKey !== value.sourceRecoveryKey ||
|
|
2279
|
+
typeof source.tokenSha256 !== "string" ||
|
|
2280
|
+
!/^[a-f0-9]{64}$/u.test(source.tokenSha256) ||
|
|
2281
|
+
!Number.isSafeInteger(source.updateId) ||
|
|
2282
|
+
source.updateId < 0 ||
|
|
2283
|
+
typeof value.handoffId !== "string" ||
|
|
2284
|
+
!value.handoffId ||
|
|
2285
|
+
value.handoffId.length > 256 ||
|
|
2286
|
+
typeof value.sentAtMs !== "number")
|
|
2287
|
+
return undefined;
|
|
2288
|
+
return {
|
|
2289
|
+
kind: "leader.offerInputCustodyHandoff",
|
|
2290
|
+
requestId,
|
|
2291
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2292
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2293
|
+
recipientBindingKey: value.recipientBindingKey,
|
|
2294
|
+
sourceRecoveryKey: value.sourceRecoveryKey,
|
|
2295
|
+
source: {
|
|
2296
|
+
journalBindingKey: source.journalBindingKey,
|
|
2297
|
+
tokenSha256: source.tokenSha256,
|
|
2298
|
+
updateId: source.updateId,
|
|
2299
|
+
},
|
|
2300
|
+
handoffId: value.handoffId,
|
|
2301
|
+
sentAtMs: value.sentAtMs,
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
function parseWakeInputCustodyEnvelope(value, requestId) {
|
|
2305
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2306
|
+
return delivery &&
|
|
2307
|
+
delivery.sourceRecoveryKey &&
|
|
2308
|
+
delivery.sourceClaim &&
|
|
2309
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2310
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
2311
|
+
typeof value.sentAtMs === "number"
|
|
2312
|
+
? {
|
|
2313
|
+
kind: "leader.wakeInputCustody",
|
|
2314
|
+
requestId,
|
|
2315
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2316
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2317
|
+
delivery,
|
|
2318
|
+
sentAtMs: value.sentAtMs,
|
|
2319
|
+
}
|
|
2320
|
+
: undefined;
|
|
2321
|
+
}
|
|
2322
|
+
function parseQueueHandoffEnvelope(value, requestId, kind) {
|
|
2323
|
+
let serializedPayloadBytes;
|
|
2324
|
+
try {
|
|
2325
|
+
serializedPayloadBytes = Buffer.byteLength(JSON.stringify(value.payload));
|
|
2326
|
+
}
|
|
2327
|
+
catch {
|
|
2328
|
+
return undefined;
|
|
2329
|
+
}
|
|
2330
|
+
if (serializedPayloadBytes > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
|
|
2331
|
+
return undefined;
|
|
2332
|
+
}
|
|
2333
|
+
const payload = parseTelegramQueueHandoffPayload(value.payload);
|
|
2334
|
+
if (!payload ||
|
|
2335
|
+
typeof value.recipientInstanceId !== "string" ||
|
|
2336
|
+
typeof value.recipientRegistrationGeneration !== "string" ||
|
|
2337
|
+
!Number.isSafeInteger(value.donorProcessId) ||
|
|
2338
|
+
value.donorProcessId <= 0 ||
|
|
2339
|
+
typeof value.donorProcessBirthId !== "string" ||
|
|
2340
|
+
!value.donorProcessBirthId ||
|
|
2341
|
+
!Number.isSafeInteger(value.donorSessionGeneration) ||
|
|
2342
|
+
value.donorSessionGeneration <= 0 ||
|
|
2343
|
+
typeof value.donorAcquisitionId !== "string" ||
|
|
2344
|
+
!value.donorAcquisitionId ||
|
|
2345
|
+
!Number.isSafeInteger(value.donorAcquiredAtMs) ||
|
|
2346
|
+
value.donorAcquiredAtMs < 0 ||
|
|
2347
|
+
typeof value.handoffToken !== "string" ||
|
|
2348
|
+
value.handoffToken.length < 32 ||
|
|
2349
|
+
value.handoffToken.length > 256 ||
|
|
2350
|
+
value.donorProcessBirthId.length > 256 ||
|
|
2351
|
+
value.donorAcquisitionId.length > 256 ||
|
|
2352
|
+
typeof value.sentAtMs !== "number") {
|
|
2353
|
+
return undefined;
|
|
2354
|
+
}
|
|
2355
|
+
const fields = {
|
|
2356
|
+
requestId,
|
|
2357
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2358
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2359
|
+
donorProcessId: value.donorProcessId,
|
|
2360
|
+
donorProcessBirthId: value.donorProcessBirthId,
|
|
2361
|
+
donorSessionGeneration: value.donorSessionGeneration,
|
|
2362
|
+
donorAcquisitionId: value.donorAcquisitionId,
|
|
2363
|
+
donorAcquiredAtMs: value.donorAcquiredAtMs,
|
|
2364
|
+
handoffToken: value.handoffToken,
|
|
2365
|
+
payload,
|
|
2366
|
+
sentAtMs: value.sentAtMs,
|
|
2367
|
+
};
|
|
2368
|
+
if (kind === "leader.offerQueueHandoff") {
|
|
2369
|
+
return typeof value.donorInstanceId === "string"
|
|
2370
|
+
? { kind, donorInstanceId: value.donorInstanceId, ...fields }
|
|
2371
|
+
: undefined;
|
|
2372
|
+
}
|
|
2373
|
+
return typeof value.instanceId === "string" &&
|
|
2374
|
+
typeof value.registrationGeneration === "string"
|
|
2375
|
+
? {
|
|
2376
|
+
kind,
|
|
2377
|
+
instanceId: value.instanceId,
|
|
2378
|
+
registrationGeneration: value.registrationGeneration,
|
|
2379
|
+
...fields,
|
|
2380
|
+
}
|
|
2381
|
+
: undefined;
|
|
2382
|
+
}
|
|
2383
|
+
function parseResolveAgentTargetEnvelope(value, requestId) {
|
|
2384
|
+
const selectorValue = isRecord(value.selector) ? value.selector : undefined;
|
|
2385
|
+
if (typeof value.instanceId !== "string" ||
|
|
2386
|
+
!selectorValue ||
|
|
2387
|
+
typeof value.sentAtMs !== "number") {
|
|
2388
|
+
return undefined;
|
|
2389
|
+
}
|
|
2390
|
+
const chatId = typeof selectorValue.chatId === "number" &&
|
|
2391
|
+
Number.isInteger(selectorValue.chatId)
|
|
2392
|
+
? selectorValue.chatId
|
|
2393
|
+
: undefined;
|
|
2394
|
+
const threadId = typeof selectorValue.threadId === "number" &&
|
|
2395
|
+
Number.isInteger(selectorValue.threadId) &&
|
|
2396
|
+
selectorValue.threadId > 0
|
|
2397
|
+
? selectorValue.threadId
|
|
2398
|
+
: undefined;
|
|
2399
|
+
const threadName = typeof selectorValue.threadName === "string" &&
|
|
2400
|
+
selectorValue.threadName.trim()
|
|
2401
|
+
? selectorValue.threadName.trim()
|
|
2402
|
+
: undefined;
|
|
2403
|
+
if ((threadId === undefined) === (threadName === undefined))
|
|
2404
|
+
return undefined;
|
|
2405
|
+
return {
|
|
2406
|
+
kind: "follower.resolveAgentTarget",
|
|
2407
|
+
requestId,
|
|
2408
|
+
instanceId: value.instanceId,
|
|
2409
|
+
...(typeof value.registrationGeneration === "string"
|
|
2410
|
+
? { registrationGeneration: value.registrationGeneration }
|
|
2411
|
+
: {}),
|
|
2412
|
+
selector: {
|
|
2413
|
+
...(chatId !== undefined ? { chatId } : {}),
|
|
2414
|
+
...(threadId !== undefined ? { threadId } : {}),
|
|
2415
|
+
...(threadName !== undefined ? { threadName } : {}),
|
|
2416
|
+
},
|
|
2417
|
+
sentAtMs: value.sentAtMs,
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
function parseRouteAgentMessageEnvelope(value, requestId) {
|
|
2421
|
+
const messageValue = isRecord(value.message) ? value.message : undefined;
|
|
2422
|
+
const target = parseThreadTarget(messageValue?.target);
|
|
2423
|
+
if (typeof value.instanceId !== "string" ||
|
|
2424
|
+
!messageValue ||
|
|
2425
|
+
!target ||
|
|
2426
|
+
typeof messageValue.messageId !== "number" ||
|
|
2427
|
+
!Number.isInteger(messageValue.messageId) ||
|
|
2428
|
+
messageValue.messageId <= 0 ||
|
|
2429
|
+
typeof messageValue.text !== "string" ||
|
|
2430
|
+
!messageValue.text.trim() ||
|
|
2431
|
+
typeof value.sentAtMs !== "number") {
|
|
2432
|
+
return undefined;
|
|
2433
|
+
}
|
|
2434
|
+
return {
|
|
2435
|
+
kind: "follower.routeAgentMessage",
|
|
2436
|
+
requestId,
|
|
2437
|
+
instanceId: value.instanceId,
|
|
2438
|
+
...(typeof value.registrationGeneration === "string"
|
|
2439
|
+
? { registrationGeneration: value.registrationGeneration }
|
|
2440
|
+
: {}),
|
|
2441
|
+
message: {
|
|
2442
|
+
target,
|
|
2443
|
+
messageId: messageValue.messageId,
|
|
2444
|
+
text: messageValue.text,
|
|
2445
|
+
},
|
|
2446
|
+
sentAtMs: value.sentAtMs,
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
const nonempty = (input, limit = 128) => typeof input === "string" && !!input.trim() && input.length <= limit;
|
|
2450
|
+
const positive = (input) => Number.isSafeInteger(input) && input > 0;
|
|
2451
|
+
const nonnegative = (input) => Number.isSafeInteger(input) && input >= 0;
|
|
2452
|
+
/** Shared closed text-effect codec; parsing alone supplies no recipient or issuance authority. */
|
|
2453
|
+
export function parseTelegramBusSelectedMenuTextEffect(effect) {
|
|
2454
|
+
if (!isRecord(effect) ||
|
|
2455
|
+
(effect.kind !== "send-text" && effect.kind !== "edit-text") ||
|
|
2456
|
+
!hasOnlyWireKeys(effect, [
|
|
2457
|
+
"kind",
|
|
2458
|
+
"text",
|
|
2459
|
+
"parseMode",
|
|
2460
|
+
"replyMarkup",
|
|
2461
|
+
...(effect.kind === "send-text" ? ["replyToMessageId"] : ["messageId"]),
|
|
2462
|
+
]) ||
|
|
2463
|
+
!nonempty(effect.text, 4096) ||
|
|
2464
|
+
(effect.parseMode !== undefined && effect.parseMode !== "HTML") ||
|
|
2465
|
+
(effect.kind === "send-text"
|
|
2466
|
+
? effect.replyToMessageId !== undefined &&
|
|
2467
|
+
!positive(effect.replyToMessageId)
|
|
2468
|
+
: !positive(effect.messageId)))
|
|
2469
|
+
return undefined;
|
|
2470
|
+
let replyMarkup;
|
|
2471
|
+
if (effect.replyMarkup !== undefined) {
|
|
2472
|
+
const markup = effect.replyMarkup;
|
|
2473
|
+
if (!isRecord(markup) ||
|
|
2474
|
+
!hasOnlyWireKeys(markup, ["inline_keyboard"]) ||
|
|
2475
|
+
!Array.isArray(markup.inline_keyboard) ||
|
|
2476
|
+
markup.inline_keyboard.length > 32 ||
|
|
2477
|
+
markup.inline_keyboard.some((row) => !Array.isArray(row) ||
|
|
2478
|
+
!row.length ||
|
|
2479
|
+
row.length > 8 ||
|
|
2480
|
+
row.some((button) => !isRecord(button) ||
|
|
2481
|
+
!hasOnlyWireKeys(button, ["text", "callback_data"]) ||
|
|
2482
|
+
!nonempty(button.text) ||
|
|
2483
|
+
!nonempty(button.callback_data, 64) ||
|
|
2484
|
+
Buffer.byteLength(button.callback_data, "utf8") > 64)))
|
|
2485
|
+
return undefined;
|
|
2486
|
+
replyMarkup = {
|
|
2487
|
+
inline_keyboard: markup.inline_keyboard.map((row) => row.map((button) => ({
|
|
2488
|
+
text: button.text,
|
|
2489
|
+
callback_data: button.callback_data,
|
|
2490
|
+
}))),
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
const common = {
|
|
2494
|
+
text: effect.text,
|
|
2495
|
+
...(effect.parseMode === "HTML" ? { parseMode: "HTML" } : {}),
|
|
2496
|
+
...(replyMarkup ? { replyMarkup } : {}),
|
|
2497
|
+
};
|
|
2498
|
+
return effect.kind === "send-text"
|
|
2499
|
+
? {
|
|
2500
|
+
...common,
|
|
2501
|
+
kind: "send-text",
|
|
2502
|
+
...(effect.replyToMessageId !== undefined
|
|
2503
|
+
? { replyToMessageId: effect.replyToMessageId }
|
|
2504
|
+
: {}),
|
|
2505
|
+
}
|
|
2506
|
+
: { ...common, kind: "edit-text", messageId: effect.messageId };
|
|
2507
|
+
}
|
|
2508
|
+
function parseSelectedMenuDeliveryEnvelope(value, requestId) {
|
|
2509
|
+
const recipient = value.recipient, executor = value.executor, effect = parseTelegramBusSelectedMenuTextEffect(value.effect);
|
|
2510
|
+
if (!hasOnlyWireKeys(value, [
|
|
2511
|
+
"kind",
|
|
2512
|
+
"requestId",
|
|
2513
|
+
"auth",
|
|
2514
|
+
"instanceId",
|
|
2515
|
+
"registrationGeneration",
|
|
2516
|
+
"operationId",
|
|
2517
|
+
"recipient",
|
|
2518
|
+
"executor",
|
|
2519
|
+
"operatorUserId",
|
|
2520
|
+
"effect",
|
|
2521
|
+
"sentAtMs",
|
|
2522
|
+
]) ||
|
|
2523
|
+
!nonempty(requestId) ||
|
|
2524
|
+
!nonempty(value.instanceId) ||
|
|
2525
|
+
!nonempty(value.registrationGeneration) ||
|
|
2526
|
+
!nonempty(value.operationId) ||
|
|
2527
|
+
!isRecord(recipient) ||
|
|
2528
|
+
!hasOnlyWireKeys(recipient, [
|
|
2529
|
+
"sessionId",
|
|
2530
|
+
"sessionGeneration",
|
|
2531
|
+
"processId",
|
|
2532
|
+
"processBirthId",
|
|
2533
|
+
"profileKey",
|
|
2534
|
+
"journalBindingKey",
|
|
2535
|
+
"target",
|
|
2536
|
+
]) ||
|
|
2537
|
+
!nonempty(recipient.sessionId) ||
|
|
2538
|
+
!nonnegative(recipient.sessionGeneration) ||
|
|
2539
|
+
!positive(recipient.processId) ||
|
|
2540
|
+
!nonempty(recipient.processBirthId) ||
|
|
2541
|
+
!nonempty(recipient.profileKey, 1024) ||
|
|
2542
|
+
!nonempty(recipient.journalBindingKey, 8192) ||
|
|
2543
|
+
recipient.profileKey === recipient.journalBindingKey ||
|
|
2544
|
+
!isRecord(recipient.target) ||
|
|
2545
|
+
!hasOnlyWireKeys(recipient.target, ["chatId", "threadId"]) ||
|
|
2546
|
+
!positive(recipient.target.chatId) ||
|
|
2547
|
+
!positive(recipient.target.threadId) ||
|
|
2548
|
+
!positive(value.operatorUserId) ||
|
|
2549
|
+
value.operatorUserId !== recipient.target.chatId ||
|
|
2550
|
+
!isRecord(executor) ||
|
|
2551
|
+
!hasOnlyWireKeys(executor, ["instanceId", "leaderEpoch"]) ||
|
|
2552
|
+
!nonempty(executor.instanceId) ||
|
|
2553
|
+
!nonempty(executor.leaderEpoch) ||
|
|
2554
|
+
!effect ||
|
|
2555
|
+
typeof value.sentAtMs !== "number" ||
|
|
2556
|
+
!Number.isFinite(value.sentAtMs) ||
|
|
2557
|
+
value.sentAtMs < 0 ||
|
|
2558
|
+
(value.auth !== undefined && typeof value.auth !== "string"))
|
|
2559
|
+
return undefined;
|
|
2560
|
+
return {
|
|
2561
|
+
kind: "follower.deliverSelectedMenu",
|
|
2562
|
+
requestId,
|
|
2563
|
+
instanceId: value.instanceId,
|
|
2564
|
+
registrationGeneration: value.registrationGeneration,
|
|
2565
|
+
operationId: value.operationId,
|
|
2566
|
+
recipient: {
|
|
2567
|
+
sessionId: recipient.sessionId,
|
|
2568
|
+
sessionGeneration: recipient.sessionGeneration,
|
|
2569
|
+
processId: recipient.processId,
|
|
2570
|
+
processBirthId: recipient.processBirthId,
|
|
2571
|
+
profileKey: recipient.profileKey,
|
|
2572
|
+
journalBindingKey: recipient.journalBindingKey,
|
|
2573
|
+
target: {
|
|
2574
|
+
chatId: recipient.target.chatId,
|
|
2575
|
+
threadId: recipient.target.threadId,
|
|
2576
|
+
},
|
|
2577
|
+
},
|
|
2578
|
+
executor: {
|
|
2579
|
+
instanceId: executor.instanceId,
|
|
2580
|
+
leaderEpoch: executor.leaderEpoch,
|
|
2581
|
+
},
|
|
2582
|
+
operatorUserId: value.operatorUserId,
|
|
2583
|
+
effect,
|
|
2584
|
+
sentAtMs: value.sentAtMs,
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
function parseCallApiEnvelope(value, requestId) {
|
|
2588
|
+
return typeof value.instanceId === "string" &&
|
|
2589
|
+
typeof value.method === "string" &&
|
|
2590
|
+
Array.isArray(value.args) &&
|
|
2591
|
+
typeof value.sentAtMs === "number"
|
|
2592
|
+
? {
|
|
2593
|
+
kind: "follower.callApi",
|
|
2594
|
+
requestId,
|
|
2595
|
+
instanceId: value.instanceId,
|
|
2596
|
+
...(typeof value.registrationGeneration === "string"
|
|
2597
|
+
? { registrationGeneration: value.registrationGeneration }
|
|
2598
|
+
: {}),
|
|
2599
|
+
method: value.method,
|
|
2600
|
+
args: value.args,
|
|
2601
|
+
sentAtMs: value.sentAtMs,
|
|
2602
|
+
}
|
|
2603
|
+
: undefined;
|
|
2604
|
+
}
|
|
2605
|
+
function parseAckEnvelope(value, requestId) {
|
|
2606
|
+
if (typeof value.ok !== "boolean")
|
|
2607
|
+
return undefined;
|
|
2608
|
+
const envelope = {
|
|
2609
|
+
kind: "bus.ack",
|
|
2610
|
+
requestId,
|
|
2611
|
+
ok: value.ok,
|
|
2612
|
+
message: typeof value.message === "string" ? value.message : undefined,
|
|
2613
|
+
};
|
|
2614
|
+
if (Object.hasOwn(value, "result"))
|
|
2615
|
+
envelope.result = value.result;
|
|
2616
|
+
const protocol = parseTelegramBusProtocolIdentity(value.protocol);
|
|
2617
|
+
if (protocol)
|
|
2618
|
+
envelope.protocol = protocol;
|
|
2619
|
+
if (isRecord(value.error)) {
|
|
2620
|
+
const code = value.error.code;
|
|
2621
|
+
if (code === "commit-unknown" ||
|
|
2622
|
+
code === "request-id-collision" ||
|
|
2623
|
+
code === "ledger-overloaded" ||
|
|
2624
|
+
code === "incompatible-protocol" ||
|
|
2625
|
+
code === "stale-target" ||
|
|
2626
|
+
code === "workspace-binding-unavailable") {
|
|
2627
|
+
const chatId = value.error.chatId;
|
|
2628
|
+
const threadId = value.error.threadId;
|
|
2629
|
+
if (code === "stale-target" &&
|
|
2630
|
+
(!Number.isSafeInteger(chatId) || !Number.isSafeInteger(threadId))) {
|
|
2631
|
+
return undefined;
|
|
2632
|
+
}
|
|
2633
|
+
envelope.error = {
|
|
2634
|
+
code,
|
|
2635
|
+
...(typeof value.error.method === "string"
|
|
2636
|
+
? { method: value.error.method }
|
|
2637
|
+
: {}),
|
|
2638
|
+
...(typeof chatId === "number" ? { chatId } : {}),
|
|
2639
|
+
...(typeof threadId === "number" ? { threadId } : {}),
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
return envelope;
|
|
2644
|
+
}
|
|
2645
|
+
function parseTelegramBusProtocolIdentity(value) {
|
|
2646
|
+
if (!isRecord(value))
|
|
2647
|
+
return undefined;
|
|
2648
|
+
if (!Number.isSafeInteger(value.protocolVersion) ||
|
|
2649
|
+
value.protocolVersion <= 0 ||
|
|
2650
|
+
typeof value.runtimeBuild !== "string" ||
|
|
2651
|
+
!value.runtimeBuild.trim() ||
|
|
2652
|
+
value.runtimeBuild !== value.runtimeBuild.trim() ||
|
|
2653
|
+
value.runtimeBuild.length > 128 ||
|
|
2654
|
+
!Array.isArray(value.capabilities) ||
|
|
2655
|
+
value.capabilities.length > 32 ||
|
|
2656
|
+
value.capabilities.some((capability) => typeof capability !== "string" ||
|
|
2657
|
+
capability.length > 128 ||
|
|
2658
|
+
!/^[a-z0-9][a-z0-9._-]*$/u.test(capability))) {
|
|
2659
|
+
return undefined;
|
|
2660
|
+
}
|
|
2661
|
+
const capabilities = value.capabilities;
|
|
2662
|
+
if (capabilities.some((capability, index) => index > 0 && capability <= capabilities[index - 1])) {
|
|
2663
|
+
return undefined;
|
|
2664
|
+
}
|
|
2665
|
+
return {
|
|
2666
|
+
protocolVersion: value.protocolVersion,
|
|
2667
|
+
runtimeBuild: value.runtimeBuild,
|
|
2668
|
+
capabilities: [...capabilities],
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function parseRegistration(value) {
|
|
2672
|
+
if (!isRecord(value))
|
|
2673
|
+
return undefined;
|
|
2674
|
+
if (typeof value.instanceId !== "string")
|
|
2675
|
+
return undefined;
|
|
2676
|
+
if (typeof value.connectedAtMs !== "number")
|
|
2677
|
+
return undefined;
|
|
2678
|
+
const target = parseTarget(value.target);
|
|
2679
|
+
if (value.target !== undefined && !target)
|
|
2680
|
+
return undefined;
|
|
2681
|
+
const registration = {
|
|
2682
|
+
instanceId: value.instanceId,
|
|
2683
|
+
connectedAtMs: value.connectedAtMs,
|
|
2684
|
+
};
|
|
2685
|
+
if (typeof value.previousInstanceId === "string") {
|
|
2686
|
+
registration.previousInstanceId = value.previousInstanceId;
|
|
2687
|
+
}
|
|
2688
|
+
if (typeof value.profileKey === "string")
|
|
2689
|
+
registration.profileKey = value.profileKey;
|
|
2690
|
+
if (typeof value.threadName === "string")
|
|
2691
|
+
registration.threadName = value.threadName;
|
|
2692
|
+
if (typeof value.slot === "string" && /^[A-Z]$/.test(value.slot)) {
|
|
2693
|
+
registration.slot = value.slot;
|
|
2694
|
+
}
|
|
2695
|
+
if (typeof value.cwd === "string")
|
|
2696
|
+
registration.cwd = value.cwd;
|
|
2697
|
+
if (value.sessionId !== undefined) {
|
|
2698
|
+
if (typeof value.sessionId !== "string" ||
|
|
2699
|
+
!value.sessionId ||
|
|
2700
|
+
value.sessionId !== value.sessionId.trim() ||
|
|
2701
|
+
Buffer.byteLength(value.sessionId, "utf8") > 256)
|
|
2702
|
+
return undefined;
|
|
2703
|
+
registration.sessionId = value.sessionId;
|
|
2704
|
+
}
|
|
2705
|
+
if (typeof value.pid === "number")
|
|
2706
|
+
registration.pid = value.pid;
|
|
2707
|
+
if (typeof value.busSocketPath === "string") {
|
|
2708
|
+
registration.busSocketPath = value.busSocketPath;
|
|
2709
|
+
}
|
|
2710
|
+
if (typeof value.registrationGeneration === "string") {
|
|
2711
|
+
registration.registrationGeneration = value.registrationGeneration;
|
|
2712
|
+
}
|
|
2713
|
+
if (Number.isSafeInteger(value.sessionGeneration) &&
|
|
2714
|
+
value.sessionGeneration > 0) {
|
|
2715
|
+
registration.sessionGeneration = value.sessionGeneration;
|
|
2716
|
+
}
|
|
2717
|
+
if (typeof value.processBirthId === "string" && value.processBirthId) {
|
|
2718
|
+
registration.processBirthId = value.processBirthId;
|
|
2719
|
+
}
|
|
2720
|
+
const protocol = parseTelegramBusProtocolIdentity(value.protocol);
|
|
2721
|
+
if (protocol)
|
|
2722
|
+
registration.protocol = protocol;
|
|
2723
|
+
if (target)
|
|
2724
|
+
registration.target = target;
|
|
2725
|
+
return registration;
|
|
2726
|
+
}
|
|
2727
|
+
function parseThreadTarget(value) {
|
|
2728
|
+
const target = parseTarget(value);
|
|
2729
|
+
return target && typeof target.threadId === "number"
|
|
2730
|
+
? { chatId: target.chatId, threadId: target.threadId }
|
|
2731
|
+
: undefined;
|
|
2732
|
+
}
|