@gajae-code/coding-agent 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -2
- package/dist/types/append-only-mode.d.ts +1 -1
- package/dist/types/cli/args.d.ts +4 -5
- package/dist/types/cli/daemon-cli.d.ts +12 -1
- package/dist/types/cli/notify-cli.d.ts +21 -1
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +39 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/notify.d.ts +28 -1
- package/dist/types/commands/sdk.d.ts +33 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
- package/dist/types/config/model-profile-activation.d.ts +35 -15
- package/dist/types/config/model-profiles.d.ts +11 -5
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +39 -6
- package/dist/types/config/model-selector-value.d.ts +17 -0
- package/dist/types/config/models-config-schema.d.ts +10 -6
- package/dist/types/config/settings-schema.d.ts +133 -6
- package/dist/types/config/settings.d.ts +63 -35
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
- package/dist/types/coordinator-mcp/server.d.ts +6 -49
- package/dist/types/daemon/builtin.d.ts +6 -5
- package/dist/types/daemon/control-types.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +12 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
- package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +10 -1
- package/dist/types/extensibility/extensions/runner.d.ts +3 -3
- package/dist/types/extensibility/extensions/types.d.ts +141 -16
- package/dist/types/extensibility/hooks/types.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
- package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
- package/dist/types/harness-control-plane/operate.d.ts +5 -5
- package/dist/types/harness-control-plane/owner.d.ts +18 -4
- package/dist/types/harness-control-plane/receipts.d.ts +0 -1
- package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
- package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
- package/dist/types/harness-control-plane/types.d.ts +3 -4
- package/dist/types/lsp/index.d.ts +1 -1
- package/dist/types/main.d.ts +32 -38
- package/dist/types/modes/acp/acp-agent.d.ts +224 -53
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
- package/dist/types/modes/acp/acp-mode.d.ts +6 -15
- package/dist/types/modes/acp/permission-mode.d.ts +4 -0
- package/dist/types/modes/acp/startup-options.d.ts +6 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -4
- package/dist/types/modes/components/welcome.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
- package/dist/types/modes/index.d.ts +0 -4
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/runtime-init.d.ts +2 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
- package/dist/types/modes/types.d.ts +5 -0
- package/dist/types/runtime-mcp/client.d.ts +2 -0
- package/dist/types/runtime-mcp/config.d.ts +5 -1
- package/dist/types/runtime-mcp/manager.d.ts +8 -1
- package/dist/types/runtime-mcp/types.d.ts +4 -0
- package/dist/types/sdk/acp/adapter.d.ts +67 -0
- package/dist/types/sdk/acp/index.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +53 -0
- package/dist/types/sdk/broker/discovery.d.ts +30 -0
- package/dist/types/sdk/broker/ensure.d.ts +33 -0
- package/dist/types/sdk/broker/identity.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +8 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
- package/dist/types/sdk/broker/runtime.d.ts +33 -0
- package/dist/types/sdk/broker/session-index.d.ts +57 -0
- package/dist/types/sdk/broker/state-version.d.ts +8 -0
- package/dist/types/sdk/broker/transport.d.ts +9 -0
- package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
- package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
- package/dist/types/sdk/bus/config.d.ts +274 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
- package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
- package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
- package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
- package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
- package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
- package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
- package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
- package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
- package/dist/types/sdk/bus/session-control.d.ts +83 -0
- package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
- package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
- package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
- package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
- package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
- package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
- package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
- package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/session-cli.d.ts +19 -0
- package/dist/types/sdk/client/client.d.ts +1 -0
- package/dist/types/sdk/client/discovery.d.ts +27 -0
- package/dist/types/sdk/client/index.d.ts +2 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
- package/dist/types/sdk/host/control/index.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +65 -0
- package/dist/types/sdk/host/events.d.ts +35 -0
- package/dist/types/sdk/host/host.d.ts +35 -0
- package/dist/types/sdk/host/index.d.ts +4 -0
- package/dist/types/sdk/host/query/cursor.d.ts +62 -0
- package/dist/types/sdk/host/query/handlers.d.ts +70 -0
- package/dist/types/sdk/host/query/index.d.ts +3 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
- package/dist/types/sdk/host/types.d.ts +32 -0
- package/dist/types/sdk/index.d.ts +6 -0
- package/dist/types/sdk/lifecycle-session.d.ts +14 -0
- package/dist/types/sdk/mcp/index.d.ts +1 -0
- package/dist/types/sdk/mcp/server.d.ts +37 -0
- package/dist/types/sdk/models.d.ts +44 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
- package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
- package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
- package/dist/types/sdk/startup-capability.d.ts +50 -0
- package/dist/types/session/agent-session.d.ts +114 -21
- package/dist/types/session/default-model-selection.d.ts +25 -0
- package/dist/types/session/fallback-chain-controller.d.ts +52 -0
- package/dist/types/session/session-manager.d.ts +58 -1
- package/dist/types/setup/credential-auto-import.d.ts +36 -6
- package/dist/types/tools/ask-answer-registry.d.ts +1 -1
- package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
- package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
- package/dist/types/tools/browser/launch.d.ts +22 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
- package/dist/types/tools/browser/profile-posture.d.ts +37 -0
- package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
- package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
- package/dist/types/tools/browser/registry.d.ts +19 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +8 -8
- package/package.json +34 -13
- package/scripts/build-sdk-package-smoke.ts +94 -0
- package/scripts/capture-notifications-settings-showcase.ts +435 -0
- package/scripts/compile-args.ts +4 -2
- package/scripts/g005-daemon-smoke.ts +4 -5
- package/scripts/g011-daemon-path-smoke.ts +5 -5
- package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
- package/scripts/generate-sdk-operation-inventory.ts +867 -0
- package/scripts/generate-telegram-baseline-manifest.ts +128 -0
- package/scripts/manifest-schema.ts +124 -0
- package/scripts/run-test-manifest.ts +153 -0
- package/scripts/run-threat-model-check.ts +157 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
- package/scripts/verify-gjc-sdk-rename.ts +90 -0
- package/src/append-only-mode.ts +1 -1
- package/src/cli/args.ts +43 -12
- package/src/cli/auth-gateway-cli.ts +2 -2
- package/src/cli/config-cli.ts +2 -1
- package/src/cli/daemon-cli.ts +63 -14
- package/src/cli/list-models.ts +1 -1
- package/src/cli/notify-cli.ts +288 -286
- package/src/cli.ts +21 -2
- package/src/commands/daemon.ts +50 -6
- package/src/commands/harness.ts +93 -36
- package/src/commands/launch.ts +5 -2
- package/src/commands/mcp-serve.ts +19 -12
- package/src/commands/notify.ts +36 -3
- package/src/commands/sdk.ts +426 -0
- package/src/commit/agentic/agent.ts +1 -0
- package/src/config/atomic-yaml-patch.ts +423 -0
- package/src/config/model-profile-activation.ts +240 -89
- package/src/config/model-profiles.ts +18 -15
- package/src/config/model-registry.ts +51 -26
- package/src/config/model-resolver.ts +138 -51
- package/src/config/model-selector-value.ts +50 -0
- package/src/config/models-config-schema.ts +14 -21
- package/src/config/settings-schema.ts +128 -10
- package/src/config/settings.ts +1120 -158
- package/src/coordinator-mcp/model-preset.ts +74 -20
- package/src/coordinator-mcp/server.ts +1597 -3052
- package/src/daemon/builtin.ts +8 -5
- package/src/daemon/control-types.ts +1 -1
- package/src/defaults/gjc/skills/team/SKILL.md +4 -4
- package/src/discovery/mcp-json.ts +327 -20
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/compact-handler.ts +3 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -2
- package/src/extensibility/extensions/loader.ts +69 -5
- package/src/extensibility/extensions/runner.ts +112 -4
- package/src/extensibility/extensions/types.ts +176 -17
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +170 -86
- package/src/gjc-runtime/psmux-detect.ts +82 -4
- package/src/gjc-runtime/session-state-sidecar.ts +41 -31
- package/src/gjc-runtime/team-runtime.ts +54 -35
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +1 -1
- package/src/gjc-runtime/tmux-sessions.ts +19 -13
- package/src/gjc-runtime/ultragoal-guard.ts +53 -6
- package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
- package/src/gjc-runtime/windows-powershell-command.ts +58 -0
- package/src/harness-control-plane/operate.ts +9 -9
- package/src/harness-control-plane/owner.ts +283 -39
- package/src/harness-control-plane/receipts.ts +0 -1
- package/src/harness-control-plane/sdk-transport.ts +395 -0
- package/src/harness-control-plane/session-transport.ts +94 -0
- package/src/harness-control-plane/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +34 -35
- package/src/lsp/client.ts +42 -6
- package/src/lsp/index.ts +2 -2
- package/src/main.ts +299 -204
- package/src/modes/DESIGN.md +258 -0
- package/src/modes/acp/acp-agent.ts +1228 -2612
- package/src/modes/acp/acp-client-bridge.ts +2 -20
- package/src/modes/acp/acp-event-mapper.ts +18 -2
- package/src/modes/acp/acp-mode.ts +8 -36
- package/src/modes/acp/permission-mode.ts +25 -0
- package/src/modes/acp/startup-options.ts +6 -0
- package/src/modes/components/agent-dashboard.ts +37 -17
- package/src/modes/components/model-selector.ts +93 -35
- package/src/modes/components/notifications-settings-editor.ts +1379 -0
- package/src/modes/components/settings-selector.ts +60 -11
- package/src/modes/components/welcome.ts +4 -4
- package/src/modes/controllers/command-controller.ts +1 -2
- package/src/modes/controllers/event-controller.ts +11 -16
- package/src/modes/controllers/extension-ui-controller.ts +348 -4
- package/src/modes/controllers/selector-controller.ts +674 -36
- package/src/modes/index.ts +0 -22
- package/src/modes/interactive-mode.ts +59 -20
- package/src/modes/runtime-init.ts +298 -6
- package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
- package/src/modes/shared/agent-wire/event-contract.ts +7 -8
- package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
- package/src/modes/shared/agent-wire/event-observation.ts +16 -7
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
- package/src/modes/types.ts +5 -0
- package/src/runtime-mcp/client.ts +94 -19
- package/src/runtime-mcp/config.ts +27 -15
- package/src/runtime-mcp/manager.ts +343 -105
- package/src/runtime-mcp/oauth-flow.ts +42 -12
- package/src/runtime-mcp/transports/http.ts +49 -19
- package/src/runtime-mcp/transports/stdio.ts +47 -28
- package/src/runtime-mcp/types.ts +10 -0
- package/src/sdk/acp/adapter.ts +418 -0
- package/src/sdk/acp/index.ts +2 -0
- package/src/sdk/broker/broker.ts +642 -0
- package/src/sdk/broker/discovery.ts +94 -0
- package/src/sdk/broker/ensure.ts +249 -0
- package/src/sdk/broker/identity.ts +42 -0
- package/src/sdk/broker/index.ts +8 -0
- package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
- package/src/sdk/broker/internal-source.bunfig.toml +1 -0
- package/src/sdk/broker/lifecycle-ledger.ts +286 -0
- package/src/sdk/broker/lifecycle.ts +2157 -0
- package/src/sdk/broker/process-incarnation.ts +159 -0
- package/src/sdk/broker/runtime.ts +164 -0
- package/src/sdk/broker/session-index.ts +318 -0
- package/src/sdk/broker/state-version.ts +23 -0
- package/src/sdk/broker/transport.ts +125 -0
- package/src/sdk/bus/chat-command-policy.ts +107 -0
- package/src/sdk/bus/chat-daemon-cli.ts +214 -0
- package/src/sdk/bus/chat-daemon-control.ts +713 -0
- package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
- package/src/sdk/bus/chat-effect-journal.ts +353 -0
- package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
- package/src/sdk/bus/config.ts +433 -0
- package/src/sdk/bus/conversation-store.ts +391 -0
- package/src/sdk/bus/discord-conversation.ts +85 -0
- package/src/sdk/bus/discord-daemon.ts +2275 -0
- package/src/sdk/bus/discord-live-provider.ts +521 -0
- package/src/sdk/bus/discord-provider.ts +56 -0
- package/src/{notifications → sdk/bus}/html-format.ts +2 -2
- package/src/sdk/bus/index.ts +3943 -0
- package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
- package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
- package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
- package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
- package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
- package/src/sdk/bus/natives-augment.d.ts +38 -0
- package/src/sdk/bus/notification-orchestration.ts +535 -0
- package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
- package/src/sdk/bus/session-control.ts +333 -0
- package/src/sdk/bus/slack-conversation.ts +74 -0
- package/src/sdk/bus/slack-daemon.ts +2144 -0
- package/src/sdk/bus/slack-live-provider.ts +380 -0
- package/src/sdk/bus/slack-provider.ts +76 -0
- package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
- package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
- package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
- package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
- package/src/sdk/bus/telegram-setup.ts +663 -0
- package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/session-cli.ts +275 -0
- package/src/sdk/client/client.ts +1 -0
- package/src/sdk/client/discovery.ts +108 -0
- package/src/sdk/client/index.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +367 -0
- package/src/sdk/host/control/index.ts +10 -0
- package/src/sdk/host/control/operations.ts +63 -0
- package/src/sdk/host/events.ts +91 -0
- package/src/sdk/host/host.ts +296 -0
- package/src/sdk/host/index.ts +4 -0
- package/src/sdk/host/query/cursor.ts +189 -0
- package/src/sdk/host/query/handlers.ts +533 -0
- package/src/sdk/host/query/index.ts +3 -0
- package/src/sdk/host/query/revision-store.ts +1126 -0
- package/src/sdk/host/reverse-leases.ts +325 -0
- package/src/sdk/host/types.ts +29 -0
- package/src/sdk/index.ts +13 -0
- package/src/sdk/lifecycle-session.ts +39 -0
- package/src/sdk/mcp/index.ts +1 -0
- package/src/sdk/mcp/server.ts +332 -0
- package/src/sdk/models.ts +168 -0
- package/src/sdk/protocol/adapter-validation.ts +48 -0
- package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
- package/src/sdk/protocol/operation-registry.ts +312 -0
- package/src/{sdk.ts → sdk/session.ts} +339 -212
- package/src/sdk/startup-capability.ts +189 -0
- package/src/session/agent-session.ts +2453 -894
- package/src/session/default-model-selection.ts +93 -0
- package/src/session/fallback-chain-controller.ts +136 -0
- package/src/session/session-manager.ts +438 -11
- package/src/session/session-storage.ts +8 -1
- package/src/setup/credential-auto-import.ts +290 -35
- package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
- package/src/slash-commands/builtin-registry.ts +2 -1
- package/src/task/executor.ts +62 -13
- package/src/tools/ask-answer-registry.ts +1 -1
- package/src/tools/ask.ts +13 -8
- package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
- package/src/tools/browser/benchmark/detector-report.ts +157 -0
- package/src/tools/browser/benchmark/run-suite.ts +39 -0
- package/src/tools/browser/launch.ts +124 -19
- package/src/tools/browser/profile-discovery.ts +63 -0
- package/src/tools/browser/profile-posture.ts +59 -0
- package/src/tools/browser/profile-reuse.ts +102 -0
- package/src/tools/browser/profile-warmup.ts +109 -0
- package/src/tools/browser/registry.ts +87 -11
- package/src/tools/browser/tab-protocol.ts +1 -0
- package/src/tools/browser/tab-supervisor.ts +1 -0
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +6 -0
- package/src/tools/index.ts +8 -8
- package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
- package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
- package/src/tools/telegram-send.ts +2 -2
- package/vendor/insane-search/MANIFEST.json +2 -1
- package/vendor/insane-search/engine/bias_check.py +4 -1
- package/vendor/insane-search/engine/phase0.py +26 -1
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
- package/dist/types/modes/bridge/auth.d.ts +0 -12
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
- package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
- package/dist/types/modes/bridge/event-stream.d.ts +0 -8
- package/dist/types/modes/rpc/host-tools.d.ts +0 -1
- package/dist/types/modes/rpc/host-uris.d.ts +0 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
- package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
- package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
- package/dist/types/notifications/config.d.ts +0 -132
- package/src/harness-control-plane/rpc-adapter.ts +0 -290
- package/src/modes/bridge/auth.ts +0 -41
- package/src/modes/bridge/bridge-client-bridge.ts +0 -47
- package/src/modes/bridge/bridge-mode.ts +0 -675
- package/src/modes/bridge/bridge-ui-context.ts +0 -200
- package/src/modes/bridge/event-stream.ts +0 -70
- package/src/modes/rpc/host-tools.ts +0 -1
- package/src/modes/rpc/host-uris.ts +0 -1
- package/src/modes/rpc/rpc-client.ts +0 -1166
- package/src/modes/rpc/rpc-mode.ts +0 -1029
- package/src/modes/rpc/rpc-socket-security.ts +0 -115
- package/src/modes/rpc/rpc-types.ts +0 -637
- package/src/modes/shared/agent-wire/command-contract.ts +0 -18
- package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
- package/src/modes/shared/agent-wire/command-validation.ts +0 -181
- package/src/modes/shared/agent-wire/handshake.ts +0 -157
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
- package/src/modes/shared/agent-wire/protocol.ts +0 -46
- package/src/modes/shared/agent-wire/responses.ts +0 -17
- package/src/modes/shared/agent-wire/scopes.ts +0 -94
- package/src/modes/shared/agent-wire/session-registry.ts +0 -109
- package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
- package/src/modes/shared/agent-wire/ui-result.ts +0 -48
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
- package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
- package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
- package/src/notifications/config.ts +0 -219
- package/src/notifications/index.ts +0 -1835
- package/src/notifications/telegram-daemon-cli.ts +0 -243
- package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
- /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
- /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
- /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
- /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
- /package/src/{notifications → sdk/bus}/engine.ts +0 -0
- /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
- /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
- /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
- /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
- /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
- /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
- /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
- /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
- /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import * as fsPromises from "node:fs/promises";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
-
import type { Settings } from "
|
|
15
|
+
import type { Settings } from "../../config/settings";
|
|
16
16
|
import {
|
|
17
17
|
getNotificationConfig,
|
|
18
|
+
isDiscordConfigured,
|
|
18
19
|
isGloballyConfigured,
|
|
19
20
|
isTelegramConfigured,
|
|
20
21
|
maskToken,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
tokenFingerprint,
|
|
23
24
|
} from "./config";
|
|
24
25
|
import { type DaemonPaths, daemonPaths, HEARTBEAT_TTL_MS } from "./daemon-paths";
|
|
25
|
-
import
|
|
26
|
+
import { DAEMON_GENERATION } from "./telegram-daemon-contract";
|
|
26
27
|
|
|
27
28
|
const DEFAULT_API_BASE = "https://api.telegram.org";
|
|
28
29
|
|
|
@@ -139,8 +140,8 @@ export function buildNotificationStatusReport(settings: Settings): NotificationS
|
|
|
139
140
|
},
|
|
140
141
|
discord: {
|
|
141
142
|
botTokenMasked: maskToken(cfg.discord.botToken),
|
|
142
|
-
channel: cfg.discord.
|
|
143
|
-
configured:
|
|
143
|
+
channel: cfg.discord.parentChannelId,
|
|
144
|
+
configured: isDiscordConfigured(cfg),
|
|
144
145
|
},
|
|
145
146
|
slack: {
|
|
146
147
|
botTokenMasked: maskToken(cfg.slack.botToken),
|
|
@@ -164,7 +165,7 @@ export function formatNotificationStatusReport(report: NotificationStatusReport)
|
|
|
164
165
|
` telegram.fingerprint: ${report.telegram.tokenFingerprint ?? "(unset)"}`,
|
|
165
166
|
` telegram.configured: ${yesNo(report.telegram.configured)}`,
|
|
166
167
|
` discord.botToken: ${report.discord.botTokenMasked}`,
|
|
167
|
-
` discord.
|
|
168
|
+
` discord.parentChannelId: ${report.discord.channel ?? "(unset)"}`,
|
|
168
169
|
` slack.botToken: ${report.slack.botTokenMasked}`,
|
|
169
170
|
` slack.channelId: ${report.slack.channel ?? "(unset)"}`,
|
|
170
171
|
].join("\n");
|
|
@@ -215,7 +216,36 @@ async function readEndpointView(fs: NotificationServiceFs, file: string): Promis
|
|
|
215
216
|
};
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
|
|
219
|
+
interface NormalizedDaemonState {
|
|
220
|
+
pid: number;
|
|
221
|
+
ownerId: string;
|
|
222
|
+
tokenFingerprint: string | undefined;
|
|
223
|
+
chatId: string | undefined;
|
|
224
|
+
startedAt: number | undefined;
|
|
225
|
+
heartbeatAt: number | undefined;
|
|
226
|
+
stoppedAt: number | undefined;
|
|
227
|
+
roots: string[] | undefined;
|
|
228
|
+
generation: number | undefined;
|
|
229
|
+
generationStatus: "missing" | "valid" | "invalid";
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function finiteNonNegativeNumber(value: unknown): number | undefined {
|
|
233
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function safePositiveInteger(value: unknown): number | undefined {
|
|
237
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function safeNonNegativeInteger(value: unknown): number | undefined {
|
|
241
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function stringArray(value: unknown): string[] | undefined {
|
|
245
|
+
return Array.isArray(value) && value.every(item => typeof item === "string") ? value : undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parseDaemonState(raw: string): NormalizedDaemonState | undefined {
|
|
219
249
|
let parsed: unknown;
|
|
220
250
|
try {
|
|
221
251
|
parsed = JSON.parse(raw);
|
|
@@ -224,11 +254,33 @@ function parseDaemonState(raw: string): DaemonState | undefined {
|
|
|
224
254
|
}
|
|
225
255
|
if (!parsed || typeof parsed !== "object") return undefined;
|
|
226
256
|
const rec = parsed as Record<string, unknown>;
|
|
227
|
-
|
|
228
|
-
|
|
257
|
+
const pid = safePositiveInteger(rec.pid);
|
|
258
|
+
if (pid === undefined || typeof rec.ownerId !== "string" || rec.ownerId.length === 0) return undefined;
|
|
259
|
+
|
|
260
|
+
const generation = safeNonNegativeInteger(rec.generation);
|
|
261
|
+
const generationStatus = !Object.hasOwn(rec, "generation")
|
|
262
|
+
? "missing"
|
|
263
|
+
: generation === undefined
|
|
264
|
+
? "invalid"
|
|
265
|
+
: "valid";
|
|
266
|
+
return {
|
|
267
|
+
pid,
|
|
268
|
+
ownerId: rec.ownerId,
|
|
269
|
+
tokenFingerprint: typeof rec.tokenFingerprint === "string" ? rec.tokenFingerprint : undefined,
|
|
270
|
+
chatId: typeof rec.chatId === "string" ? rec.chatId : undefined,
|
|
271
|
+
startedAt: finiteNonNegativeNumber(rec.startedAt),
|
|
272
|
+
heartbeatAt: finiteNonNegativeNumber(rec.heartbeatAt),
|
|
273
|
+
stoppedAt: finiteNonNegativeNumber(rec.stoppedAt),
|
|
274
|
+
roots: stringArray(rec.roots),
|
|
275
|
+
generation,
|
|
276
|
+
generationStatus,
|
|
277
|
+
};
|
|
229
278
|
}
|
|
230
279
|
|
|
231
|
-
async function readDaemonStateFile(
|
|
280
|
+
async function readDaemonStateFile(
|
|
281
|
+
fs: NotificationServiceFs,
|
|
282
|
+
file: string,
|
|
283
|
+
): Promise<NormalizedDaemonState | undefined> {
|
|
232
284
|
try {
|
|
233
285
|
return parseDaemonState(await fs.readFile(file, "utf8"));
|
|
234
286
|
} catch {
|
|
@@ -262,8 +314,28 @@ export interface DaemonHealth {
|
|
|
262
314
|
heartbeatFresh: boolean;
|
|
263
315
|
identityMatches: boolean;
|
|
264
316
|
stopped: boolean;
|
|
317
|
+
heartbeatAt: number | undefined;
|
|
318
|
+
heartbeatAgeMs: number | undefined;
|
|
319
|
+
generation: number | undefined;
|
|
320
|
+
currentGeneration: number;
|
|
321
|
+
generationRelation: DaemonGenerationRelation;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export type DaemonGenerationRelation = "pre_generation" | "older" | "current" | "newer" | "unknown";
|
|
325
|
+
|
|
326
|
+
function daemonGenerationRelation(state: NormalizedDaemonState | undefined): DaemonGenerationRelation {
|
|
327
|
+
if (!state) return "unknown";
|
|
328
|
+
if (state.generationStatus === "missing") return "pre_generation";
|
|
329
|
+
if (state.generation === undefined) return "unknown";
|
|
330
|
+
if (state.generation < DAEMON_GENERATION) return "older";
|
|
331
|
+
if (state.generation === DAEMON_GENERATION) return "current";
|
|
332
|
+
return "newer";
|
|
265
333
|
}
|
|
266
334
|
|
|
335
|
+
function displayHeartbeatAgeMs(now: number, heartbeatAt: number): number | undefined {
|
|
336
|
+
const age = now - heartbeatAt;
|
|
337
|
+
return Number.isFinite(age) ? Math.max(0, age) : undefined;
|
|
338
|
+
}
|
|
267
339
|
export interface EndpointHealth {
|
|
268
340
|
total: number;
|
|
269
341
|
live: number;
|
|
@@ -349,18 +421,24 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
349
421
|
// Daemon ownership state (offline; read the persisted state file directly).
|
|
350
422
|
const paths = daemonPaths(opts.settings.getAgentDir());
|
|
351
423
|
const state = await readDaemonStateFile(fs, paths.state);
|
|
424
|
+
const heartbeatAt = state?.heartbeatAt;
|
|
352
425
|
const daemon: DaemonHealth = {
|
|
353
426
|
present: Boolean(state),
|
|
354
427
|
ownerId: state?.ownerId,
|
|
355
428
|
pid: state?.pid,
|
|
356
429
|
alive: state ? pidAlive(state.pid) : false,
|
|
357
|
-
heartbeatFresh:
|
|
430
|
+
heartbeatFresh: heartbeatAt !== undefined ? now - heartbeatAt <= HEARTBEAT_TTL_MS : false,
|
|
358
431
|
identityMatches:
|
|
359
432
|
Boolean(state) &&
|
|
360
433
|
telegramConfigured &&
|
|
361
434
|
state?.tokenFingerprint === tokenFingerprint(cfg.botToken) &&
|
|
362
435
|
state?.chatId === cfg.chatId,
|
|
363
|
-
stopped:
|
|
436
|
+
stopped: state?.stoppedAt !== undefined,
|
|
437
|
+
heartbeatAt,
|
|
438
|
+
heartbeatAgeMs: heartbeatAt === undefined ? undefined : displayHeartbeatAgeMs(now, heartbeatAt),
|
|
439
|
+
generation: state?.generation,
|
|
440
|
+
currentGeneration: DAEMON_GENERATION,
|
|
441
|
+
generationRelation: daemonGenerationRelation(state),
|
|
364
442
|
};
|
|
365
443
|
if (!state) {
|
|
366
444
|
checks.push({ name: "daemon", level: "ok", detail: "no daemon ownership record (none running)" });
|
|
@@ -421,6 +499,22 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
421
499
|
detail: `${live} live, ${unknownEndpoints} unverified endpoint file(s)`,
|
|
422
500
|
});
|
|
423
501
|
}
|
|
502
|
+
if (
|
|
503
|
+
telegramConfigured &&
|
|
504
|
+
daemon.present &&
|
|
505
|
+
daemon.alive &&
|
|
506
|
+
daemon.heartbeatFresh &&
|
|
507
|
+
daemon.identityMatches &&
|
|
508
|
+
!daemon.stopped &&
|
|
509
|
+
endpoints.total === 0
|
|
510
|
+
) {
|
|
511
|
+
checks.push({
|
|
512
|
+
name: "local_endpoint",
|
|
513
|
+
level: "warn",
|
|
514
|
+
detail:
|
|
515
|
+
"No local notification endpoint for this working directory. In this GJC terminal run /notify on; if it does not report notifications enabled, start a new local GJC session. Do not re-pair Telegram.",
|
|
516
|
+
});
|
|
517
|
+
}
|
|
424
518
|
|
|
425
519
|
// Optional network reachability probe.
|
|
426
520
|
let reachability = { probed: false, ok: false, detail: "not probed" };
|
|
@@ -566,7 +660,7 @@ async function removeDeadOwnerLock(
|
|
|
566
660
|
fs: NotificationServiceFs,
|
|
567
661
|
paths: DaemonPaths,
|
|
568
662
|
pidAlive: (pid: number) => boolean,
|
|
569
|
-
expected:
|
|
663
|
+
expected: NormalizedDaemonState,
|
|
570
664
|
): Promise<"cleared" | "contended" | "superseded" | "now-alive" | "unlink-failed"> {
|
|
571
665
|
if (!(await fs.createExclusive(paths.steal))) return "contended";
|
|
572
666
|
try {
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentTelegramActivationMarker,
|
|
3
|
+
isSessionNotificationsEnabled,
|
|
4
|
+
isTelegramConfigured,
|
|
5
|
+
type NotificationConfig,
|
|
6
|
+
} from "./config";
|
|
7
|
+
|
|
8
|
+
/** Minimal session-manager surface shared by extension, TUI, and headless hosts. */
|
|
9
|
+
export interface NotificationSessionContext {
|
|
10
|
+
sessionManager: {
|
|
11
|
+
getCwd(): string;
|
|
12
|
+
getSessionId(): string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A snapshot of the current session, resolved from the session manager per operation. */
|
|
17
|
+
export interface BoundNotificationSession<Context extends NotificationSessionContext = NotificationSessionContext> {
|
|
18
|
+
readonly context: Context;
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly sessionId: string;
|
|
21
|
+
unbind(): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type NotificationEndpointStartResult = "started" | "already" | "disabled" | "failed";
|
|
25
|
+
export type TelegramDaemonPreflightResult = "ready" | "blocked_identity";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The endpoint implementation is deliberately injected. The controller owns
|
|
29
|
+
* policy and session-local state while the extension continues to own its
|
|
30
|
+
* concrete NotificationServer resources.
|
|
31
|
+
*/
|
|
32
|
+
export interface NotificationSessionRuntime<Context extends NotificationSessionContext = NotificationSessionContext> {
|
|
33
|
+
isRunning(binding: BoundNotificationSession<Context>): boolean;
|
|
34
|
+
start(binding: BoundNotificationSession<Context>): Promise<NotificationEndpointStartResult>;
|
|
35
|
+
stop(binding: BoundNotificationSession<Context>): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Proves the complete Telegram owner identity before a generic endpoint can
|
|
38
|
+
* emit a frame. `blocked_identity` is fail-closed and starts nothing.
|
|
39
|
+
*/
|
|
40
|
+
ensureTelegramDaemon?(binding: BoundNotificationSession<Context>): Promise<TelegramDaemonPreflightResult>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface NotificationSessionStatus {
|
|
44
|
+
eligible: boolean;
|
|
45
|
+
locallyEnabled: boolean;
|
|
46
|
+
effectiveEnabled: boolean;
|
|
47
|
+
running: boolean;
|
|
48
|
+
environment: "off" | "explicit" | "token" | "default";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface NotificationSessionReconcileResult {
|
|
52
|
+
outcome: NotificationEndpointStartResult | "stopped";
|
|
53
|
+
status: NotificationSessionStatus;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface NotificationSessionControllerOptions {
|
|
57
|
+
/** Gate A result, resolved once by the SDK from the canonical host predicate. */
|
|
58
|
+
eligible: boolean;
|
|
59
|
+
/** Reads the global-only, schema-default-resolved notification configuration. */
|
|
60
|
+
getConfig(): NotificationConfig;
|
|
61
|
+
/** Kept as a reference so test and embedding hosts can supply their own environment. */
|
|
62
|
+
env?: NodeJS.ProcessEnv;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Shared owner of notification session policy.
|
|
67
|
+
*
|
|
68
|
+
* Gate A is captured at creation. Gate B is evaluated for each session
|
|
69
|
+
* operation with `isSessionNotificationsEnabled`. Gate C verifies complete
|
|
70
|
+
* Telegram ownership before a generic endpoint is allowed to start.
|
|
71
|
+
*/
|
|
72
|
+
export class NotificationSessionController {
|
|
73
|
+
readonly #eligible: boolean;
|
|
74
|
+
readonly #getConfig: () => NotificationConfig;
|
|
75
|
+
readonly #env: NodeJS.ProcessEnv;
|
|
76
|
+
readonly #disabledSessions = new Set<string>();
|
|
77
|
+
/** Sessions held inactive after a post-commit foreign daemon identity race. */
|
|
78
|
+
readonly #blockedRuntimeSessions = new Set<string>();
|
|
79
|
+
/** Sessions closed during host shutdown; no queued operation may restart them. */
|
|
80
|
+
readonly #shuttingDownSessions = new Set<string>();
|
|
81
|
+
/** Serializes endpoint mutations for each bound session snapshot. */
|
|
82
|
+
readonly #sessionOperations = new Map<string, Promise<void>>();
|
|
83
|
+
#runtime: NotificationSessionRuntime<NotificationSessionContext> | undefined;
|
|
84
|
+
|
|
85
|
+
constructor(options: NotificationSessionControllerOptions) {
|
|
86
|
+
this.#eligible = options.eligible;
|
|
87
|
+
this.#getConfig = options.getConfig;
|
|
88
|
+
this.#env = options.env ?? process.env;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Attach the concrete generic endpoint implementation used by this host. */
|
|
92
|
+
attachRuntime<Context extends NotificationSessionContext>(runtime: NotificationSessionRuntime<Context>): () => void {
|
|
93
|
+
const attached = runtime as unknown as NotificationSessionRuntime<NotificationSessionContext>;
|
|
94
|
+
this.#runtime = attached;
|
|
95
|
+
return () => {
|
|
96
|
+
if (this.#runtime === attached) this.#runtime = undefined;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Bind a fresh session snapshot. Callers should not cache it: cwd and session
|
|
102
|
+
* id may change on `/new`, fork, or resume.
|
|
103
|
+
*/
|
|
104
|
+
bind<Context extends NotificationSessionContext>(context: Context): BoundNotificationSession<Context> {
|
|
105
|
+
let bound = true;
|
|
106
|
+
const getCwd = context.sessionManager.getCwd;
|
|
107
|
+
const cwd =
|
|
108
|
+
typeof getCwd === "function" ? getCwd.call(context.sessionManager) : (context as { cwd?: unknown }).cwd;
|
|
109
|
+
if (typeof cwd !== "string" || cwd.length === 0) {
|
|
110
|
+
throw new Error("Notification session context does not expose a cwd.");
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
cwd,
|
|
114
|
+
sessionId: context.sessionManager.getSessionId(),
|
|
115
|
+
unbind: () => {
|
|
116
|
+
bound = false;
|
|
117
|
+
},
|
|
118
|
+
get context() {
|
|
119
|
+
if (!bound) throw new Error("Notification session binding has been released.");
|
|
120
|
+
return context;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Preserve session-local safety state and pending-operation ownership across a session rekey. */
|
|
126
|
+
rekeySession(previousSessionId: string, nextSessionId: string): void {
|
|
127
|
+
if (previousSessionId === nextSessionId) return;
|
|
128
|
+
if (this.#disabledSessions.delete(previousSessionId)) this.#disabledSessions.add(nextSessionId);
|
|
129
|
+
if (this.#blockedRuntimeSessions.delete(previousSessionId)) this.#blockedRuntimeSessions.add(nextSessionId);
|
|
130
|
+
if (this.#shuttingDownSessions.delete(previousSessionId)) this.#shuttingDownSessions.add(nextSessionId);
|
|
131
|
+
|
|
132
|
+
const previousOperation = this.#sessionOperations.get(previousSessionId);
|
|
133
|
+
if (!previousOperation) return;
|
|
134
|
+
const nextOperation = this.#sessionOperations.get(nextSessionId);
|
|
135
|
+
const completion = nextOperation ? previousOperation.then(() => nextOperation) : previousOperation;
|
|
136
|
+
this.#sessionOperations.delete(previousSessionId);
|
|
137
|
+
this.#sessionOperations.set(nextSessionId, completion);
|
|
138
|
+
void completion.then(() => {
|
|
139
|
+
if (this.#sessionOperations.get(nextSessionId) === completion) {
|
|
140
|
+
this.#sessionOperations.delete(nextSessionId);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
query<Context extends NotificationSessionContext>(context: Context): NotificationSessionStatus {
|
|
146
|
+
const binding = this.bind(context);
|
|
147
|
+
try {
|
|
148
|
+
return this.#query(binding);
|
|
149
|
+
} finally {
|
|
150
|
+
binding.unbind();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Stop the current endpoint during host shutdown without changing local preference. */
|
|
155
|
+
async stopCurrentSession<Context extends NotificationSessionContext>(context: Context): Promise<boolean> {
|
|
156
|
+
const binding = this.bind(context);
|
|
157
|
+
try {
|
|
158
|
+
// Set this before joining the queue so an in-flight start self-stops before
|
|
159
|
+
// shutdown resolves, and no later operation can restart this session.
|
|
160
|
+
this.#shuttingDownSessions.add(binding.sessionId);
|
|
161
|
+
return await this.#enqueue(binding, async () => {
|
|
162
|
+
const runtime = this.#runtime as NotificationSessionRuntime<Context> | undefined;
|
|
163
|
+
return runtime?.isRunning(binding) ? await runtime.stop(binding) : false;
|
|
164
|
+
});
|
|
165
|
+
} finally {
|
|
166
|
+
binding.unbind();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Hold this session's endpoint inactive after a foreign-daemon identity race.
|
|
172
|
+
* The block remains until an explicit same-identity reconnect or CAS restore clears it.
|
|
173
|
+
*/
|
|
174
|
+
async enterBlockedRuntime<Context extends NotificationSessionContext>(context: Context): Promise<boolean> {
|
|
175
|
+
const binding = this.bind(context);
|
|
176
|
+
try {
|
|
177
|
+
return await this.#enqueue(binding, async () => {
|
|
178
|
+
const runtime = this.#runtime as NotificationSessionRuntime<Context> | undefined;
|
|
179
|
+
const stopped = runtime?.isRunning(binding) ? await runtime.stop(binding) : false;
|
|
180
|
+
this.#blockedRuntimeSessions.add(binding.sessionId);
|
|
181
|
+
return stopped;
|
|
182
|
+
});
|
|
183
|
+
} finally {
|
|
184
|
+
binding.unbind();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Clear a block only after the caller has verified a safe same-identity reconnect or restore. */
|
|
189
|
+
async clearBlockedRuntime<Context extends NotificationSessionContext>(context: Context): Promise<void> {
|
|
190
|
+
const binding = this.bind(context);
|
|
191
|
+
try {
|
|
192
|
+
await this.#enqueue(binding, async () => {
|
|
193
|
+
this.#blockedRuntimeSessions.delete(binding.sessionId);
|
|
194
|
+
});
|
|
195
|
+
} finally {
|
|
196
|
+
binding.unbind();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async setLocalEnabled<Context extends NotificationSessionContext>(
|
|
201
|
+
context: Context,
|
|
202
|
+
enabled: boolean,
|
|
203
|
+
): Promise<NotificationSessionReconcileResult> {
|
|
204
|
+
const binding = this.bind(context);
|
|
205
|
+
try {
|
|
206
|
+
return await this.#enqueue(binding, async () => {
|
|
207
|
+
if (enabled) {
|
|
208
|
+
this.#disabledSessions.delete(binding.sessionId);
|
|
209
|
+
this.#shuttingDownSessions.delete(binding.sessionId);
|
|
210
|
+
} else {
|
|
211
|
+
this.#disabledSessions.add(binding.sessionId);
|
|
212
|
+
}
|
|
213
|
+
return await this.#reconcile(binding);
|
|
214
|
+
});
|
|
215
|
+
} finally {
|
|
216
|
+
binding.unbind();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async reconcileCurrentSession<Context extends NotificationSessionContext>(
|
|
221
|
+
context: Context,
|
|
222
|
+
): Promise<NotificationSessionReconcileResult> {
|
|
223
|
+
const binding = this.bind(context);
|
|
224
|
+
try {
|
|
225
|
+
return await this.#enqueue(binding, () => this.#reconcile(binding));
|
|
226
|
+
} finally {
|
|
227
|
+
binding.unbind();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async #reconcile<Context extends NotificationSessionContext>(
|
|
232
|
+
binding: BoundNotificationSession<Context>,
|
|
233
|
+
): Promise<NotificationSessionReconcileResult> {
|
|
234
|
+
const cfg = this.#getConfig();
|
|
235
|
+
const runtime = this.#runtime as NotificationSessionRuntime<Context> | undefined;
|
|
236
|
+
const status = this.#status(binding, cfg, runtime);
|
|
237
|
+
if (!status.effectiveEnabled) {
|
|
238
|
+
if (runtime && status.running) await runtime.stop(binding);
|
|
239
|
+
return { outcome: status.running ? "stopped" : "disabled", status: this.#status(binding, cfg, runtime) };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!runtime) return { outcome: "disabled", status };
|
|
243
|
+
if (getCurrentTelegramActivationMarker(cfg)) {
|
|
244
|
+
if (runtime.isRunning(binding)) await runtime.stop(binding);
|
|
245
|
+
this.#blockedRuntimeSessions.add(binding.sessionId);
|
|
246
|
+
return { outcome: "disabled", status: this.#status(binding, cfg, runtime) };
|
|
247
|
+
}
|
|
248
|
+
if (isTelegramConfigured(cfg)) {
|
|
249
|
+
try {
|
|
250
|
+
const ensured = await runtime.ensureTelegramDaemon?.(binding);
|
|
251
|
+
if (ensured !== "ready") {
|
|
252
|
+
if (runtime.isRunning(binding)) await runtime.stop(binding);
|
|
253
|
+
this.#blockedRuntimeSessions.add(binding.sessionId);
|
|
254
|
+
return { outcome: "disabled", status: this.#status(binding, cfg, runtime) };
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
if (runtime.isRunning(binding)) await runtime.stop(binding);
|
|
258
|
+
this.#blockedRuntimeSessions.add(binding.sessionId);
|
|
259
|
+
return { outcome: "failed", status: this.#status(binding, cfg, runtime) };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const current = this.#status(binding, cfg, runtime);
|
|
264
|
+
if (!current.effectiveEnabled) {
|
|
265
|
+
if (current.running) await runtime.stop(binding);
|
|
266
|
+
return { outcome: current.running ? "stopped" : "disabled", status: this.#status(binding, cfg, runtime) };
|
|
267
|
+
}
|
|
268
|
+
const outcome = current.running ? "already" : await runtime.start(binding);
|
|
269
|
+
const afterStart = this.#status(binding, cfg, runtime);
|
|
270
|
+
if (!afterStart.effectiveEnabled) {
|
|
271
|
+
if (afterStart.running) await runtime.stop(binding);
|
|
272
|
+
return { outcome: afterStart.running ? "stopped" : "disabled", status: this.#status(binding, cfg, runtime) };
|
|
273
|
+
}
|
|
274
|
+
return { outcome, status: this.#status(binding, cfg, runtime) };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#enqueue<Context extends NotificationSessionContext, Result>(
|
|
278
|
+
binding: BoundNotificationSession<Context>,
|
|
279
|
+
operation: () => Promise<Result>,
|
|
280
|
+
): Promise<Result> {
|
|
281
|
+
const previous = this.#sessionOperations.get(binding.sessionId) ?? Promise.resolve();
|
|
282
|
+
const result = previous.then(operation, operation);
|
|
283
|
+
const completion = result.then(
|
|
284
|
+
() => undefined,
|
|
285
|
+
() => undefined,
|
|
286
|
+
);
|
|
287
|
+
this.#sessionOperations.set(binding.sessionId, completion);
|
|
288
|
+
void completion.then(() => {
|
|
289
|
+
if (this.#sessionOperations.get(binding.sessionId) === completion) {
|
|
290
|
+
this.#sessionOperations.delete(binding.sessionId);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#query<Context extends NotificationSessionContext>(
|
|
297
|
+
binding: BoundNotificationSession<Context>,
|
|
298
|
+
): NotificationSessionStatus {
|
|
299
|
+
const cfg = this.#getConfig();
|
|
300
|
+
return this.#status(binding, cfg, this.#runtime as NotificationSessionRuntime<Context> | undefined);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#status<Context extends NotificationSessionContext>(
|
|
304
|
+
binding: BoundNotificationSession<Context>,
|
|
305
|
+
cfg: NotificationConfig,
|
|
306
|
+
runtime: NotificationSessionRuntime<Context> | undefined,
|
|
307
|
+
): NotificationSessionStatus {
|
|
308
|
+
const locallyEnabled = !this.#disabledSessions.has(binding.sessionId);
|
|
309
|
+
const blockedRuntime = this.#blockedRuntimeSessions.has(binding.sessionId);
|
|
310
|
+
const shuttingDown = this.#shuttingDownSessions.has(binding.sessionId);
|
|
311
|
+
const effectiveEnabled =
|
|
312
|
+
!blockedRuntime &&
|
|
313
|
+
!shuttingDown &&
|
|
314
|
+
!getCurrentTelegramActivationMarker(cfg) &&
|
|
315
|
+
this.#eligible &&
|
|
316
|
+
isSessionNotificationsEnabled({ cfg, env: this.#env, sessionDisabled: !locallyEnabled });
|
|
317
|
+
const environment =
|
|
318
|
+
this.#env.GJC_NOTIFICATIONS === "0"
|
|
319
|
+
? "off"
|
|
320
|
+
: this.#env.GJC_NOTIFICATIONS === "1"
|
|
321
|
+
? "explicit"
|
|
322
|
+
: this.#env.GJC_NOTIFICATIONS_TOKEN
|
|
323
|
+
? "token"
|
|
324
|
+
: "default";
|
|
325
|
+
return {
|
|
326
|
+
eligible: this.#eligible,
|
|
327
|
+
locallyEnabled,
|
|
328
|
+
effectiveEnabled,
|
|
329
|
+
running: runtime?.isRunning(binding) ?? false,
|
|
330
|
+
environment,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { boundedDedupe, type ConversationRecord } from "./conversation-store";
|
|
2
|
+
|
|
3
|
+
export interface SlackInboundDispatchReceipt {
|
|
4
|
+
key: string;
|
|
5
|
+
eventId: string;
|
|
6
|
+
interactionId: string;
|
|
7
|
+
retryKey: string;
|
|
8
|
+
eventContext?: string;
|
|
9
|
+
kind: "action" | "command";
|
|
10
|
+
actionId?: string;
|
|
11
|
+
/** The endpoint generation and SDK effect captured before Socket Mode ACK. */
|
|
12
|
+
endpointGeneration: number;
|
|
13
|
+
/** Identifier of the protected journal payload; mappings never retain message bodies. */
|
|
14
|
+
effectId: string;
|
|
15
|
+
idempotencyKey: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SlackConversationState = "absent" | "posting_root" | "active" | "closed_marker" | "resumed_root" | "error";
|
|
19
|
+
|
|
20
|
+
/** Durable Slack mapping. It contains identifiers and receipt metadata only; protected journal effects own message bodies. */
|
|
21
|
+
export interface SlackConversation extends ConversationRecord {
|
|
22
|
+
state: SlackConversationState;
|
|
23
|
+
teamId: string;
|
|
24
|
+
channelId: string;
|
|
25
|
+
rootTs?: string;
|
|
26
|
+
sessionId?: string;
|
|
27
|
+
endpointGeneration?: number;
|
|
28
|
+
clientMsgId?: string;
|
|
29
|
+
/** Exclusive, expiring authority for provider reconciliation/publication of the root intent. */
|
|
30
|
+
rootPublicationOwner?: string;
|
|
31
|
+
rootPublicationLeaseExpiresAt?: number;
|
|
32
|
+
/** Monotonic fencing token; stale root publishers may not commit after takeover. */
|
|
33
|
+
rootPublicationFence?: number;
|
|
34
|
+
|
|
35
|
+
/** Exclusive, expiring authority for provider reconciliation/publication of the outbound action intent. */
|
|
36
|
+
outboundActionOwner?: string;
|
|
37
|
+
outboundActionLeaseExpiresAt?: number;
|
|
38
|
+
/** Monotonic fencing token; stale action publishers may not commit after takeover. */
|
|
39
|
+
outboundActionFence?: number;
|
|
40
|
+
/** Action authority only; action bodies and endpoint credentials are never persisted. */
|
|
41
|
+
pendingActionId?: string;
|
|
42
|
+
/** Durable, unpublished action intent. The action body is supplied only by the caller. */
|
|
43
|
+
outboundActionId?: string;
|
|
44
|
+
outboundActionClientMsgId?: string;
|
|
45
|
+
lastError?: string;
|
|
46
|
+
updatedAt: number;
|
|
47
|
+
seenEventIds: string[];
|
|
48
|
+
seenContextIds: string[];
|
|
49
|
+
seenRetryKeys: string[];
|
|
50
|
+
seenInteractionIds: string[];
|
|
51
|
+
/** Durable inbound effect identifiers. The protected journal owns replay payloads. */
|
|
52
|
+
inboundDispatches?: SlackInboundDispatchReceipt[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function slackConversationKey(input: { teamId: string; channelId: string; rootTs: string }): string {
|
|
56
|
+
return `${input.teamId}:${input.channelId}:${input.rootTs}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function normalizeSlackConversation(record: SlackConversation): SlackConversation {
|
|
60
|
+
const inboundDispatches = record.inboundDispatches ?? [];
|
|
61
|
+
return {
|
|
62
|
+
...record,
|
|
63
|
+
seenEventIds: boundedDedupe(record.seenEventIds),
|
|
64
|
+
seenContextIds: boundedDedupe(record.seenContextIds),
|
|
65
|
+
seenRetryKeys: boundedDedupe(record.seenRetryKeys),
|
|
66
|
+
seenInteractionIds: boundedDedupe(record.seenInteractionIds),
|
|
67
|
+
// Replay lifecycle and terminal retention are journal-owned; mappings retain identifiers.
|
|
68
|
+
inboundDispatches,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function acceptsSlackInbound(record: SlackConversation, rootTs: string, endpointGeneration: number): boolean {
|
|
73
|
+
return record.state === "active" && record.rootTs === rootTs && record.endpointGeneration === endpointGeneration;
|
|
74
|
+
}
|