@gajae-code/coding-agent 0.11.1 → 0.11.3
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 +64 -0
- package/dist/types/cli/config-cli.d.ts +11 -1
- package/dist/types/cli/launch-disposition.d.ts +23 -0
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/cli/session-picker.d.ts +2 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/mcp-serve.d.ts +33 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/commands/notify.d.ts +0 -2
- package/dist/types/commands/sdk.d.ts +1 -1
- package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
- package/dist/types/config/file-lock.d.ts +14 -4
- package/dist/types/config/keybindings.d.ts +42 -1
- package/dist/types/config/model-auth.d.ts +3 -0
- package/dist/types/config/model-bindings-applier.d.ts +13 -0
- package/dist/types/config/model-discovery-manager.d.ts +49 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-registry.d.ts +16 -15
- package/dist/types/config/model-resolver.d.ts +28 -18
- package/dist/types/config/models-config-schema.d.ts +11 -0
- package/dist/types/config/settings-schema.d.ts +141 -11
- package/dist/types/config/settings.d.ts +14 -11
- package/dist/types/coordinator-mcp/policy.d.ts +5 -0
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +0 -6
- package/dist/types/edit/index.d.ts +3 -0
- package/dist/types/eval/py/env.d.ts +9 -0
- package/dist/types/extensibility/extensions/loader.d.ts +2 -1
- package/dist/types/extensibility/extensions/runner.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +34 -3
- package/dist/types/extensibility/hooks/runner.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
- package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
- package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
- package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
- package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
- package/dist/types/gjc-runtime/team-store.d.ts +160 -0
- package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
- package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
- package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/goals/state.d.ts +8 -0
- package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/state.d.ts +11 -4
- package/dist/types/lsp/lspmux.d.ts +5 -3
- package/dist/types/lsp/path-trust.d.ts +1 -0
- package/dist/types/main.d.ts +4 -2
- package/dist/types/memories/storage.d.ts +1 -0
- package/dist/types/modes/action-registry.d.ts +29 -0
- package/dist/types/modes/components/chrome.d.ts +14 -0
- package/dist/types/modes/components/command-palette.d.ts +27 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
- package/dist/types/modes/components/index.d.ts +2 -1
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
- package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
- package/dist/types/modes/components/queue-pane.d.ts +12 -0
- package/dist/types/modes/components/read-tool-group.d.ts +6 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
- package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/show-images-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +1 -94
- package/dist/types/modes/components/tasks-pane.d.ts +15 -0
- package/dist/types/modes/components/theme-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +15 -0
- package/dist/types/modes/components/tool-status-header.d.ts +111 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -1
- package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
- package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
- package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
- package/dist/types/modes/interactive-mode.d.ts +19 -17
- package/dist/types/modes/jobs-observer.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
- package/dist/types/modes/tasks-aggregator.d.ts +39 -0
- package/dist/types/modes/transcript-item-registry.d.ts +60 -0
- package/dist/types/modes/types.d.ts +20 -13
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +78 -12
- package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
- package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
- package/dist/types/sdk/broker/session-index.d.ts +0 -1
- package/dist/types/sdk/broker/state-version.d.ts +2 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
- package/dist/types/sdk/bus/config-commands.d.ts +1 -1
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/html-format.d.ts +3 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/notification-service.d.ts +20 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
- package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
- package/dist/types/sdk/bus/rich-document.d.ts +7 -0
- package/dist/types/sdk/bus/rich-render.d.ts +18 -41
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
- package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/session-directory.d.ts +56 -0
- package/dist/types/sdk/session.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +82 -47
- package/dist/types/session/agent-storage.d.ts +7 -0
- package/dist/types/session/fallback-chain-controller.d.ts +10 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
- package/dist/types/session/messages.d.ts +16 -0
- package/dist/types/session/session-manager-internal.d.ts +14 -0
- package/dist/types/session/session-manager.d.ts +82 -12
- package/dist/types/session/session-storage.d.ts +43 -3
- package/dist/types/session/volatile-context-pruning.d.ts +15 -0
- package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
- package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
- package/dist/types/system-prompt.d.ts +2 -11
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/thinking.d.ts +7 -0
- package/dist/types/tool-discovery/tool-index.d.ts +1 -50
- package/dist/types/tools/ask.d.ts +64 -2
- package/dist/types/tools/bash.d.ts +3 -0
- package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
- package/dist/types/tools/cron.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/index.d.ts +30 -15
- package/dist/types/tools/output-meta.d.ts +1 -0
- package/dist/types/tools/read.d.ts +5 -0
- package/dist/types/tools/search-tool-bm25.d.ts +3 -4
- package/dist/types/tools/subagent.d.ts +1 -0
- package/dist/types/utils/event-bus.d.ts +1 -1
- package/dist/types/utils/file-mentions.d.ts +3 -3
- package/dist/types/web/search/index.d.ts +3 -1
- package/package.json +21 -9
- package/scripts/build-sdk-package-smoke.ts +34 -1
- package/scripts/generate-hotkeys-docs.ts +74 -0
- package/scripts/generate-sdk-operation-inventory.ts +64 -20
- package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
- package/src/async/job-manager.ts +49 -6
- package/src/cli/config-cli.ts +75 -2
- package/src/cli/launch-disposition.ts +42 -0
- package/src/cli/notify-cli.ts +92 -26
- package/src/cli/session-picker.ts +20 -3
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/harness.ts +34 -106
- package/src/commands/launch.ts +6 -4
- package/src/commands/mcp-serve.ts +85 -8
- package/src/commands/mcp.ts +2 -2
- package/src/commands/notify.ts +8 -4
- package/src/commands/sdk.ts +61 -40
- package/src/commands/ultragoal.ts +4 -1
- package/src/config/atomic-yaml-patch.ts +80 -13
- package/src/config/file-lock.ts +105 -43
- package/src/config/keybindings.ts +64 -20
- package/src/config/model-auth.ts +9 -0
- package/src/config/model-bindings-applier.ts +139 -0
- package/src/config/model-discovery-manager.ts +224 -0
- package/src/config/model-equivalence.ts +66 -0
- package/src/config/model-registry.ts +169 -302
- package/src/config/model-resolver.ts +361 -369
- package/src/config/models-config-schema.ts +5 -4
- package/src/config/settings-schema.ts +214 -12
- package/src/config/settings.ts +458 -482
- package/src/coordinator-mcp/policy.ts +76 -11
- package/src/coordinator-mcp/question-gate-codec.ts +274 -0
- package/src/coordinator-mcp/question-state.ts +778 -0
- package/src/coordinator-mcp/server.ts +1702 -238
- package/src/debug/index.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
- package/src/defaults/gjc/skills/team/SKILL.md +16 -0
- package/src/discovery/helpers.ts +1 -50
- package/src/edit/index.ts +64 -0
- package/src/eval/py/env.ts +26 -0
- package/src/eval/py/kernel.ts +5 -3
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +12 -3
- package/src/extensibility/extensions/types.ts +39 -1
- package/src/extensibility/gjc-plugins/installer.ts +16 -16
- package/src/extensibility/hooks/runner.ts +2 -2
- package/src/extensibility/runtime-skill-discovery.ts +33 -13
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
- package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
- package/src/gjc-runtime/deep-interview-state.ts +317 -5
- package/src/gjc-runtime/goal-mode-request.ts +55 -8
- package/src/gjc-runtime/launch-tmux.ts +2 -5
- package/src/gjc-runtime/launch-worktree.ts +5 -5
- package/src/gjc-runtime/linux-proc.ts +71 -0
- package/src/gjc-runtime/ralplan-runtime.ts +74 -90
- package/src/gjc-runtime/session-resolution.ts +18 -3
- package/src/gjc-runtime/session-state-sidecar.ts +6 -107
- package/src/gjc-runtime/state-migrations.ts +1 -1
- package/src/gjc-runtime/state-runtime.ts +327 -249
- package/src/gjc-runtime/state-writer.ts +36 -40
- package/src/gjc-runtime/team-launch.ts +319 -0
- package/src/gjc-runtime/team-notify.ts +326 -0
- package/src/gjc-runtime/team-runtime.ts +1099 -1482
- package/src/gjc-runtime/team-store.ts +828 -0
- package/src/gjc-runtime/team-workers.ts +962 -0
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
- package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
- package/src/gjc-runtime/tmux-sessions.ts +4 -21
- package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
- package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
- package/src/gjc-runtime/ultragoal-guard.ts +29 -5
- package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
- package/src/gjc-runtime/workflow-cli-common.ts +30 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +18 -0
- package/src/goals/continuation-timeout-guard.ts +79 -0
- package/src/goals/runtime.ts +7 -5
- package/src/goals/state.ts +18 -0
- package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
- package/src/harness-control-plane/phase-rollup.ts +4 -7
- package/src/harness-control-plane/receipt-ingest.ts +5 -0
- package/src/harness-control-plane/receipt-spool.ts +51 -7
- package/src/harness-control-plane/session-lease.ts +12 -130
- package/src/hindsight/backend.ts +26 -27
- package/src/hindsight/state.ts +101 -32
- package/src/hooks/skill-state.ts +17 -11
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/lsp/client.ts +1 -1
- package/src/lsp/config.ts +145 -26
- package/src/lsp/index.ts +1 -1
- package/src/lsp/lspmux.ts +39 -10
- package/src/lsp/path-trust.ts +81 -0
- package/src/main.ts +127 -30
- package/src/memories/index.ts +1 -0
- package/src/memories/storage.ts +6 -2
- package/src/modes/action-registry.ts +165 -0
- package/src/modes/components/chrome.ts +40 -0
- package/src/modes/components/command-palette.ts +168 -0
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/custom-provider-wizard.ts +51 -14
- package/src/modes/components/index.ts +2 -1
- package/src/modes/components/pet-selector.ts +11 -17
- package/src/modes/components/plan-preview-overlay.ts +295 -0
- package/src/modes/components/queue-mode-selector.ts +10 -27
- package/src/modes/components/queue-pane.ts +79 -0
- package/src/modes/components/read-tool-group.ts +13 -0
- package/src/modes/components/session-observer-overlay.ts +207 -772
- package/src/modes/components/sessions-dashboard.ts +181 -0
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +10 -24
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +1 -969
- package/src/modes/components/tasks-pane.ts +73 -0
- package/src/modes/components/theme-selector.ts +11 -31
- package/src/modes/components/tool-execution.ts +22 -0
- package/src/modes/components/tool-status-header.ts +1056 -0
- package/src/modes/components/transcript-viewer-overlay.ts +384 -0
- package/src/modes/controllers/btw-controller.ts +1 -0
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +24 -1
- package/src/modes/controllers/goal-mode-controller.ts +474 -0
- package/src/modes/controllers/input-controller.ts +764 -229
- package/src/modes/controllers/mode-gate.ts +24 -0
- package/src/modes/controllers/plan-mode-controller.ts +517 -0
- package/src/modes/controllers/selector-controller.ts +225 -10
- package/src/modes/controllers/stt-controller.ts +125 -0
- package/src/modes/interactive-mode.ts +289 -1150
- package/src/modes/jobs-observer.ts +3 -0
- package/src/modes/prompt-action-autocomplete.ts +7 -0
- package/src/modes/runtime-init.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
- package/src/modes/tasks-aggregator.ts +199 -0
- package/src/modes/transcript-item-registry.ts +229 -0
- package/src/modes/types.ts +45 -18
- package/src/modes/utils/ui-helpers.ts +17 -9
- package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -0
- package/src/prompts/agents/architect.md +2 -8
- package/src/prompts/agents/critic.md +2 -8
- package/src/prompts/agents/executor.md +3 -0
- package/src/prompts/agents/planner.md +4 -12
- package/src/prompts/system/custom-system-prompt.md +1 -23
- package/src/prompts/system/plan-mode-approved.md +8 -0
- package/src/prompts/system/system-prompt.md +17 -96
- package/src/prompts/tools/bash.md +4 -4
- package/src/prompts/tools/browser.md +15 -52
- package/src/prompts/tools/find.md +2 -2
- package/src/prompts/tools/hashline.md +13 -55
- package/src/prompts/tools/read.md +3 -6
- package/src/prompts/tools/search-tool-bm25.md +0 -2
- package/src/prompts/tools/search.md +2 -4
- package/src/prompts/tools/skill-discovery.md +1 -2
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-mcp/manager.ts +1 -1
- package/src/sdk/broker/broker.ts +206 -78
- package/src/sdk/broker/discovery.ts +1 -1
- package/src/sdk/broker/ensure.ts +3 -1
- package/src/sdk/broker/lifecycle-codec.ts +11 -0
- package/src/sdk/broker/lifecycle-ledger.ts +371 -74
- package/src/sdk/broker/lifecycle.ts +1399 -142
- package/src/sdk/broker/process-incarnation.ts +5 -13
- package/src/sdk/broker/session-index.ts +145 -56
- package/src/sdk/broker/state-version.ts +21 -0
- package/src/sdk/broker/transport.ts +1 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -28
- package/src/sdk/bus/chat-effect-journal.ts +10 -2
- package/src/sdk/bus/config-commands.ts +5 -4
- package/src/sdk/bus/config.ts +98 -0
- package/src/sdk/bus/conversation-store.ts +5 -29
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/html-format.ts +20 -3
- package/src/sdk/bus/index.ts +782 -94
- package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
- package/src/sdk/bus/natives-augment.d.ts +26 -1
- package/src/sdk/bus/notification-service.ts +168 -20
- package/src/sdk/bus/rate-limit-pool.ts +58 -14
- package/src/sdk/bus/recent-activity.ts +188 -74
- package/src/sdk/bus/rich-document.ts +258 -0
- package/src/sdk/bus/rich-render.ts +62 -70
- package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon.ts +1389 -237
- package/src/sdk/bus/threaded-inbound.ts +8 -3
- package/src/sdk/bus/threaded-render.ts +57 -1
- package/src/sdk/bus/topic-registry.ts +120 -13
- package/src/sdk/host/control/dispatch.ts +14 -2
- package/src/sdk/host/control/operations.ts +6 -1
- package/src/sdk/host/host.ts +44 -2
- package/src/sdk/host/query/handlers.ts +20 -22
- package/src/sdk/host/query/revision-store.ts +80 -57
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
- package/src/sdk/session-directory.ts +141 -0
- package/src/sdk/session.ts +462 -480
- package/src/session/agent-session.ts +1700 -583
- package/src/session/agent-storage.ts +18 -0
- package/src/session/cache-economics.ts +25 -8
- package/src/session/fallback-chain-controller.ts +53 -3
- package/src/session/internal/managed-session-scope.ts +2226 -0
- package/src/session/internal/managed-session-storage.ts +453 -0
- package/src/session/messages.ts +91 -2
- package/src/session/session-manager-internal.ts +33 -0
- package/src/session/session-manager.ts +1112 -333
- package/src/session/session-storage.ts +444 -39
- package/src/session/volatile-context-pruning.ts +78 -0
- package/src/setup/credential-auto-import.ts +2 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
- package/src/setup/provider-onboarding.ts +36 -61
- package/src/setup/provider-presets.json +5 -0
- package/src/skill-state/active-state.ts +3 -13
- package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
- package/src/skill-state/workflow-state-contract.ts +6 -41
- package/src/slash-commands/builtin-registry.ts +46 -11
- package/src/system-prompt.ts +37 -44
- package/src/task/agents.ts +19 -1
- package/src/task/executor.ts +90 -6
- package/src/task/index.ts +36 -21
- package/src/thinking.ts +21 -6
- package/src/tool-discovery/tool-index.ts +2 -134
- package/src/tools/ask.ts +157 -21
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/bash.ts +38 -0
- package/src/tools/browser/dead-tab-recovery.ts +78 -0
- package/src/tools/browser/tab-supervisor.ts +282 -36
- package/src/tools/computer.ts +10 -3
- package/src/tools/cron.ts +30 -16
- package/src/tools/eval.ts +1 -2
- package/src/tools/fetch.ts +11 -4
- package/src/tools/index.ts +76 -24
- package/src/tools/output-meta.ts +19 -7
- package/src/tools/read.ts +219 -64
- package/src/tools/resource-gc.ts +2 -2
- package/src/tools/search-tool-bm25.ts +11 -74
- package/src/tools/subagent.ts +43 -2
- package/src/utils/event-bus.ts +9 -4
- package/src/utils/file-mentions.ts +4 -4
- package/src/web/search/index.ts +3 -3
- package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
- package/src/discovery/plugin-dir-roots.ts +0 -28
- package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
|
@@ -11,6 +11,8 @@ import * as fs from "node:fs";
|
|
|
11
11
|
import * as fsPromises from "node:fs/promises";
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import type { NotificationControlServer as NativeNotificationControlServer } from "@gajae-code/natives";
|
|
14
|
+
import { logger } from "@gajae-code/utils";
|
|
15
|
+
import { readLinuxProcStartTime } from "../../gjc-runtime/linux-proc";
|
|
14
16
|
import { tmuxRuntimeSessionPath } from "../../gjc-runtime/session-layout";
|
|
15
17
|
import {
|
|
16
18
|
GJC_COORDINATOR_SESSION_ID_ENV,
|
|
@@ -116,6 +118,20 @@ function isLifecycleErrorReason(value: unknown): value is LifecycleErrorReason {
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/** Atomic + fsynced file-backed idempotency ledger store. */
|
|
121
|
+
function hasNonDirectoryLedgerParent(idempotencyFile: string): boolean {
|
|
122
|
+
let parent = path.dirname(idempotencyFile);
|
|
123
|
+
while (true) {
|
|
124
|
+
try {
|
|
125
|
+
return !fs.statSync(parent).isDirectory();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
128
|
+
const nextParent = path.dirname(parent);
|
|
129
|
+
if (nextParent === parent) return false;
|
|
130
|
+
parent = nextParent;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
function ledgerReadError(error: unknown): Error {
|
|
120
136
|
const errorCode = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
121
137
|
const code = typeof errorCode === "string" ? errorCode : "invalid";
|
|
@@ -181,27 +197,55 @@ function migrateLegacyLedger(doc: LedgerDoc): LedgerDoc {
|
|
|
181
197
|
return doc;
|
|
182
198
|
}
|
|
183
199
|
|
|
200
|
+
const LIFECYCLE_COMPATIBILITY_DIAGNOSTIC_LIMIT = 1;
|
|
201
|
+
const LIFECYCLE_CONTROL_DIAGNOSTIC_LIMIT = 10;
|
|
202
|
+
let lifecycleCompatibilityDiagnosticCount = 0;
|
|
203
|
+
let lifecycleControlDiagnosticCount = 0;
|
|
204
|
+
|
|
184
205
|
function isUnsupportedDirectorySyncError(error: unknown): boolean {
|
|
185
206
|
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
186
|
-
return
|
|
207
|
+
return (
|
|
208
|
+
code === "EINVAL" ||
|
|
209
|
+
code === "ENOTSUP" ||
|
|
210
|
+
code === "EOPNOTSUPP" ||
|
|
211
|
+
(process.platform === "win32" && code === "EPERM")
|
|
212
|
+
);
|
|
187
213
|
}
|
|
188
214
|
|
|
189
|
-
function
|
|
215
|
+
function recordBoundedLifecycleDiagnostic(message: string, compatibility = false): void {
|
|
216
|
+
if (compatibility) {
|
|
217
|
+
if (lifecycleCompatibilityDiagnosticCount >= LIFECYCLE_COMPATIBILITY_DIAGNOSTIC_LIMIT) return;
|
|
218
|
+
lifecycleCompatibilityDiagnosticCount++;
|
|
219
|
+
} else {
|
|
220
|
+
if (lifecycleControlDiagnosticCount >= LIFECYCLE_CONTROL_DIAGNOSTIC_LIMIT) return;
|
|
221
|
+
lifecycleControlDiagnosticCount++;
|
|
222
|
+
}
|
|
190
223
|
try {
|
|
191
|
-
|
|
224
|
+
logger.warn(message);
|
|
192
225
|
} catch {
|
|
193
|
-
//
|
|
226
|
+
// Diagnostics are best-effort and must not poison the lifecycle queue.
|
|
194
227
|
}
|
|
195
228
|
}
|
|
196
229
|
|
|
197
230
|
function fsyncLedgerParentDirectory(directory: string): void {
|
|
198
|
-
|
|
231
|
+
let dirFd: number;
|
|
232
|
+
try {
|
|
233
|
+
dirFd = fs.openSync(directory, "r");
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (process.platform === "win32" && isUnsupportedDirectorySyncError(error)) {
|
|
236
|
+
recordBoundedLifecycleDiagnostic("GJC lifecycle ledger directory sync unsupported", true);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
|
|
199
242
|
let syncFailure: unknown;
|
|
200
243
|
let closeFailure: unknown;
|
|
201
244
|
try {
|
|
202
245
|
fs.fsyncSync(dirFd);
|
|
203
246
|
} catch (error) {
|
|
204
|
-
if (isUnsupportedDirectorySyncError(error))
|
|
247
|
+
if (process.platform === "win32" && isUnsupportedDirectorySyncError(error))
|
|
248
|
+
recordBoundedLifecycleDiagnostic("GJC lifecycle ledger directory sync unsupported", true);
|
|
205
249
|
else syncFailure = error;
|
|
206
250
|
} finally {
|
|
207
251
|
try {
|
|
@@ -224,7 +268,16 @@ export function fileLedgerStore(idempotencyFile: string): LedgerStore {
|
|
|
224
268
|
contents = fs.readFileSync(idempotencyFile, "utf8");
|
|
225
269
|
} catch (error) {
|
|
226
270
|
const code = (error as NodeJS.ErrnoException).code;
|
|
227
|
-
if (code === "ENOENT")
|
|
271
|
+
if (code === "ENOENT") {
|
|
272
|
+
let hasNonDirectoryParent: boolean;
|
|
273
|
+
try {
|
|
274
|
+
hasNonDirectoryParent = hasNonDirectoryLedgerParent(idempotencyFile);
|
|
275
|
+
} catch (parentError) {
|
|
276
|
+
throw ledgerReadError(parentError);
|
|
277
|
+
}
|
|
278
|
+
if (hasNonDirectoryParent) throw ledgerReadError({ code: "ENOTDIR" });
|
|
279
|
+
return { version: 1, entries: {} };
|
|
280
|
+
}
|
|
228
281
|
throw ledgerReadError(error);
|
|
229
282
|
}
|
|
230
283
|
try {
|
|
@@ -373,15 +426,11 @@ function lifecycleOwnerIsolationProbe(tmux: string, env: NodeJS.ProcessEnv): Own
|
|
|
373
426
|
cgroup: { classification: "not_applicable" },
|
|
374
427
|
sessionNames,
|
|
375
428
|
};
|
|
376
|
-
const [cgroupText,
|
|
429
|
+
const [cgroupText, startTime] = await Promise.all([
|
|
377
430
|
fsPromises.readFile(`/proc/${pid}/cgroup`, "utf8").catch(() => null),
|
|
378
|
-
|
|
431
|
+
readLinuxProcStartTime(pid),
|
|
379
432
|
]);
|
|
380
433
|
const cgroup = classifyCgroup({ platform: process.platform, cgroupText });
|
|
381
|
-
const startTime = stat
|
|
382
|
-
?.slice(stat.lastIndexOf(")") + 2)
|
|
383
|
-
.trim()
|
|
384
|
-
.split(/\s+/)[19];
|
|
385
434
|
if (!startTime) return { state: "unverifiable", pid, cgroup, sessionNames };
|
|
386
435
|
return {
|
|
387
436
|
state:
|
|
@@ -931,6 +980,8 @@ export function daemonCloseSession(
|
|
|
931
980
|
export function daemonResumeSession(
|
|
932
981
|
env: NodeJS.ProcessEnv = process.env,
|
|
933
982
|
opts: {
|
|
983
|
+
agentDir?: string;
|
|
984
|
+
/** Explicit managed root for isolated tests. */
|
|
934
985
|
sessionsRoot?: string;
|
|
935
986
|
listSessions?: (env: NodeJS.ProcessEnv) => GjcTmuxSessionStatus[];
|
|
936
987
|
ownerIsolationProbe?: OwnerIsolationProbe;
|
|
@@ -971,21 +1022,28 @@ export function daemonResumeSession(
|
|
|
971
1022
|
// blindly spawning `gjc --resume <prefix>` against a non-authoritative id.
|
|
972
1023
|
let resumeId = target.sessionIdOrPrefix;
|
|
973
1024
|
let resumeCwd = target.path;
|
|
974
|
-
if (opts.sessionsRoot) {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1025
|
+
if (!target.path && !opts.agentDir && !opts.sessionsRoot) return { notFound: true };
|
|
1026
|
+
const recent = await listRecentSessions({
|
|
1027
|
+
cwd: target.path ?? opts.agentDir ?? opts.sessionsRoot ?? "",
|
|
1028
|
+
agentDir: opts.agentDir,
|
|
1029
|
+
sessionsRoot: opts.sessionsRoot,
|
|
1030
|
+
allWorkspaces: target.path === undefined,
|
|
1031
|
+
limit: 1000,
|
|
1032
|
+
});
|
|
1033
|
+
if (recent.kind === "error") throw new Error(`gjc_lifecycle_saved_sessions_unavailable: ${recent.message}`);
|
|
1034
|
+
const saved = recent.entries;
|
|
1035
|
+
const prefixed = saved.filter(
|
|
1036
|
+
s => s.sessionId === target.sessionIdOrPrefix || s.sessionId.startsWith(target.sessionIdOrPrefix),
|
|
1037
|
+
);
|
|
1038
|
+
const exact = prefixed.filter(s => s.sessionId === target.sessionIdOrPrefix);
|
|
1039
|
+
const resolved = exact.length > 0 ? exact : prefixed;
|
|
1040
|
+
if (resolved.length === 0) return { notFound: true };
|
|
1041
|
+
if (resolved.length > 1) {
|
|
1042
|
+
return { ambiguous: resolved.map(s => ({ sessionId: s.sessionId, path: s.path })) };
|
|
988
1043
|
}
|
|
1044
|
+
const selected = resolved[0]!;
|
|
1045
|
+
resumeId = selected.sessionId;
|
|
1046
|
+
resumeCwd = selected.path;
|
|
989
1047
|
const resolvedResumeCwd = resumeCwd ? path.resolve(resumeCwd) : undefined;
|
|
990
1048
|
const resumeCwdStat = resolvedResumeCwd ? fs.statSync(resolvedResumeCwd, { throwIfNoEntry: false }) : undefined;
|
|
991
1049
|
if (typeof resolvedResumeCwd !== "string" || !resumeCwdStat?.isDirectory()) {
|
|
@@ -1139,11 +1197,7 @@ function lifecycleFailureResponse(requestId: unknown): SessionLifecycleResponse
|
|
|
1139
1197
|
}
|
|
1140
1198
|
|
|
1141
1199
|
function recordLifecycleControlDiagnostic(): void {
|
|
1142
|
-
|
|
1143
|
-
process.stderr.write("gjc lifecycle control request failed\n");
|
|
1144
|
-
} catch {
|
|
1145
|
-
// Diagnostics must not suppress the fixed wire failure response or recovery.
|
|
1146
|
-
}
|
|
1200
|
+
recordBoundedLifecycleDiagnostic("GJC lifecycle control request failed");
|
|
1147
1201
|
}
|
|
1148
1202
|
|
|
1149
1203
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -1267,6 +1321,8 @@ export function buildOrchestratorDeps(input: {
|
|
|
1267
1321
|
auditRedactionKey: Uint8Array;
|
|
1268
1322
|
/** Root of saved session histories (`<agentDir>/sessions`), for resume resolution. */
|
|
1269
1323
|
sessionsRoot?: string;
|
|
1324
|
+
/** Agent directory used for readonly managed-session resume resolution. */
|
|
1325
|
+
agentDir?: string;
|
|
1270
1326
|
env?: NodeJS.ProcessEnv;
|
|
1271
1327
|
}): OrchestratorDeps {
|
|
1272
1328
|
if (input.auditRedactionKey.byteLength !== 32) throw new Error("invalid_audit_redaction_key");
|
|
@@ -1285,7 +1341,9 @@ export function buildOrchestratorDeps(input: {
|
|
|
1285
1341
|
},
|
|
1286
1342
|
spawnCreate: daemonSpawnCreate(env),
|
|
1287
1343
|
closeSession: daemonCloseSession(env),
|
|
1288
|
-
resumeSession: daemonResumeSession(env, {
|
|
1344
|
+
resumeSession: daemonResumeSession(env, {
|
|
1345
|
+
agentDir: input.agentDir ?? path.dirname(input.agentNotificationsDir),
|
|
1346
|
+
}),
|
|
1289
1347
|
};
|
|
1290
1348
|
}
|
|
1291
1349
|
|
|
@@ -26,7 +26,11 @@ declare module "@gajae-code/natives" {
|
|
|
26
26
|
onSdkFrame(callback: (err: null | Error, frame: SdkFrameEvent) => void): void;
|
|
27
27
|
/** Register the connection-close callback. Must be called before start. */
|
|
28
28
|
onConnectionClose(callback: (err: null | Error, connectionId: string) => void): void;
|
|
29
|
-
/**
|
|
29
|
+
/** Register negotiated v3 client capabilities. Must be called before start. */
|
|
30
|
+
onNegotiatedCapabilities(
|
|
31
|
+
callback: (err: null | Error, connectionId: string, capabilities: string[]) => void,
|
|
32
|
+
): void;
|
|
33
|
+
/** Directed delivery of a validated, bounded JSON v3 envelope to one connection. */
|
|
30
34
|
sendTo(connectionId: string, json: string): void;
|
|
31
35
|
/** Register a correlated workflow-gate action_needed frame. */
|
|
32
36
|
registerWorkflowGateAsk(workflowJson: string, repliable: boolean): void;
|
|
@@ -34,5 +38,26 @@ declare module "@gajae-code/natives" {
|
|
|
34
38
|
registerArbitratedAsk(actionJson: string, repliable: boolean): PresentationLease;
|
|
35
39
|
/** Retire an exact private lease unless a generic reply has already claimed it. */
|
|
36
40
|
retireIfUnclaimed(lease: PresentationLease): RetireIfUnclaimedResult;
|
|
41
|
+
/** Broadcast a TypeScript-constructed turn frame without JSON serde validation. */
|
|
42
|
+
pushTurnStreamUnchecked(
|
|
43
|
+
sessionId: string,
|
|
44
|
+
phase: "live" | "finalized",
|
|
45
|
+
text: string,
|
|
46
|
+
finalAnswer?: boolean,
|
|
47
|
+
messageRef?: string,
|
|
48
|
+
): void;
|
|
49
|
+
/** Broadcast raw file bytes; Rust encodes the unchanged base64 wire field. */
|
|
50
|
+
pushFileAttachmentUnchecked(
|
|
51
|
+
sessionId: string,
|
|
52
|
+
name: string,
|
|
53
|
+
mime: string | undefined,
|
|
54
|
+
data: Buffer,
|
|
55
|
+
caption: string | undefined,
|
|
56
|
+
): void;
|
|
57
|
+
/** Counters for known-good N-API frame crossings. */
|
|
58
|
+
knownGoodFrameStats(): {
|
|
59
|
+
knownGoodTurnStreamFrames: number;
|
|
60
|
+
turnStreamSerdeValidationParses: number;
|
|
61
|
+
};
|
|
37
62
|
}
|
|
38
63
|
}
|
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
* artifacts belonging to a DEAD owner (dead-PID / explicitly-stale). It never
|
|
11
11
|
* touches a live owner's lock/state and never kills a process.
|
|
12
12
|
*/
|
|
13
|
+
import * as crypto from "node:crypto";
|
|
14
|
+
import * as fsSync from "node:fs";
|
|
13
15
|
import * as fsPromises from "node:fs/promises";
|
|
14
16
|
import * as path from "node:path";
|
|
17
|
+
import * as native from "@gajae-code/natives";
|
|
15
18
|
import type { Settings } from "../../config/settings";
|
|
16
19
|
import {
|
|
17
20
|
getNotificationConfig,
|
|
@@ -46,10 +49,32 @@ export function sanitizeDiagnostic(text: string, token?: string): string {
|
|
|
46
49
|
return out.replace(TELEGRAM_TOKEN_PATTERN, "<redacted>");
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
/** Identity evidence required to remove precisely the endpoint that was inspected. */
|
|
53
|
+
export interface NotificationEndpointFileIdentity {
|
|
54
|
+
dev: bigint;
|
|
55
|
+
ino: bigint;
|
|
56
|
+
size: bigint;
|
|
57
|
+
mtimeNs: bigint;
|
|
58
|
+
sha256: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface NotificationEndpointFile {
|
|
62
|
+
bytes: Buffer;
|
|
63
|
+
identity: NotificationEndpointFileIdentity;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface NotificationExactUnlinkResult {
|
|
67
|
+
ok: boolean;
|
|
68
|
+
code?: string;
|
|
69
|
+
detachedPath?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
/** Minimal filesystem surface the service needs; injectable for tests. */
|
|
50
73
|
export interface NotificationServiceFs {
|
|
51
74
|
readdir(dir: string): Promise<string[]>;
|
|
52
75
|
readFile(file: string, encoding: "utf8"): Promise<string>;
|
|
76
|
+
readEndpointFile(file: string): Promise<NotificationEndpointFile>;
|
|
77
|
+
exactUnlink(file: string, identity: NotificationEndpointFileIdentity): Promise<NotificationExactUnlinkResult>;
|
|
53
78
|
unlink(file: string): Promise<void>;
|
|
54
79
|
/**
|
|
55
80
|
* Atomically create `file` with O_EXCL semantics: resolves `true` when this
|
|
@@ -63,6 +88,47 @@ export interface NotificationServiceFs {
|
|
|
63
88
|
const nodeServiceFs: NotificationServiceFs = {
|
|
64
89
|
readdir: dir => fsPromises.readdir(dir),
|
|
65
90
|
readFile: (file, encoding) => fsPromises.readFile(file, encoding),
|
|
91
|
+
readEndpointFile: async file => {
|
|
92
|
+
const before = await fsPromises.lstat(file, { bigint: true });
|
|
93
|
+
if (!before.isFile() || before.isSymbolicLink()) throw new Error("Endpoint is not a regular file");
|
|
94
|
+
const noFollow = fsSync.constants.O_NOFOLLOW;
|
|
95
|
+
const handle = await fsPromises.open(file, fsSync.constants.O_RDONLY | (noFollow ?? 0));
|
|
96
|
+
try {
|
|
97
|
+
const opened = await handle.stat({ bigint: true });
|
|
98
|
+
if (!opened.isFile() || !sameEndpointFileMetadata(before, opened))
|
|
99
|
+
throw new Error("Endpoint changed before it was opened");
|
|
100
|
+
const bytes = await handle.readFile();
|
|
101
|
+
const after = await handle.stat({ bigint: true });
|
|
102
|
+
const pathname = await fsPromises.lstat(file, { bigint: true });
|
|
103
|
+
if (
|
|
104
|
+
!after.isFile() ||
|
|
105
|
+
!pathname.isFile() ||
|
|
106
|
+
pathname.isSymbolicLink() ||
|
|
107
|
+
!sameEndpointFileMetadata(opened, after) ||
|
|
108
|
+
!sameEndpointFileMetadata(opened, pathname)
|
|
109
|
+
)
|
|
110
|
+
throw new Error("Endpoint changed while it was read");
|
|
111
|
+
return {
|
|
112
|
+
bytes,
|
|
113
|
+
identity: {
|
|
114
|
+
dev: opened.dev,
|
|
115
|
+
ino: opened.ino,
|
|
116
|
+
size: opened.size,
|
|
117
|
+
mtimeNs: opened.mtimeNs,
|
|
118
|
+
sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
} finally {
|
|
122
|
+
await handle.close();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
exactUnlink: async (file, identity) => {
|
|
126
|
+
const result = native.exactUnlink(file, {
|
|
127
|
+
...identity,
|
|
128
|
+
quarantineName: `.gjc-delete-notification-endpoint-${crypto.randomUUID()}.json`,
|
|
129
|
+
});
|
|
130
|
+
return { ok: result.ok, code: result.code, detachedPath: result.detachedPath };
|
|
131
|
+
},
|
|
66
132
|
unlink: file => fsPromises.unlink(file),
|
|
67
133
|
createExclusive: async file => {
|
|
68
134
|
try {
|
|
@@ -99,7 +165,7 @@ function defaultStateRoot(): string {
|
|
|
99
165
|
}
|
|
100
166
|
|
|
101
167
|
function endpointDir(stateRoot: string): string {
|
|
102
|
-
return path.join(stateRoot, "
|
|
168
|
+
return path.join(stateRoot, "sdk");
|
|
103
169
|
}
|
|
104
170
|
|
|
105
171
|
// --- status -------------------------------------------------------------
|
|
@@ -179,6 +245,11 @@ interface EndpointView {
|
|
|
179
245
|
stale: boolean;
|
|
180
246
|
}
|
|
181
247
|
|
|
248
|
+
type EndpointFileRead =
|
|
249
|
+
| { kind: "endpoint"; view: EndpointView; identity: NotificationEndpointFileIdentity }
|
|
250
|
+
| { kind: "non-endpoint" }
|
|
251
|
+
| { kind: "unreadable" };
|
|
252
|
+
|
|
182
253
|
type EndpointLiveness = "live" | "dead" | "unknown";
|
|
183
254
|
|
|
184
255
|
/**
|
|
@@ -194,25 +265,74 @@ function endpointLiveness(view: EndpointView, pidAlive: (pid: number) => boolean
|
|
|
194
265
|
return pidAlive(view.pid) ? "live" : "dead";
|
|
195
266
|
}
|
|
196
267
|
|
|
197
|
-
|
|
268
|
+
function sameEndpointFileMetadata(
|
|
269
|
+
left: { dev: bigint; ino: bigint; size: bigint; mtimeNs: bigint },
|
|
270
|
+
right: { dev: bigint; ino: bigint; size: bigint; mtimeNs: bigint },
|
|
271
|
+
): boolean {
|
|
272
|
+
return (
|
|
273
|
+
left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function isLifecycleArtifact(record: Record<string, unknown>): boolean {
|
|
278
|
+
const keys = Object.keys(record);
|
|
279
|
+
const isEffectMarker =
|
|
280
|
+
typeof record.pid === "number" &&
|
|
281
|
+
Number.isSafeInteger(record.pid) &&
|
|
282
|
+
record.pid > 0 &&
|
|
283
|
+
typeof record.effectMarker === "string" &&
|
|
284
|
+
record.effectMarker.length > 0 &&
|
|
285
|
+
typeof record.incarnation === "string" &&
|
|
286
|
+
record.incarnation.length > 0;
|
|
287
|
+
if (isEffectMarker && keys.length === 3) return true;
|
|
288
|
+
return (
|
|
289
|
+
isEffectMarker &&
|
|
290
|
+
typeof record.phase === "string" &&
|
|
291
|
+
typeof record.reason === "string" &&
|
|
292
|
+
typeof record.message === "string" &&
|
|
293
|
+
typeof record.rollback === "object" &&
|
|
294
|
+
record.rollback !== null
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
function isCanonicalLifecycleArtifactName(name: string): boolean {
|
|
298
|
+
return (
|
|
299
|
+
name.endsWith(".lifecycle.json") ||
|
|
300
|
+
name.endsWith(".lifecycle.ready.json") ||
|
|
301
|
+
/^.+\.lifecycle\.failure\.[A-Za-z0-9._-]{1,128}\.json$/.test(name)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function unreadableEndpointResult(file: string): EndpointFileRead {
|
|
306
|
+
return isCanonicalLifecycleArtifactName(path.basename(file)) ? { kind: "non-endpoint" } : { kind: "unreadable" };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function readEndpointView(fs: NotificationServiceFs, file: string): Promise<EndpointFileRead> {
|
|
310
|
+
let endpoint: NotificationEndpointFile;
|
|
198
311
|
let raw: string;
|
|
199
312
|
try {
|
|
200
|
-
|
|
313
|
+
endpoint = await fs.readEndpointFile(file);
|
|
314
|
+
raw = endpoint.bytes.toString("utf8");
|
|
201
315
|
} catch {
|
|
202
|
-
return
|
|
316
|
+
return unreadableEndpointResult(file);
|
|
203
317
|
}
|
|
204
318
|
let parsed: unknown;
|
|
205
319
|
try {
|
|
206
320
|
parsed = JSON.parse(raw);
|
|
207
321
|
} catch {
|
|
208
|
-
return
|
|
322
|
+
return unreadableEndpointResult(file);
|
|
209
323
|
}
|
|
210
|
-
if (!parsed || typeof parsed !== "object") return
|
|
324
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { kind: "non-endpoint" };
|
|
211
325
|
const rec = parsed as Record<string, unknown>;
|
|
326
|
+
if (isLifecycleArtifact(rec) || typeof rec.url !== "string" || typeof rec.token !== "string")
|
|
327
|
+
return { kind: "non-endpoint" };
|
|
212
328
|
return {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
329
|
+
kind: "endpoint",
|
|
330
|
+
view: {
|
|
331
|
+
sessionId: typeof rec.sessionId === "string" ? rec.sessionId : path.basename(file, ".json"),
|
|
332
|
+
pid: safePositiveInteger(rec.pid),
|
|
333
|
+
stale: rec.stale === true,
|
|
334
|
+
},
|
|
335
|
+
identity: endpoint.identity,
|
|
216
336
|
};
|
|
217
337
|
}
|
|
218
338
|
|
|
@@ -288,9 +408,13 @@ async function readDaemonStateFile(
|
|
|
288
408
|
}
|
|
289
409
|
}
|
|
290
410
|
|
|
411
|
+
function isSharedSdkArtifact(name: string): boolean {
|
|
412
|
+
return name === "broker.json";
|
|
413
|
+
}
|
|
414
|
+
|
|
291
415
|
async function listEndpointFiles(fs: NotificationServiceFs, dir: string): Promise<string[]> {
|
|
292
416
|
try {
|
|
293
|
-
return (await fs.readdir(dir)).filter(name => name.endsWith(".json"));
|
|
417
|
+
return (await fs.readdir(dir)).filter(name => name.endsWith(".json") && !isSharedSdkArtifact(name));
|
|
294
418
|
} catch {
|
|
295
419
|
return [];
|
|
296
420
|
}
|
|
@@ -468,11 +592,13 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
468
592
|
let unknownEndpoints = 0;
|
|
469
593
|
let unreadable = 0;
|
|
470
594
|
for (const name of files) {
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
595
|
+
const record = await readEndpointView(fs, path.join(dir, name));
|
|
596
|
+
if (record.kind === "non-endpoint") continue;
|
|
597
|
+
if (record.kind === "unreadable") {
|
|
473
598
|
unreadable += 1;
|
|
474
599
|
continue;
|
|
475
600
|
}
|
|
601
|
+
const view = record.view;
|
|
476
602
|
switch (endpointLiveness(view, pidAlive)) {
|
|
477
603
|
case "live":
|
|
478
604
|
live += 1;
|
|
@@ -485,12 +611,18 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
485
611
|
break;
|
|
486
612
|
}
|
|
487
613
|
}
|
|
488
|
-
const endpoints: EndpointHealth = {
|
|
614
|
+
const endpoints: EndpointHealth = {
|
|
615
|
+
total: live + dead + unknownEndpoints + unreadable,
|
|
616
|
+
live,
|
|
617
|
+
dead,
|
|
618
|
+
unknown: unknownEndpoints,
|
|
619
|
+
unreadable,
|
|
620
|
+
};
|
|
489
621
|
if (dead > 0 || unreadable > 0) {
|
|
490
622
|
checks.push({
|
|
491
623
|
name: "endpoints",
|
|
492
624
|
level: "warn",
|
|
493
|
-
detail: `${dead} dead / ${unreadable} unreadable of ${
|
|
625
|
+
detail: `${dead} dead / ${unreadable} unreadable of ${endpoints.total} endpoint file(s); run recovery`,
|
|
494
626
|
});
|
|
495
627
|
} else {
|
|
496
628
|
checks.push({
|
|
@@ -633,6 +765,7 @@ export interface NotificationRecoveryReport {
|
|
|
633
765
|
endpointsRemoved: RecoveredEndpoint[];
|
|
634
766
|
endpointsKept: number;
|
|
635
767
|
endpointsUnreadable: number;
|
|
768
|
+
endpointsDetached?: string[];
|
|
636
769
|
daemon: {
|
|
637
770
|
action: DaemonRecoveryAction;
|
|
638
771
|
detail: string;
|
|
@@ -699,15 +832,19 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
|
|
|
699
832
|
const dir = endpointDir(stateRoot);
|
|
700
833
|
const files = await listEndpointFiles(fs, dir);
|
|
701
834
|
const removed: RecoveredEndpoint[] = [];
|
|
835
|
+
const detached: string[] = [];
|
|
702
836
|
let kept = 0;
|
|
703
837
|
let unreadable = 0;
|
|
704
838
|
for (const name of files) {
|
|
705
|
-
const
|
|
706
|
-
|
|
839
|
+
const file = path.join(dir, name);
|
|
840
|
+
const record = await readEndpointView(fs, file);
|
|
841
|
+
if (record.kind === "non-endpoint") continue;
|
|
842
|
+
if (record.kind === "unreadable") {
|
|
707
843
|
// Leave unparseable files untouched: they may be mid-write by a live server.
|
|
708
844
|
unreadable += 1;
|
|
709
845
|
continue;
|
|
710
846
|
}
|
|
847
|
+
const view = record.view;
|
|
711
848
|
if (endpointLiveness(view, pidAlive) !== "dead") {
|
|
712
849
|
// Keep live AND unknown (PID-less) endpoints: only positive proof of
|
|
713
850
|
// death (a stale tombstone or a dead pid) authorizes removal.
|
|
@@ -715,8 +852,17 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
|
|
|
715
852
|
continue;
|
|
716
853
|
}
|
|
717
854
|
try {
|
|
718
|
-
await fs.
|
|
719
|
-
|
|
855
|
+
const result = await fs.exactUnlink(file, record.identity);
|
|
856
|
+
if (!result.ok) {
|
|
857
|
+
if (result.detachedPath) detached.push(result.detachedPath);
|
|
858
|
+
else kept += 1;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
removed.push({
|
|
862
|
+
sessionId: view.sessionId,
|
|
863
|
+
pid: view.pid,
|
|
864
|
+
reason: view.stale ? "stale-flag" : "dead-pid",
|
|
865
|
+
});
|
|
720
866
|
} catch {
|
|
721
867
|
kept += 1;
|
|
722
868
|
}
|
|
@@ -782,10 +928,11 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
|
|
|
782
928
|
}
|
|
783
929
|
|
|
784
930
|
return {
|
|
785
|
-
endpointsScanned:
|
|
931
|
+
endpointsScanned: removed.length + kept + unreadable + detached.length,
|
|
786
932
|
endpointsRemoved: removed,
|
|
787
933
|
endpointsKept: kept,
|
|
788
934
|
endpointsUnreadable: unreadable,
|
|
935
|
+
endpointsDetached: detached,
|
|
789
936
|
daemon,
|
|
790
937
|
};
|
|
791
938
|
}
|
|
@@ -794,11 +941,12 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
|
|
|
794
941
|
export function formatNotificationRecoveryReport(report: NotificationRecoveryReport): string {
|
|
795
942
|
const lines = ["Notification recovery"];
|
|
796
943
|
lines.push(
|
|
797
|
-
` endpoints: scanned ${report.endpointsScanned}, removed ${report.endpointsRemoved.length}, kept ${report.endpointsKept}, unreadable ${report.endpointsUnreadable}`,
|
|
944
|
+
` endpoints: scanned ${report.endpointsScanned}, removed ${report.endpointsRemoved.length}, kept ${report.endpointsKept}, unreadable ${report.endpointsUnreadable}, detached ${report.endpointsDetached?.length ?? 0}`,
|
|
798
945
|
);
|
|
799
946
|
for (const ep of report.endpointsRemoved) {
|
|
800
947
|
lines.push(` - removed ${ep.sessionId} (pid ${ep.pid ?? "?"}, ${ep.reason})`);
|
|
801
948
|
}
|
|
949
|
+
for (const detached of report.endpointsDetached ?? []) lines.push(` - retained detached endpoint ${detached}`);
|
|
802
950
|
lines.push(` daemon: ${report.daemon.action} — ${report.daemon.detail}`);
|
|
803
951
|
return lines.join("\n");
|
|
804
952
|
}
|