@gajae-code/coding-agent 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -2
- package/README.md +2 -0
- package/bin/gajaessi.js +4 -0
- package/dist/types/async/job-manager.d.ts +55 -2
- package/dist/types/async/support.d.ts +14 -0
- package/dist/types/capability/mcp.d.ts +6 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/accounts-cli.d.ts +19 -0
- package/dist/types/cli/args.d.ts +3 -0
- package/dist/types/cli/config-cli.d.ts +6 -0
- package/dist/types/cli/crash-cli.d.ts +10 -0
- package/dist/types/cli/customize-doctor.d.ts +145 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/mcp-cli.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/root-flags.d.ts +12 -0
- package/dist/types/cli/setup-cli.d.ts +3 -1
- package/dist/types/cli/skills-cli.d.ts +2 -1
- package/dist/types/cli/update-cli.d.ts +19 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/accounts.d.ts +40 -0
- package/dist/types/commands/crash.d.ts +24 -0
- package/dist/types/commands/customize.d.ts +22 -0
- package/dist/types/commands/daemon.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/commands/skills.d.ts +6 -1
- package/dist/types/commands/update.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
- package/dist/types/config/config-schema-version.d.ts +10 -0
- package/dist/types/config/file-lock.d.ts +18 -0
- package/dist/types/config/model-profile-activation.d.ts +3 -1
- package/dist/types/config/model-registry.d.ts +9 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +70 -6
- package/dist/types/config/settings.d.ts +63 -0
- package/dist/types/config/skill-settings-defaults.d.ts +26 -0
- package/dist/types/config/task-delegation.d.ts +39 -0
- package/dist/types/config/update-channel.d.ts +10 -0
- package/dist/types/coordinator-mcp/server.d.ts +5 -1
- package/dist/types/crash/index-store.d.ts +87 -0
- package/dist/types/crash/nudge.d.ts +50 -0
- package/dist/types/crash/record-loader.d.ts +17 -0
- package/dist/types/crash/report.d.ts +134 -0
- package/dist/types/crash/sanitize.d.ts +25 -0
- package/dist/types/cursor.d.ts +13 -2
- package/dist/types/customization/import.d.ts +37 -0
- package/dist/types/customization/index.d.ts +4 -0
- package/dist/types/customization/inventory.d.ts +25 -0
- package/dist/types/customization/mutations.d.ts +32 -0
- package/dist/types/customization/types.d.ts +115 -0
- package/dist/types/daemon/control-types.d.ts +8 -1
- package/dist/types/daemon/operator-contract.d.ts +4 -4
- package/dist/types/discovery/claude.d.ts +39 -1
- package/dist/types/discovery/codex.d.ts +39 -1
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/mcp-compat.d.ts +30 -0
- package/dist/types/edit/renderer.d.ts +23 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -4
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
- package/dist/types/extensibility/hooks/loader.d.ts +19 -5
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
- package/dist/types/extensibility/skill-management.d.ts +107 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
- package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
- package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
- package/dist/types/hooks/events.d.ts +60 -0
- package/dist/types/hooks/normalize.d.ts +83 -0
- package/dist/types/hooks/skill-state.d.ts +9 -2
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/acp/abort-scope.d.ts +13 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -4
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
- package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
- package/dist/types/modes/components/customization/index.d.ts +1 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
- package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
- package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -4
- package/dist/types/modes/components/pet-capability.d.ts +11 -25
- package/dist/types/modes/components/status-line/gh.d.ts +6 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
- package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
- package/dist/types/runtime-mcp/config.d.ts +9 -0
- package/dist/types/runtime-mcp/manager.d.ts +26 -1
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
- package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
- package/dist/types/runtime-mcp/protocol.d.ts +278 -0
- package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
- package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
- package/dist/types/runtime-mcp/types.d.ts +102 -10
- package/dist/types/sdk/acp/adapter.d.ts +26 -5
- package/dist/types/sdk/broker/broker.d.ts +3 -55
- package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
- package/dist/types/sdk/broker/session-index.d.ts +14 -5
- package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
- package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
- package/dist/types/sdk/bus/index.d.ts +85 -99
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
- package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
- package/dist/types/sdk/bus/notification-service.d.ts +52 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
- package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
- package/dist/types/sdk/bus/session-control.d.ts +25 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
- package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
- package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
- package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
- package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
- package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
- package/dist/types/sdk/cli/rows.d.ts +2 -11
- package/dist/types/sdk/cli/session-cli.d.ts +15 -25
- package/dist/types/sdk/client/client.d.ts +97 -1
- package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +18 -5
- package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
- package/dist/types/sdk/host/host.d.ts +15 -1
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/host/session-runtime.d.ts +127 -7
- package/dist/types/sdk/host/types.d.ts +1 -1
- package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +2 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
- package/dist/types/sdk/lifecycle/client.d.ts +59 -0
- package/dist/types/sdk/lifecycle/index.d.ts +3 -0
- package/dist/types/sdk/lifecycle/service.d.ts +232 -0
- package/dist/types/sdk/mcp/server.d.ts +9 -6
- package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
- package/dist/types/sdk/prompt-status.d.ts +10 -3
- package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
- package/dist/types/sdk/receipt-state.d.ts +17 -0
- package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
- package/dist/types/sdk/router/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +152 -0
- package/dist/types/sdk/session-list.d.ts +22 -0
- package/dist/types/sdk/session-reconnect.d.ts +43 -1
- package/dist/types/sdk/session.d.ts +34 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
- package/dist/types/session/account-inventory.d.ts +86 -0
- package/dist/types/session/agent-session.d.ts +145 -6
- package/dist/types/session/agent-storage.d.ts +10 -0
- package/dist/types/session/coordinator-tool-label.d.ts +28 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/secure-token-file.d.ts +3 -0
- package/dist/types/session/session-manager.d.ts +40 -1
- package/dist/types/session/startup-auth-config.d.ts +44 -0
- package/dist/types/session/terminal-abort.d.ts +378 -0
- package/dist/types/session/yield-queue.d.ts +7 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +88 -0
- package/dist/types/session-import/command.d.ts +35 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/provider-codex.d.ts +22 -0
- package/dist/types/session-import/provider-service.d.ts +38 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +47 -0
- package/dist/types/session-import/types.d.ts +120 -0
- package/dist/types/setup/paseo/check.d.ts +16 -0
- package/dist/types/setup/paseo/install-saga.d.ts +115 -0
- package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
- package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
- package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
- package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
- package/dist/types/setup/paseo/provider-config.d.ts +35 -0
- package/dist/types/setup/paseo/remove.d.ts +14 -0
- package/dist/types/setup/paseo/result-types.d.ts +59 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
- package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
- package/dist/types/skill-state/active-state.d.ts +5 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
- package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
- package/dist/types/slash-commands/types.d.ts +7 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/tools/ask-answer-registry.d.ts +6 -3
- package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
- package/dist/types/tools/bash.d.ts +2 -6
- package/dist/types/tools/browser/attach.d.ts +11 -0
- package/dist/types/tools/browser/launch.d.ts +20 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +9 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/monitor.d.ts +1 -1
- package/dist/types/tools/skill-discovery.d.ts +8 -1
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
- package/dist/types/utils/herdr-pane.d.ts +76 -0
- package/package.json +19 -9
- package/scripts/build-binary.ts +2 -1
- package/scripts/build-sdk-package-smoke.ts +2 -13
- package/scripts/capture-sticky-viewport-showcase.ts +0 -1
- package/scripts/compile-args.ts +2 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
- package/scripts/generate-sdk-operation-inventory.ts +28 -0
- package/scripts/generate-telegram-baseline-manifest.ts +1 -0
- package/scripts/generate-tool-catalog.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
- package/src/async/job-manager.ts +352 -17
- package/src/async/support.ts +25 -0
- package/src/autoresearch/dashboard.ts +28 -13
- package/src/capability/index.ts +3 -1
- package/src/capability/mcp.ts +6 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/accounts-cli.ts +395 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/auth-broker-cli.ts +136 -66
- package/src/cli/auth-gateway-cli.ts +94 -70
- package/src/cli/config-cli.ts +27 -2
- package/src/cli/crash-cli.ts +78 -0
- package/src/cli/customize-doctor.ts +1605 -0
- package/src/cli/daemon-cli.ts +12 -3
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/mcp-cli.ts +128 -60
- package/src/cli/notify-cli.ts +8 -14
- package/src/cli/root-flags.ts +8 -0
- package/src/cli/setup-cli.ts +145 -0
- package/src/cli/skills-cli.ts +38 -2
- package/src/cli/update-cli.ts +167 -12
- package/src/cli.ts +4 -0
- package/src/commands/accounts.ts +71 -0
- package/src/commands/crash.ts +35 -0
- package/src/commands/customize.ts +84 -0
- package/src/commands/daemon.ts +2 -2
- package/src/commands/deep-interview.ts +2 -0
- package/src/commands/launch.ts +1 -0
- package/src/commands/mcp.ts +12 -7
- package/src/commands/ralplan.ts +2 -0
- package/src/commands/sdk.ts +65 -32
- package/src/commands/setup.ts +5 -0
- package/src/commands/skills.ts +14 -4
- package/src/commands/ultragoal.ts +10 -1
- package/src/commands/update.ts +26 -15
- package/src/config/atomic-yaml-patch-worker.ts +57 -0
- package/src/config/atomic-yaml-patch.ts +769 -29
- package/src/config/config-schema-version.ts +10 -0
- package/src/config/file-lock.ts +70 -1
- package/src/config/mcp-schema.json +52 -14
- package/src/config/model-profile-activation.ts +20 -1
- package/src/config/model-profiles.ts +29 -19
- package/src/config/model-registry.ts +132 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +76 -6
- package/src/config/settings.ts +4038 -199
- package/src/config/skill-settings-defaults.ts +41 -3
- package/src/config/task-delegation.ts +87 -0
- package/src/config/update-channel.ts +23 -0
- package/src/coordinator-mcp/server.ts +377 -308
- package/src/crash/index-store.ts +723 -0
- package/src/crash/nudge.ts +89 -0
- package/src/crash/record-loader.ts +149 -0
- package/src/crash/report.ts +551 -0
- package/src/crash/sanitize.ts +129 -0
- package/src/cursor.ts +36 -10
- package/src/customization/import.ts +946 -0
- package/src/customization/index.ts +4 -0
- package/src/customization/inventory.ts +335 -0
- package/src/customization/mutations.ts +141 -0
- package/src/customization/types.ts +223 -0
- package/src/daemon/control-types.ts +8 -1
- package/src/daemon/operator-contract.ts +6 -6
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
- package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/discovery/claude-hooks.ts +38 -0
- package/src/discovery/claude.ts +142 -209
- package/src/discovery/codex-hooks.ts +38 -0
- package/src/discovery/codex.ts +132 -198
- package/src/discovery/helpers.ts +8 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/mcp-compat.ts +234 -0
- package/src/discovery/mcp-json.ts +6 -0
- package/src/edit/renderer.ts +40 -0
- package/src/edit/streaming.ts +4 -2
- package/src/exec/bash-executor.ts +24 -20
- package/src/export/html/index.ts +51 -2
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +17 -4
- package/src/extensibility/extensions/types.ts +31 -6
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
- package/src/extensibility/gjc-plugins/installer.ts +9 -5
- package/src/extensibility/gjc-plugins/observability.ts +8 -2
- package/src/extensibility/gjc-plugins/registry.ts +13 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
- package/src/extensibility/gjc-plugins/schema.ts +222 -39
- package/src/extensibility/hooks/loader.ts +154 -10
- package/src/extensibility/runtime-skill-discovery.ts +257 -60
- package/src/extensibility/skill-management.ts +368 -0
- package/src/extensibility/skills.ts +52 -16
- package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
- package/src/gjc-runtime/launch-worktree.ts +56 -2
- package/src/gjc-runtime/linux-proc.ts +7 -1
- package/src/gjc-runtime/ralplan-runtime.ts +100 -164
- package/src/gjc-runtime/session-state-lock.ts +992 -0
- package/src/gjc-runtime/session-state-sidecar.ts +644 -142
- package/src/gjc-runtime/state-writer.ts +39 -14
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
- package/src/gjc-runtime/workflow-settings.ts +596 -0
- package/src/goals/tools/goal-tool.ts +6 -1
- package/src/hooks/events.ts +291 -0
- package/src/hooks/native-skill-hook.ts +4 -0
- package/src/hooks/normalize.ts +369 -0
- package/src/hooks/skill-state.ts +165 -46
- package/src/internal-urls/docs-index.generated.ts +40 -32
- package/src/main.ts +32 -5
- package/src/migrate/mcp-mapper.ts +25 -2
- package/src/modes/acp/abort-scope.ts +29 -0
- package/src/modes/acp/acp-agent.ts +423 -98
- package/src/modes/components/assistant-message.ts +6 -1
- package/src/modes/components/customization/customization-dashboard.ts +443 -0
- package/src/modes/components/customization/import-wizard.ts +372 -0
- package/src/modes/components/customization/index.ts +1 -0
- package/src/modes/components/gajae-pet-widget.ts +539 -213
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +39 -40
- package/src/modes/components/settings-selector.ts +9 -0
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +16 -1
- package/src/modes/components/tool-status-header.ts +13 -3
- package/src/modes/controllers/command-controller.ts +20 -8
- package/src/modes/controllers/extension-ui-controller.ts +56 -257
- package/src/modes/controllers/input-controller.ts +10 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
- package/src/modes/controllers/selector-controller.ts +177 -72
- package/src/modes/controllers/stt-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +88 -16
- package/src/modes/print-mode.ts +35 -6
- package/src/modes/runtime-init.ts +21 -68
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +25 -0
- package/src/modes/utils/terminal-bell.ts +15 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/critic.md +1 -1
- package/src/prompts/system/rlm-research.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -2
- package/src/prompts/tools/bash.md +7 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +6 -6
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/skill-discovery.md +4 -2
- package/src/runtime-mcp/client.ts +427 -23
- package/src/runtime-mcp/config.ts +37 -5
- package/src/runtime-mcp/manager-pool.test.ts +56 -0
- package/src/runtime-mcp/manager.ts +52 -2
- package/src/runtime-mcp/oauth-discovery.ts +13 -0
- package/src/runtime-mcp/oauth-flow.ts +46 -1
- package/src/runtime-mcp/pool-key.ts +5 -1
- package/src/runtime-mcp/pool.test.ts +9 -0
- package/src/runtime-mcp/protocol.test.ts +353 -0
- package/src/runtime-mcp/protocol.ts +624 -0
- package/src/runtime-mcp/scope-config.ts +78 -0
- package/src/runtime-mcp/tool-bridge.ts +44 -10
- package/src/runtime-mcp/transports/http.ts +84 -13
- package/src/runtime-mcp/types.ts +123 -12
- package/src/sdk/acp/adapter.ts +328 -97
- package/src/sdk/broker/broker.ts +160 -741
- package/src/sdk/broker/ensure.ts +9 -1
- package/src/sdk/broker/lifecycle.ts +510 -116
- package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
- package/src/sdk/broker/process-incarnation.ts +19 -0
- package/src/sdk/broker/session-index.ts +516 -117
- package/src/sdk/broker/startup-failure.ts +28 -5
- package/src/sdk/broker/transport.ts +1 -13
- package/src/sdk/bus/chat-daemon-cli.ts +4 -2
- package/src/sdk/bus/chat-daemon-control.ts +67 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
- package/src/sdk/bus/config.ts +13 -0
- package/src/sdk/bus/conversation-store.ts +59 -171
- package/src/sdk/bus/daemon-paths.ts +20 -0
- package/src/sdk/bus/discord-conversation.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +524 -126
- package/src/sdk/bus/discord-live-provider.ts +206 -79
- package/src/sdk/bus/existing-thread-readiness.ts +4 -97
- package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
- package/src/sdk/bus/index.ts +2480 -456
- package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
- package/src/sdk/bus/lifecycle-commands.ts +2 -2
- package/src/sdk/bus/managed-daemon.ts +2 -145
- package/src/sdk/bus/notification-service.ts +196 -0
- package/src/sdk/bus/operator-runtime.ts +1 -1
- package/src/sdk/bus/prompt-reconciliation.ts +24 -15
- package/src/sdk/bus/reconciliation-store.ts +553 -24
- package/src/sdk/bus/session-control.ts +49 -3
- package/src/sdk/bus/slack-conversation.ts +4 -0
- package/src/sdk/bus/slack-daemon.ts +733 -134
- package/src/sdk/bus/slack-live-provider.ts +65 -15
- package/src/sdk/bus/slack-provider.ts +4 -0
- package/src/sdk/bus/slack-thread-binding.ts +9 -104
- package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
- package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
- package/src/sdk/bus/telegram-daemon-control.ts +3 -13
- package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
- package/src/sdk/bus/telegram-daemon.ts +2552 -2280
- package/src/sdk/bus/telegram-reference.ts +1 -226
- package/src/sdk/bus/telegram-setup.ts +2 -2
- package/src/sdk/bus/threaded-render.ts +1 -0
- package/src/sdk/bus/topic-registry.ts +279 -146
- package/src/sdk/cli/rows.ts +2 -35
- package/src/sdk/cli/session-cli.ts +776 -700
- package/src/sdk/client/client.ts +892 -1
- package/src/sdk/host/control/dispatch.ts +93 -58
- package/src/sdk/host/control/index.ts +1 -1
- package/src/sdk/host/control/operations.ts +18 -10
- package/src/sdk/host/control/runtime-gate.ts +40 -0
- package/src/sdk/host/host.ts +63 -12
- package/src/sdk/host/query/handlers.ts +66 -7
- package/src/sdk/host/reverse-leases.ts +2 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +2787 -34
- package/src/sdk/host/session-runtime.ts +1489 -101
- package/src/sdk/host/surface-policy.ts +12 -8
- package/src/sdk/host/types.ts +1 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
- package/src/sdk/host/websocket-transport.ts +85 -50
- package/src/sdk/index.ts +2 -1
- package/src/sdk/lifecycle/broker-client.ts +53 -0
- package/src/sdk/lifecycle/client.ts +234 -0
- package/src/sdk/lifecycle/index.ts +3 -0
- package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
- package/src/sdk/lifecycle/service.ts +720 -0
- package/src/sdk/mcp/server.ts +226 -188
- package/src/sdk/prompt-deadline-lease.ts +55 -0
- package/src/sdk/prompt-deadline-manager.ts +148 -0
- package/src/sdk/prompt-status.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +272 -129
- package/src/sdk/protocol/operation-registry.ts +71 -8
- package/src/sdk/receipt-state.ts +43 -0
- package/src/sdk/reconciliation-extensions.ts +14 -0
- package/src/sdk/router/index.ts +1 -0
- package/src/sdk/router/session-router.ts +1658 -0
- package/src/sdk/session-list.ts +93 -0
- package/src/sdk/session-reconnect.ts +45 -1
- package/src/sdk/session.ts +716 -120
- package/src/sdk/transport/serve-cli.ts +18 -34
- package/src/session/account-inventory.ts +603 -0
- package/src/session/agent-session.ts +2857 -582
- package/src/session/agent-storage.ts +35 -0
- package/src/session/blob-store.ts +32 -1
- package/src/session/coordinator-tool-label.ts +40 -0
- package/src/session/internal/managed-session-scope.ts +4 -0
- package/src/session/internal/managed-session-storage.ts +173 -12
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +730 -58
- package/src/session/session-storage.ts +25 -16
- package/src/session/startup-auth-config.ts +297 -0
- package/src/session/terminal-abort.ts +1253 -0
- package/src/session/yield-queue.ts +57 -15
- package/src/session-import/claude.ts +334 -0
- package/src/session-import/codex.ts +1033 -0
- package/src/session-import/command.ts +77 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +7 -0
- package/src/session-import/provider-codex.ts +327 -0
- package/src/session-import/provider-service.ts +457 -0
- package/src/session-import/redact.ts +128 -0
- package/src/session-import/service.ts +1264 -0
- package/src/session-import/types.ts +148 -0
- package/src/setup/paseo/check.ts +213 -0
- package/src/setup/paseo/install-saga.ts +293 -0
- package/src/setup/paseo/json-publisher.ts +263 -0
- package/src/setup/paseo/orchestration-preferences.ts +86 -0
- package/src/setup/paseo/paseo-ownership.ts +224 -0
- package/src/setup/paseo/paseo-setup.ts +253 -0
- package/src/setup/paseo/provider-config.ts +139 -0
- package/src/setup/paseo/remove.ts +183 -0
- package/src/setup/paseo/result-types.ts +79 -0
- package/src/setup/paseo/setup-deps.ts +175 -0
- package/src/setup/paseo/skills-bridge.ts +260 -0
- package/src/skill-state/active-state.ts +21 -11
- package/src/slash-commands/acp-builtins.ts +9 -1
- package/src/slash-commands/builtin-registry.ts +98 -16
- package/src/slash-commands/helpers/credential-switch.ts +143 -0
- package/src/slash-commands/helpers/mcp.ts +9 -3
- package/src/slash-commands/helpers/usage-report.ts +61 -56
- package/src/slash-commands/types.ts +7 -2
- package/src/task/executor.ts +30 -11
- package/src/task/index.ts +66 -7
- package/src/task/render.ts +23 -9
- package/src/tools/ask-answer-registry.ts +23 -7
- package/src/tools/ask.ts +8 -4
- package/src/tools/bash-sleep-advisory.ts +115 -0
- package/src/tools/bash.ts +98 -18
- package/src/tools/browser/attach.ts +71 -3
- package/src/tools/browser/launch.ts +86 -13
- package/src/tools/browser/profile-discovery.ts +57 -7
- package/src/tools/browser/render.ts +8 -1
- package/src/tools/browser.ts +130 -21
- package/src/tools/computer.enforcement.test.ts +1 -1
- package/src/tools/computer.ts +3 -2
- package/src/tools/cron.ts +12 -1
- package/src/tools/image-gen.ts +27 -16
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +33 -6
- package/src/tools/monitor.ts +41 -2
- package/src/tools/skill-discovery.ts +21 -29
- package/src/tools/subagent.ts +142 -24
- package/src/tools/tool-catalog.generated.ts +6 -6
- package/src/tools/ultragoal-ask-guard.ts +10 -3
- package/src/utils/herdr-pane.ts +345 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/scrapers/index.ts +6 -4
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
- package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
- package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
- package/dist/types/runtime/lazy-service.test.d.ts +0 -1
- package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
- package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
- package/dist/types/runtime-mcp/config.test.d.ts +0 -1
- package/dist/types/runtime-mcp/http.test.d.ts +0 -1
- package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
- package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
- package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
- package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
- package/dist/types/sdk/elevation/audit.d.ts +0 -93
- package/dist/types/sdk/elevation/capability.d.ts +0 -3
- package/dist/types/sdk/elevation/digest.d.ts +0 -72
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
- package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
- package/dist/types/sdk/elevation/owner.d.ts +0 -48
- package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
- package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
- package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
- package/dist/types/skills/skills.test.d.ts +0 -1
- package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
- package/dist/types/tools/descriptors.test.d.ts +0 -1
- package/scripts/g005-daemon-smoke.ts +0 -180
- package/scripts/g011-daemon-path-smoke.ts +0 -153
- package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
- package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
- package/src/sdk/bus/telegram-cli.ts +0 -212
- package/src/sdk/elevation/allowlist.ts +0 -54
- package/src/sdk/elevation/audit.ts +0 -208
- package/src/sdk/elevation/capability.ts +0 -41
- package/src/sdk/elevation/digest.ts +0 -129
- package/src/sdk/elevation/dispatch-receipt.ts +0 -184
- package/src/sdk/elevation/grant-ledger.ts +0 -1142
- package/src/sdk/elevation/owner.ts +0 -109
- package/src/session/auth-broker-config.ts +0 -109
- /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
- /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
- /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { nativeProcessBindings } from "@gajae-code/utils/native-process";
|
|
3
|
+
import { $ } from "bun";
|
|
4
|
+
import { isProcessIncarnation } from "../broker/process-incarnation";
|
|
5
|
+
import type { TelegramDaemonFs } from "./telegram-daemon";
|
|
6
|
+
import {
|
|
7
|
+
listTelegramOwnerMarkers,
|
|
8
|
+
removeTelegramOwnerMarker,
|
|
9
|
+
type TelegramOwnerMarker,
|
|
10
|
+
} from "./telegram-daemon-owner-registry";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A stable, identity-bound reference to a process opened BEFORE the incarnation
|
|
14
|
+
* check and used for ALL subsequent operations. This closes the PID-reuse race:
|
|
15
|
+
* the native handle pins the exact process incarnation, so a reused PID cannot
|
|
16
|
+
* be signaled through a stale reference — the OS will reject the operation.
|
|
17
|
+
*
|
|
18
|
+
* `signalRoot` signals only the pinned root process (root-only).
|
|
19
|
+
* `terminateTree` signals the process AND its descendants / process group.
|
|
20
|
+
*/
|
|
21
|
+
export interface TelegramOrphanProcessRef {
|
|
22
|
+
/** The incarnation of the process this reference was opened against. */
|
|
23
|
+
incarnation: string;
|
|
24
|
+
/**
|
|
25
|
+
* Gracefully terminate this process and its entire descendant tree / process
|
|
26
|
+
* group (TERM → wait → KILL escalation). Returns true if the process (and
|
|
27
|
+
* its children) exited within the bounded wait.
|
|
28
|
+
*/
|
|
29
|
+
terminateTree(signal?: NodeJS.Signals): boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A pinned reference that can additionally prove the process's kernel-derived
|
|
34
|
+
* launch arguments. Used only by the legacy-stray sweep: candidate PIDs come
|
|
35
|
+
* from an untrusted enumeration, but every authorization fact (incarnation,
|
|
36
|
+
* argv) is read from this pinned reference, never from the enumeration.
|
|
37
|
+
*/
|
|
38
|
+
export interface TelegramStrayProcessRef extends TelegramOrphanProcessRef {
|
|
39
|
+
/** Launch arguments reported by the kernel for the pinned incarnation. */
|
|
40
|
+
args(): string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TelegramOrphanReapDeps {
|
|
44
|
+
fs?: TelegramDaemonFs;
|
|
45
|
+
now?: () => number;
|
|
46
|
+
pidAlive: (pid: number) => boolean;
|
|
47
|
+
pidIncarnation: (pid: number) => string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Opens a stable process reference bound to the exact process incarnation at
|
|
50
|
+
* open time. The reference must be opened BEFORE the incarnation check so
|
|
51
|
+
* that termination operates on the same identity that was proven stale.
|
|
52
|
+
*/
|
|
53
|
+
processReference?: (pid: number) => TelegramOrphanProcessRef | undefined;
|
|
54
|
+
platform?: NodeJS.Platform;
|
|
55
|
+
/**
|
|
56
|
+
* Enumerates candidate PIDs for the legacy-stray sweep. The list is
|
|
57
|
+
* untrusted discovery input only; authorization derives exclusively from
|
|
58
|
+
* the pinned {@link TelegramStrayProcessRef}. Defaults to a bounded POSIX
|
|
59
|
+
* `ps` listing; returns nothing on Windows or enumeration failure.
|
|
60
|
+
*/
|
|
61
|
+
listCandidatePids?: () => Promise<number[]>;
|
|
62
|
+
/** Opens a pinned argv-capable reference for stray verification. */
|
|
63
|
+
strayReference?: (pid: number) => TelegramStrayProcessRef | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* First-sighting ledger for stray confirmation, keyed by
|
|
66
|
+
* `pid|incarnation` with the first-seen timestamp. Injectable for tests;
|
|
67
|
+
* defaults to a process-lifetime module ledger.
|
|
68
|
+
*/
|
|
69
|
+
straySightings?: Map<string, number>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface TelegramOrphanCandidate {
|
|
73
|
+
marker: TelegramOwnerMarker;
|
|
74
|
+
executablePath?: string;
|
|
75
|
+
argv?: string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type OrphanReapDecision =
|
|
79
|
+
| { kind: "reaped"; pid: number; acquisitionId: string }
|
|
80
|
+
| { kind: "refused"; pid: number; acquisitionId: string; reason: string }
|
|
81
|
+
| { kind: "inert"; pid: number; acquisitionId: string };
|
|
82
|
+
|
|
83
|
+
export interface TelegramOrphanRecoveryReceipt {
|
|
84
|
+
version: 1;
|
|
85
|
+
agentDir: string;
|
|
86
|
+
currentOwnerId: string;
|
|
87
|
+
currentAcquisitionId: string;
|
|
88
|
+
currentPid: number;
|
|
89
|
+
createdAt: number;
|
|
90
|
+
candidates: number;
|
|
91
|
+
terminated: number;
|
|
92
|
+
refused: number;
|
|
93
|
+
inert: number;
|
|
94
|
+
// bounded, secret-free
|
|
95
|
+
reasons: Record<string, number>;
|
|
96
|
+
// no command lines, tokens, chatIds, env dumps
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Maximum number of candidate markers the sweep will inspect and attempt to
|
|
101
|
+
* reap. A runaway registry cannot turn the sweep into unbounded wall time.
|
|
102
|
+
*/
|
|
103
|
+
const MAX_REAP_CANDIDATES = 64;
|
|
104
|
+
|
|
105
|
+
/** Bounded cooperative-termination wait before escalating to hard kill (ms). */
|
|
106
|
+
const TERM_GRACE_MS = 2_000;
|
|
107
|
+
/** Bounded hard-kill wait before declaring termination failed (ms). */
|
|
108
|
+
const KILL_WAIT_MS = 1_500;
|
|
109
|
+
|
|
110
|
+
/** Maximum PIDs the legacy-stray enumeration will inspect per sweep. */
|
|
111
|
+
const MAX_STRAY_SCAN_PIDS = 4096;
|
|
112
|
+
/** Maximum stray terminations per sweep; the rest wait for the next cadence. */
|
|
113
|
+
const MAX_STRAY_TERMINATIONS = 8;
|
|
114
|
+
/**
|
|
115
|
+
* A stray must be sighted twice with the same pinned incarnation at least this
|
|
116
|
+
* far apart before it may be terminated. A legitimate successor daemon reaches
|
|
117
|
+
* ownership (and writes its marker) within seconds of spawning, so it can
|
|
118
|
+
* never accumulate a confirmed unmarked sighting; a pre-registry zombie
|
|
119
|
+
* persists across sweeps and is confirmed on the second one.
|
|
120
|
+
*/
|
|
121
|
+
const STRAY_CONFIRMATION_MS = 45_000;
|
|
122
|
+
/** Bounded size of the first-sighting ledger. */
|
|
123
|
+
const MAX_STRAY_SIGHTINGS = 256;
|
|
124
|
+
|
|
125
|
+
/** Process-lifetime default first-sighting ledger for the stray sweep. */
|
|
126
|
+
const moduleStraySightings = new Map<string, number>();
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Exact invocation-signature test for a legacy Telegram daemon owner:
|
|
130
|
+
* a `notify daemon-internal` subcommand bound to exactly this agent dir.
|
|
131
|
+
* This is deliberately an exact-token match on kernel-reported argv from a
|
|
132
|
+
* pinned reference — never a substring similarity over an untrusted dump.
|
|
133
|
+
*/
|
|
134
|
+
export function isLegacyStrayDaemonArgs(args: readonly string[], agentDir: string): boolean {
|
|
135
|
+
const resolvedAgentDir = path.resolve(agentDir);
|
|
136
|
+
let hasSubcommand = false;
|
|
137
|
+
let agentDirMatches = false;
|
|
138
|
+
for (let i = 0; i < args.length; i++) {
|
|
139
|
+
if (args[i] === "notify" && args[i + 1] === "daemon-internal") hasSubcommand = true;
|
|
140
|
+
if (args[i] === "--agent-dir") {
|
|
141
|
+
const value = args[i + 1];
|
|
142
|
+
if (typeof value === "string" && path.resolve(value) === resolvedAgentDir) agentDirMatches = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return hasSubcommand && agentDirMatches;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Bounded POSIX PID enumeration; empty on Windows or on any failure. */
|
|
149
|
+
async function defaultListCandidatePids(platform: NodeJS.Platform): Promise<number[]> {
|
|
150
|
+
if (platform === "win32") return [];
|
|
151
|
+
try {
|
|
152
|
+
const out = await $`ps -axo pid=`.quiet().text();
|
|
153
|
+
const pids: number[] = [];
|
|
154
|
+
for (const line of out.split("\n")) {
|
|
155
|
+
const pid = Number.parseInt(line.trim(), 10);
|
|
156
|
+
if (Number.isSafeInteger(pid) && pid > 0) pids.push(pid);
|
|
157
|
+
if (pids.length >= MAX_STRAY_SCAN_PIDS) break;
|
|
158
|
+
}
|
|
159
|
+
return pids;
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Default pinned argv-capable reference over the native process bindings. */
|
|
166
|
+
function defaultStrayReference(pid: number): TelegramStrayProcessRef | undefined {
|
|
167
|
+
try {
|
|
168
|
+
const ref = nativeProcessBindings().Process.fromPid(pid);
|
|
169
|
+
if (!ref || !isProcessIncarnation(ref.incarnation)) return undefined;
|
|
170
|
+
return {
|
|
171
|
+
incarnation: ref.incarnation,
|
|
172
|
+
args: () => {
|
|
173
|
+
try {
|
|
174
|
+
return ref.args();
|
|
175
|
+
} catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
terminateTree: (signal?: NodeJS.Signals) => {
|
|
180
|
+
try {
|
|
181
|
+
return ref.killTree(signal === "SIGKILL" ? 9 : 15) > 0;
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
} catch {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Identity-bound, process-group-aware termination.
|
|
194
|
+
*
|
|
195
|
+
* Uses the stable process reference (opened before the incarnation check) so
|
|
196
|
+
* that a reused PID cannot be signaled through a stale handle. The native
|
|
197
|
+
* `terminateTree` signals the process AND its descendants / process group,
|
|
198
|
+
* proving complete owned process-group cleanup rather than root-only signal.
|
|
199
|
+
*
|
|
200
|
+
* Falls back to POSIX `process.kill(-pgid)` when a native reference is
|
|
201
|
+
* unavailable, attempting the negative-pid group signal first.
|
|
202
|
+
*/
|
|
203
|
+
async function terminateOwnedProcessTree(
|
|
204
|
+
pid: number,
|
|
205
|
+
ref: TelegramOrphanProcessRef | undefined,
|
|
206
|
+
deps: TelegramOrphanReapDeps,
|
|
207
|
+
): Promise<boolean> {
|
|
208
|
+
// Preferred path: native stable reference with process-group termination.
|
|
209
|
+
if (ref) {
|
|
210
|
+
try {
|
|
211
|
+
const exited = await boundedTerminationWait(pid, deps, () => {
|
|
212
|
+
try {
|
|
213
|
+
return ref.terminateTree("SIGTERM");
|
|
214
|
+
} catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
if (exited) return true;
|
|
219
|
+
// Escalate to hard kill via the same stable reference.
|
|
220
|
+
try {
|
|
221
|
+
ref.terminateTree("SIGKILL");
|
|
222
|
+
} catch {
|
|
223
|
+
return !deps.pidAlive(pid);
|
|
224
|
+
}
|
|
225
|
+
return await boundedTerminationWait(pid, deps, () => false, KILL_WAIT_MS);
|
|
226
|
+
} catch {
|
|
227
|
+
return !deps.pidAlive(pid);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Fallback: POSIX process-group signal via negative-pid.
|
|
232
|
+
try {
|
|
233
|
+
try {
|
|
234
|
+
process.kill(-pid, "SIGTERM");
|
|
235
|
+
} catch (e) {
|
|
236
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
237
|
+
if (code === "ESRCH") return true;
|
|
238
|
+
// EPERM or ESRCH on the group: try root-only signal as last resort.
|
|
239
|
+
try {
|
|
240
|
+
process.kill(pid, "SIGTERM");
|
|
241
|
+
} catch (e2) {
|
|
242
|
+
if ((e2 as NodeJS.ErrnoException).code === "ESRCH") return true;
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const exited = await boundedTerminationWait(pid, deps, () => false);
|
|
247
|
+
if (exited) return true;
|
|
248
|
+
try {
|
|
249
|
+
process.kill(-pid, "SIGKILL");
|
|
250
|
+
} catch (e) {
|
|
251
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
252
|
+
if (code === "ESRCH") return true;
|
|
253
|
+
try {
|
|
254
|
+
process.kill(pid, "SIGKILL");
|
|
255
|
+
} catch (e2) {
|
|
256
|
+
if ((e2 as NodeJS.ErrnoException).code === "ESRCH") return true;
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return await boundedTerminationWait(pid, deps, () => false, KILL_WAIT_MS);
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Poll process liveness within a bounded deadline. The optional `signalFn`
|
|
268
|
+
* fires the initial signal; it is called once, then liveness is polled.
|
|
269
|
+
* Incarnation drift also proves exit (the reference's process is gone even if
|
|
270
|
+
* the PID was reused).
|
|
271
|
+
*/
|
|
272
|
+
async function boundedTerminationWait(
|
|
273
|
+
pid: number,
|
|
274
|
+
deps: TelegramOrphanReapDeps,
|
|
275
|
+
signalFn: () => boolean,
|
|
276
|
+
budgetMs = TERM_GRACE_MS,
|
|
277
|
+
): Promise<boolean> {
|
|
278
|
+
signalFn();
|
|
279
|
+
const clock = deps.now ?? Date.now;
|
|
280
|
+
const deadline = clock() + budgetMs;
|
|
281
|
+
const step = Math.max(Math.floor(budgetMs / 40), 25);
|
|
282
|
+
while (clock() < deadline) {
|
|
283
|
+
if (!deps.pidAlive(pid)) return true;
|
|
284
|
+
await new Promise<void>(r => setTimeout(r, step));
|
|
285
|
+
}
|
|
286
|
+
return !deps.pidAlive(pid);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Bounded stale-owner sweep. Authorizes termination only from product-owned
|
|
291
|
+
* marker registry bound to exact agentDir digest + acquisitionId + pid + incarnation.
|
|
292
|
+
* Never authorizes from bare /proc cmdline similarity; markers are the trust anchor.
|
|
293
|
+
*
|
|
294
|
+
* Safety invariants:
|
|
295
|
+
* 1. The stable process reference is opened BEFORE the incarnation check and
|
|
296
|
+
* used for termination, so a reused PID cannot be signaled through a stale
|
|
297
|
+
* handle.
|
|
298
|
+
* 2. Process-group termination (not root-only signal) ensures reparented
|
|
299
|
+
* descendants of an orphaned daemon are also cleaned up.
|
|
300
|
+
* 3. Zombies and dead processes are classified inert without any signaling.
|
|
301
|
+
* 4. The sweep is bounded: at most MAX_REAP_CANDIDATES markers are inspected.
|
|
302
|
+
*/
|
|
303
|
+
export async function reapTelegramDaemonOrphans(input: {
|
|
304
|
+
agentDir: string;
|
|
305
|
+
currentOwnerId: string;
|
|
306
|
+
currentAcquisitionId: string;
|
|
307
|
+
currentPid: number;
|
|
308
|
+
currentIncarnation: string;
|
|
309
|
+
fsImpl: TelegramDaemonFs;
|
|
310
|
+
deps: TelegramOrphanReapDeps;
|
|
311
|
+
}): Promise<{ decisions: OrphanReapDecision[]; receipt: TelegramOrphanRecoveryReceipt }> {
|
|
312
|
+
const fsImpl = input.fsImpl;
|
|
313
|
+
const deps = input.deps;
|
|
314
|
+
const candidates = await listTelegramOwnerMarkers(fsImpl, input.agentDir);
|
|
315
|
+
const decisions: OrphanReapDecision[] = [];
|
|
316
|
+
const reasons: Record<string, number> = {};
|
|
317
|
+
let terminated = 0;
|
|
318
|
+
let refused = 0;
|
|
319
|
+
let inert = 0;
|
|
320
|
+
|
|
321
|
+
// Bound the sweep: process at most MAX_REAP_CANDIDATES markers.
|
|
322
|
+
const bounded = candidates.slice(0, MAX_REAP_CANDIDATES);
|
|
323
|
+
|
|
324
|
+
for (const entry of bounded) {
|
|
325
|
+
if (!entry.marker) {
|
|
326
|
+
decisions.push({
|
|
327
|
+
kind: "refused",
|
|
328
|
+
pid: -1,
|
|
329
|
+
acquisitionId: entry.acquisitionId,
|
|
330
|
+
reason: "malformed_or_foreign",
|
|
331
|
+
});
|
|
332
|
+
refused += 1;
|
|
333
|
+
reasons.malformed_or_foreign = (reasons.malformed_or_foreign ?? 0) + 1;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const m = entry.marker;
|
|
337
|
+
if (
|
|
338
|
+
m.acquisitionId === input.currentAcquisitionId &&
|
|
339
|
+
m.ownerId === input.currentOwnerId &&
|
|
340
|
+
m.pid === input.currentPid &&
|
|
341
|
+
m.incarnation === input.currentIncarnation
|
|
342
|
+
) {
|
|
343
|
+
// current owner — never signal
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
// Skip markers whose pid/incarnation proves they are the current live owner
|
|
347
|
+
if (m.pid === input.currentPid && m.incarnation === input.currentIncarnation) {
|
|
348
|
+
decisions.push({ kind: "refused", pid: m.pid, acquisitionId: m.acquisitionId, reason: "current_incarnation" });
|
|
349
|
+
refused += 1;
|
|
350
|
+
reasons.current_incarnation = (reasons.current_incarnation ?? 0) + 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
// Open the stable process reference BEFORE the incarnation check.
|
|
354
|
+
// This pins the exact process identity for all subsequent operations.
|
|
355
|
+
const ref = deps.processReference?.(m.pid);
|
|
356
|
+
|
|
357
|
+
// Require stable incarnation authority; if unavailable fail closed.
|
|
358
|
+
// BUT: if pidAlive says absent, classify as inert (dead/zombie) without signaling.
|
|
359
|
+
if (!deps.pidAlive(m.pid)) {
|
|
360
|
+
// Process is dead or a zombie — inert, never signal.
|
|
361
|
+
decisions.push({ kind: "inert", pid: m.pid, acquisitionId: m.acquisitionId });
|
|
362
|
+
inert += 1;
|
|
363
|
+
// Clean stale marker for absent/dead pid.
|
|
364
|
+
await removeTelegramOwnerMarker(fsImpl, input.agentDir, m.acquisitionId).catch(() => undefined);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const curIncarnation = deps.pidIncarnation(m.pid);
|
|
368
|
+
if (!isProcessIncarnation(curIncarnation)) {
|
|
369
|
+
// Without stable proof of incarnation, fail closed — do not signal.
|
|
370
|
+
decisions.push({
|
|
371
|
+
kind: "refused",
|
|
372
|
+
pid: m.pid,
|
|
373
|
+
acquisitionId: m.acquisitionId,
|
|
374
|
+
reason: "incarnation_unavailable",
|
|
375
|
+
});
|
|
376
|
+
refused += 1;
|
|
377
|
+
reasons.incarnation_unavailable = (reasons.incarnation_unavailable ?? 0) + 1;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (curIncarnation !== m.incarnation) {
|
|
381
|
+
// PID reused — old owner is inert, marker is stale. Never signal a
|
|
382
|
+
// live process that now belongs to a different incarnation.
|
|
383
|
+
decisions.push({ kind: "inert", pid: m.pid, acquisitionId: m.acquisitionId });
|
|
384
|
+
inert += 1;
|
|
385
|
+
await removeTelegramOwnerMarker(fsImpl, input.agentDir, m.acquisitionId).catch(() => undefined);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
// Attempt bounded process-group TERM then KILL using the stable reference
|
|
389
|
+
// opened before the incarnation check.
|
|
390
|
+
const exited = await terminateOwnedProcessTree(m.pid, ref, deps);
|
|
391
|
+
if (exited || !deps.pidAlive(m.pid)) {
|
|
392
|
+
decisions.push({ kind: "reaped", pid: m.pid, acquisitionId: m.acquisitionId });
|
|
393
|
+
terminated += 1;
|
|
394
|
+
await removeTelegramOwnerMarker(fsImpl, input.agentDir, m.acquisitionId).catch(() => undefined);
|
|
395
|
+
} else {
|
|
396
|
+
decisions.push({ kind: "refused", pid: m.pid, acquisitionId: m.acquisitionId, reason: "termination_failed" });
|
|
397
|
+
refused += 1;
|
|
398
|
+
reasons.termination_failed = (reasons.termination_failed ?? 0) + 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Legacy-stray sweep: daemons spawned by builds that predate the marker
|
|
403
|
+
// registry never registered a marker and may not own the state file, so the
|
|
404
|
+
// marker sweep above cannot see them — they keep polling getUpdates and
|
|
405
|
+
// 409-starve every fresh owner. Candidate PIDs come from an untrusted
|
|
406
|
+
// enumeration; ALL authorization facts (incarnation, argv) come from the
|
|
407
|
+
// pinned native reference. Termination additionally requires a confirmed
|
|
408
|
+
// second sighting of the same pinned incarnation, so a legitimate successor
|
|
409
|
+
// mid-handoff (which reaches ownership and writes its marker within
|
|
410
|
+
// seconds) can never be killed.
|
|
411
|
+
try {
|
|
412
|
+
const markerPids = new Set<number>();
|
|
413
|
+
for (const entry of candidates) if (entry.marker) markerPids.add(entry.marker.pid);
|
|
414
|
+
const clock = deps.now ?? Date.now;
|
|
415
|
+
const sightings = deps.straySightings ?? moduleStraySightings;
|
|
416
|
+
const listPids = deps.listCandidatePids ?? (() => defaultListCandidatePids(deps.platform ?? process.platform));
|
|
417
|
+
const strayRef = deps.strayReference ?? defaultStrayReference;
|
|
418
|
+
const liveKeys = new Set<string>();
|
|
419
|
+
let strayTerminations = 0;
|
|
420
|
+
for (const pid of (await listPids()).slice(0, MAX_STRAY_SCAN_PIDS)) {
|
|
421
|
+
if (pid === input.currentPid || pid === process.pid || markerPids.has(pid)) continue;
|
|
422
|
+
const ref = strayRef(pid);
|
|
423
|
+
if (!ref || !isProcessIncarnation(ref.incarnation)) continue;
|
|
424
|
+
if (ref.incarnation === input.currentIncarnation) continue;
|
|
425
|
+
if (!isLegacyStrayDaemonArgs(ref.args(), input.agentDir)) continue;
|
|
426
|
+
const key = `${pid}|${ref.incarnation}`;
|
|
427
|
+
liveKeys.add(key);
|
|
428
|
+
const firstSeenAt = sightings.get(key);
|
|
429
|
+
if (firstSeenAt === undefined) {
|
|
430
|
+
if (sightings.size < MAX_STRAY_SIGHTINGS) sightings.set(key, clock());
|
|
431
|
+
reasons.legacy_stray_pending_confirmation = (reasons.legacy_stray_pending_confirmation ?? 0) + 1;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (clock() - firstSeenAt < STRAY_CONFIRMATION_MS) {
|
|
435
|
+
reasons.legacy_stray_pending_confirmation = (reasons.legacy_stray_pending_confirmation ?? 0) + 1;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (strayTerminations >= MAX_STRAY_TERMINATIONS) break;
|
|
439
|
+
const acquisitionId = `legacy-stray:${pid}`;
|
|
440
|
+
const exited = await terminateOwnedProcessTree(pid, ref, deps);
|
|
441
|
+
if (exited || !deps.pidAlive(pid)) {
|
|
442
|
+
decisions.push({ kind: "reaped", pid, acquisitionId });
|
|
443
|
+
terminated += 1;
|
|
444
|
+
strayTerminations += 1;
|
|
445
|
+
sightings.delete(key);
|
|
446
|
+
liveKeys.delete(key);
|
|
447
|
+
reasons.legacy_stray_reaped = (reasons.legacy_stray_reaped ?? 0) + 1;
|
|
448
|
+
} else {
|
|
449
|
+
decisions.push({ kind: "refused", pid, acquisitionId, reason: "termination_failed" });
|
|
450
|
+
refused += 1;
|
|
451
|
+
reasons.legacy_stray_termination_failed = (reasons.legacy_stray_termination_failed ?? 0) + 1;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Drop ledger entries whose process no longer matched this sweep, so the
|
|
455
|
+
// bounded ledger cannot fill with dead incarnations.
|
|
456
|
+
for (const key of [...sightings.keys()]) if (!liveKeys.has(key)) sightings.delete(key);
|
|
457
|
+
} catch {
|
|
458
|
+
reasons.legacy_stray_scan_failed = (reasons.legacy_stray_scan_failed ?? 0) + 1;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// If the registry exceeded the bound, record the overflow.
|
|
462
|
+
if (candidates.length > MAX_REAP_CANDIDATES) {
|
|
463
|
+
reasons.registry_overflow = candidates.length;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const receipt: TelegramOrphanRecoveryReceipt = {
|
|
467
|
+
version: 1,
|
|
468
|
+
agentDir: input.agentDir,
|
|
469
|
+
currentOwnerId: input.currentOwnerId,
|
|
470
|
+
currentAcquisitionId: input.currentAcquisitionId,
|
|
471
|
+
currentPid: input.currentPid,
|
|
472
|
+
createdAt: (deps.now ?? Date.now)(),
|
|
473
|
+
candidates: candidates.length,
|
|
474
|
+
terminated,
|
|
475
|
+
refused,
|
|
476
|
+
inert,
|
|
477
|
+
reasons,
|
|
478
|
+
};
|
|
479
|
+
// Bound receipt: ensure secret-free (no tokens, no chatIds, no env)
|
|
480
|
+
return { decisions, receipt };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export async function writeTelegramOrphanRecoveryReceipt(
|
|
484
|
+
fsImpl: TelegramDaemonFs,
|
|
485
|
+
agentDir: string,
|
|
486
|
+
receipt: TelegramOrphanRecoveryReceipt,
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
const { daemonPaths } = await import("./daemon-paths");
|
|
489
|
+
const file = daemonPaths(agentDir).recoveryReceipt;
|
|
490
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
491
|
+
// Bounded size: JSON stringify once, truncate to 4KiB if needed (secret-free so truncation is safe)
|
|
492
|
+
let data = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
493
|
+
if (data.length > 4096) data = data.slice(0, 4096);
|
|
494
|
+
await fsImpl.mkdir(path.dirname(file), { recursive: true, mode: 0o700 }).catch(() => undefined);
|
|
495
|
+
await fsImpl.writeFile(tmp, data, { mode: 0o600 });
|
|
496
|
+
await fsImpl.chmod(tmp, 0o600).catch(() => undefined);
|
|
497
|
+
await fsImpl.rename(tmp, file);
|
|
498
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { isProcessIncarnation } from "../broker/process-incarnation";
|
|
3
|
+
import { agentDirDigest, telegramDaemonOwnerMarkerPath } from "./daemon-paths";
|
|
4
|
+
import type { TelegramDaemonFs } from "./telegram-daemon";
|
|
5
|
+
|
|
6
|
+
export interface TelegramOwnerMarker {
|
|
7
|
+
version: 1;
|
|
8
|
+
agentDir: string;
|
|
9
|
+
agentDirDigest: string;
|
|
10
|
+
ownerId: string;
|
|
11
|
+
acquisitionId: string;
|
|
12
|
+
pid: number;
|
|
13
|
+
incarnation: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
startedAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validMarker(value: unknown): value is TelegramOwnerMarker {
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
20
|
+
const v = value as Record<string, unknown>;
|
|
21
|
+
return (
|
|
22
|
+
v.version === 1 &&
|
|
23
|
+
typeof v.agentDir === "string" &&
|
|
24
|
+
typeof v.agentDirDigest === "string" &&
|
|
25
|
+
typeof v.ownerId === "string" &&
|
|
26
|
+
v.ownerId.length > 0 &&
|
|
27
|
+
typeof v.acquisitionId === "string" &&
|
|
28
|
+
v.acquisitionId.length > 0 &&
|
|
29
|
+
typeof v.pid === "number" &&
|
|
30
|
+
Number.isSafeInteger(v.pid) &&
|
|
31
|
+
v.pid > 0 &&
|
|
32
|
+
typeof v.incarnation === "string" &&
|
|
33
|
+
isProcessIncarnation(v.incarnation) &&
|
|
34
|
+
typeof v.createdAt === "number" &&
|
|
35
|
+
Number.isSafeInteger(v.createdAt) &&
|
|
36
|
+
typeof v.startedAt === "number" &&
|
|
37
|
+
Number.isSafeInteger(v.startedAt)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function writeTelegramOwnerMarker(
|
|
42
|
+
fsImpl: TelegramDaemonFs,
|
|
43
|
+
agentDir: string,
|
|
44
|
+
marker: TelegramOwnerMarker,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const file = telegramDaemonOwnerMarkerPath(agentDir, marker.acquisitionId);
|
|
47
|
+
const dir = path.dirname(file);
|
|
48
|
+
await fsImpl.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
49
|
+
await fsImpl.chmod(dir, 0o700).catch(() => undefined);
|
|
50
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
51
|
+
await fsImpl.writeFile(tmp, `${JSON.stringify(marker, null, 2)}\n`, { mode: 0o600 });
|
|
52
|
+
await fsImpl.chmod(tmp, 0o600).catch(() => undefined);
|
|
53
|
+
await fsImpl.rename(tmp, file);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function removeTelegramOwnerMarker(
|
|
57
|
+
fsImpl: TelegramDaemonFs,
|
|
58
|
+
agentDir: string,
|
|
59
|
+
acquisitionId: string,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const file = telegramDaemonOwnerMarkerPath(agentDir, acquisitionId);
|
|
62
|
+
await fsImpl.unlink(file).catch(() => undefined);
|
|
63
|
+
// Best-effort directory cleanup; do not fail if markers remain.
|
|
64
|
+
try {
|
|
65
|
+
const entries = await fsImpl.readdir(path.dirname(file));
|
|
66
|
+
if (entries.length === 0) await fsImpl.unlink(path.dirname(file)).catch(() => undefined);
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function readTelegramOwnerMarker(
|
|
71
|
+
fsImpl: TelegramDaemonFs,
|
|
72
|
+
agentDir: string,
|
|
73
|
+
acquisitionId: string,
|
|
74
|
+
): Promise<TelegramOwnerMarker | undefined> {
|
|
75
|
+
const file = telegramDaemonOwnerMarkerPath(agentDir, acquisitionId);
|
|
76
|
+
try {
|
|
77
|
+
const raw = await fsImpl.readFile(file, "utf8");
|
|
78
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
79
|
+
if (!validMarker(parsed)) return undefined;
|
|
80
|
+
// Digest must match the path's agent dir; otherwise fail closed (foreign).
|
|
81
|
+
if (parsed.agentDirDigest !== agentDirDigest(agentDir)) return undefined;
|
|
82
|
+
if (path.resolve(parsed.agentDir) !== path.resolve(agentDir)) return undefined;
|
|
83
|
+
return parsed;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ListedOwnerMarker {
|
|
91
|
+
acquisitionId: string;
|
|
92
|
+
marker: TelegramOwnerMarker | undefined;
|
|
93
|
+
// undefined marker means malformed/unreadable/foreign-digest-mismatch (fail-closed).
|
|
94
|
+
raw?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function listTelegramOwnerMarkers(
|
|
98
|
+
fsImpl: TelegramDaemonFs,
|
|
99
|
+
agentDir: string,
|
|
100
|
+
): Promise<ListedOwnerMarker[]> {
|
|
101
|
+
const { daemonPaths } = await import("./daemon-paths");
|
|
102
|
+
const dir = daemonPaths(agentDir).ownerRegistry;
|
|
103
|
+
let entries: string[];
|
|
104
|
+
try {
|
|
105
|
+
entries = await fsImpl.readdir(dir);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
const out: ListedOwnerMarker[] = [];
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (!entry.endsWith(".json")) continue;
|
|
113
|
+
const acquisitionId = entry.slice(0, -5);
|
|
114
|
+
const file = path.join(dir, entry);
|
|
115
|
+
try {
|
|
116
|
+
const raw = await fsImpl.readFile(file, "utf8");
|
|
117
|
+
let parsed: unknown;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
} catch {
|
|
121
|
+
out.push({ acquisitionId, marker: undefined, raw });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!validMarker(parsed)) {
|
|
125
|
+
out.push({ acquisitionId, marker: undefined, raw });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (parsed.agentDirDigest !== agentDirDigest(agentDir)) {
|
|
129
|
+
out.push({ acquisitionId, marker: undefined, raw });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (path.resolve(parsed.agentDir) !== path.resolve(agentDir)) {
|
|
133
|
+
out.push({ acquisitionId, marker: undefined, raw });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
out.push({ acquisitionId, marker: parsed });
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
139
|
+
out.push({ acquisitionId, marker: undefined });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function clearStaleTelegramOwnerMarkers(
|
|
146
|
+
fsImpl: TelegramDaemonFs,
|
|
147
|
+
agentDir: string,
|
|
148
|
+
pidAlive: (pid: number) => boolean,
|
|
149
|
+
pidIncarnation: (pid: number) => string | undefined,
|
|
150
|
+
now: number,
|
|
151
|
+
ttlMs = 86_400_000,
|
|
152
|
+
): Promise<number> {
|
|
153
|
+
const markers = await listTelegramOwnerMarkers(fsImpl, agentDir);
|
|
154
|
+
let removed = 0;
|
|
155
|
+
for (const entry of markers) {
|
|
156
|
+
if (!entry.marker) {
|
|
157
|
+
// Malformed markers are not auto-removed here; the reaper counts them as refused.
|
|
158
|
+
// But very old malformed cruft beyond TTL may be removed if its mtime is ancient and pid proves absent.
|
|
159
|
+
// For now fail closed and leave it.
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const m = entry.marker;
|
|
163
|
+
const alive = pidAlive(m.pid);
|
|
164
|
+
const currentIncarnation = pidIncarnation(m.pid);
|
|
165
|
+
const pidReused =
|
|
166
|
+
isProcessIncarnation(currentIncarnation) &&
|
|
167
|
+
isProcessIncarnation(m.incarnation) &&
|
|
168
|
+
currentIncarnation !== m.incarnation;
|
|
169
|
+
const isStaleTime = now - m.createdAt > ttlMs;
|
|
170
|
+
if ((!alive || pidReused) && isStaleTime) {
|
|
171
|
+
await removeTelegramOwnerMarker(fsImpl, agentDir, m.acquisitionId);
|
|
172
|
+
removed += 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return removed;
|
|
176
|
+
}
|