@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
package/src/lsp/client.ts
CHANGED
|
@@ -509,7 +509,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
509
509
|
|
|
510
510
|
// Wrap with lspmux if available and supported
|
|
511
511
|
const { command, args, env } = isLspmuxSupported(baseCommand)
|
|
512
|
-
? await getLspmuxCommand(baseCommand, baseArgs)
|
|
512
|
+
? await getLspmuxCommand(baseCommand, baseArgs, cwd)
|
|
513
513
|
: { command: baseCommand, args: baseArgs };
|
|
514
514
|
|
|
515
515
|
const owner = spawnOwnedProcess([command, ...args], {
|
package/src/lsp/config.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type ClaudePluginRoot, getPreloadedPluginRoots } from "../discovery/hel
|
|
|
8
8
|
import { BiomeClient } from "./clients/biome-client";
|
|
9
9
|
import { SwiftLintClient } from "./clients/swiftlint-client";
|
|
10
10
|
import DEFAULTS from "./defaults.json" with { type: "json" };
|
|
11
|
+
import { isProjectControlledPath } from "./path-trust";
|
|
11
12
|
import type { ServerConfig } from "./types";
|
|
12
13
|
|
|
13
14
|
export interface LspConfig {
|
|
@@ -69,6 +70,40 @@ function normalizeExtensionToFileTypes(value: unknown): string[] | null {
|
|
|
69
70
|
return extensions.length > 0 ? extensions : null;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function sanitizeServerConfig(config: unknown, allowProcessOverrides: boolean): RawServerConfig | null {
|
|
74
|
+
if (!isRecord(config)) return null;
|
|
75
|
+
|
|
76
|
+
const sanitized: RawServerConfig = {};
|
|
77
|
+
if (allowProcessOverrides && typeof config.command === "string" && config.command.length > 0) {
|
|
78
|
+
sanitized.command = config.command;
|
|
79
|
+
}
|
|
80
|
+
if (allowProcessOverrides && Array.isArray(config.args)) {
|
|
81
|
+
sanitized.args = config.args.filter((entry): entry is string => typeof entry === "string");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const fileTypes = normalizeStringArray(config.fileTypes);
|
|
85
|
+
if (fileTypes) sanitized.fileTypes = fileTypes;
|
|
86
|
+
if (isRecord(config.extensionToLanguage)) sanitized.extensionToLanguage = config.extensionToLanguage;
|
|
87
|
+
|
|
88
|
+
const rootMarkers = normalizeStringArray(config.rootMarkers);
|
|
89
|
+
if (rootMarkers) sanitized.rootMarkers = rootMarkers;
|
|
90
|
+
if (allowProcessOverrides && isRecord(config.initOptions)) sanitized.initOptions = config.initOptions;
|
|
91
|
+
if (allowProcessOverrides && isRecord(config.initializationOptions)) {
|
|
92
|
+
sanitized.initializationOptions = config.initializationOptions;
|
|
93
|
+
}
|
|
94
|
+
if (allowProcessOverrides && isRecord(config.settings)) sanitized.settings = config.settings;
|
|
95
|
+
if (typeof config.disabled === "boolean") sanitized.disabled = config.disabled;
|
|
96
|
+
if (typeof config.warmupTimeoutMs === "number" && Number.isFinite(config.warmupTimeoutMs)) {
|
|
97
|
+
sanitized.warmupTimeoutMs = config.warmupTimeoutMs;
|
|
98
|
+
}
|
|
99
|
+
if (isRecord(config.capabilities)) sanitized.capabilities = config.capabilities;
|
|
100
|
+
const supersedes = normalizeStringArray(config.supersedes);
|
|
101
|
+
if (supersedes) sanitized.supersedes = supersedes;
|
|
102
|
+
if (typeof config.isLinter === "boolean") sanitized.isLinter = config.isLinter;
|
|
103
|
+
|
|
104
|
+
return sanitized;
|
|
105
|
+
}
|
|
106
|
+
|
|
72
107
|
function normalizeServerConfig(name: string, config: RawServerConfig): ServerConfig | null {
|
|
73
108
|
const command = typeof config.command === "string" && config.command.length > 0 ? config.command : null;
|
|
74
109
|
const fileTypes =
|
|
@@ -91,13 +126,17 @@ function normalizeServerConfig(name: string, config: RawServerConfig): ServerCon
|
|
|
91
126
|
const supersedes = normalizeStringArray(config.supersedes);
|
|
92
127
|
|
|
93
128
|
return {
|
|
94
|
-
...config,
|
|
95
129
|
command,
|
|
96
|
-
args,
|
|
130
|
+
...(args ? { args } : {}),
|
|
97
131
|
fileTypes,
|
|
98
132
|
rootMarkers,
|
|
99
133
|
...(initOptions ? { initOptions } : {}),
|
|
134
|
+
...(isRecord(config.settings) ? { settings: config.settings } : {}),
|
|
135
|
+
...(typeof config.disabled === "boolean" ? { disabled: config.disabled } : {}),
|
|
136
|
+
...(typeof config.warmupTimeoutMs === "number" ? { warmupTimeoutMs: config.warmupTimeoutMs } : {}),
|
|
137
|
+
...(isRecord(config.capabilities) ? { capabilities: config.capabilities } : {}),
|
|
100
138
|
...(supersedes ? { supersedes } : {}),
|
|
139
|
+
...(typeof config.isLinter === "boolean" ? { isLinter: config.isLinter } : {}),
|
|
101
140
|
};
|
|
102
141
|
}
|
|
103
142
|
|
|
@@ -114,7 +153,8 @@ function readConfigFile(filePath: string): NormalizedConfig | null {
|
|
|
114
153
|
function coerceServerConfigs(servers: Record<string, RawServerConfig>): Record<string, ServerConfig> {
|
|
115
154
|
const result: Record<string, ServerConfig> = {};
|
|
116
155
|
for (const [name, config] of Object.entries(servers)) {
|
|
117
|
-
const
|
|
156
|
+
const sanitized = sanitizeServerConfig(config, true);
|
|
157
|
+
const normalized = sanitized ? normalizeServerConfig(name, sanitized) : null;
|
|
118
158
|
if (normalized) {
|
|
119
159
|
result[name] = normalized;
|
|
120
160
|
}
|
|
@@ -125,11 +165,30 @@ function coerceServerConfigs(servers: Record<string, RawServerConfig>): Record<s
|
|
|
125
165
|
function mergeServers(
|
|
126
166
|
base: Record<string, ServerConfig>,
|
|
127
167
|
overrides: Record<string, RawServerConfig>,
|
|
168
|
+
allowProcessOverrides: boolean,
|
|
128
169
|
): Record<string, ServerConfig> {
|
|
129
170
|
const merged: Record<string, ServerConfig> = { ...base };
|
|
130
171
|
for (const [name, config] of Object.entries(overrides)) {
|
|
172
|
+
if (
|
|
173
|
+
!allowProcessOverrides &&
|
|
174
|
+
isRecord(config) &&
|
|
175
|
+
("command" in config ||
|
|
176
|
+
"args" in config ||
|
|
177
|
+
"resolvedCommand" in config ||
|
|
178
|
+
"createClient" in config ||
|
|
179
|
+
"initOptions" in config ||
|
|
180
|
+
"initializationOptions" in config ||
|
|
181
|
+
"settings" in config)
|
|
182
|
+
) {
|
|
183
|
+
logger.warn("Ignoring project-controlled LSP process-affecting overrides.", { name });
|
|
184
|
+
}
|
|
185
|
+
const sanitized = sanitizeServerConfig(config, allowProcessOverrides);
|
|
186
|
+
if (!sanitized) {
|
|
187
|
+
logger.warn("Ignoring invalid LSP server config.", { name });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
131
190
|
if (merged[name]) {
|
|
132
|
-
const candidate = { ...merged[name], ...
|
|
191
|
+
const candidate = { ...merged[name], ...sanitized };
|
|
133
192
|
const normalized = normalizeServerConfig(name, candidate);
|
|
134
193
|
if (normalized) {
|
|
135
194
|
merged[name] = normalized;
|
|
@@ -137,7 +196,7 @@ function mergeServers(
|
|
|
137
196
|
logger.warn("Ignoring invalid LSP overrides (keeping previous config).", { name });
|
|
138
197
|
}
|
|
139
198
|
} else {
|
|
140
|
-
const normalized = normalizeServerConfig(name,
|
|
199
|
+
const normalized = normalizeServerConfig(name, sanitized);
|
|
141
200
|
if (normalized) {
|
|
142
201
|
merged[name] = normalized;
|
|
143
202
|
}
|
|
@@ -276,17 +335,49 @@ export function resolveCommand(command: string, cwd: string): string | null {
|
|
|
276
335
|
return $which(command);
|
|
277
336
|
}
|
|
278
337
|
|
|
338
|
+
/** Resolve an LSP executable without consulting project-controlled bin directories. */
|
|
339
|
+
function resolveTrustedLspCommand(command: string, cwd: string): string | null {
|
|
340
|
+
if (!path.isAbsolute(command) && (command.includes("/") || command.includes("\\"))) return null;
|
|
341
|
+
const discovered = path.isAbsolute(command) ? command : $which(command);
|
|
342
|
+
if (!discovered) return null;
|
|
343
|
+
if (isProjectControlledPath(discovered, cwd)) return null;
|
|
344
|
+
const canonical = canonicalExistingPath(discovered);
|
|
345
|
+
return canonical;
|
|
346
|
+
}
|
|
347
|
+
|
|
279
348
|
interface ConfigSource {
|
|
280
|
-
|
|
349
|
+
allowProcessOverrides: boolean;
|
|
350
|
+
read(): LoadedConfigSource | null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
interface LoadedConfigSource {
|
|
354
|
+
config: NormalizedConfig;
|
|
355
|
+
projectControlled: boolean;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function canonicalExistingPath(filePath: string): string | null {
|
|
359
|
+
try {
|
|
360
|
+
return fs.realpathSync(filePath);
|
|
361
|
+
} catch {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
281
364
|
}
|
|
282
365
|
|
|
283
|
-
function
|
|
366
|
+
function readCanonicalConfigFile(filePath: string, cwd: string): LoadedConfigSource | null {
|
|
367
|
+
const canonicalPath = canonicalExistingPath(filePath);
|
|
368
|
+
if (!canonicalPath) return null;
|
|
369
|
+
const config = readConfigFile(canonicalPath);
|
|
370
|
+
return config ? { config, projectControlled: isProjectControlledPath(canonicalPath, cwd) } : null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function fileConfigSource(filePath: string, cwd: string, allowProcessOverrides: boolean): ConfigSource {
|
|
284
374
|
return {
|
|
285
|
-
|
|
375
|
+
allowProcessOverrides,
|
|
376
|
+
read: () => readCanonicalConfigFile(filePath, cwd),
|
|
286
377
|
};
|
|
287
378
|
}
|
|
288
379
|
|
|
289
|
-
function readMarketplaceLspConfig(root: ClaudePluginRoot):
|
|
380
|
+
function readMarketplaceLspConfig(root: ClaudePluginRoot, cwd: string): LoadedConfigSource | null {
|
|
290
381
|
const catalogPaths = [
|
|
291
382
|
path.resolve(root.path, "..", "..", "marketplace.json"),
|
|
292
383
|
path.resolve(root.path, "..", "..", ".claude-plugin", "marketplace.json"),
|
|
@@ -294,8 +385,11 @@ function readMarketplaceLspConfig(root: ClaudePluginRoot): NormalizedConfig | nu
|
|
|
294
385
|
|
|
295
386
|
for (const catalogPath of catalogPaths) {
|
|
296
387
|
try {
|
|
297
|
-
const
|
|
388
|
+
const canonicalCatalogPath = canonicalExistingPath(catalogPath);
|
|
389
|
+
if (!canonicalCatalogPath) continue;
|
|
390
|
+
const catalog = JSON.parse(fs.readFileSync(canonicalCatalogPath, "utf-8")) as unknown;
|
|
298
391
|
if (!isRecord(catalog) || !Array.isArray(catalog.plugins)) continue;
|
|
392
|
+
const catalogIsProjectControlled = isProjectControlledPath(canonicalCatalogPath, cwd);
|
|
299
393
|
|
|
300
394
|
for (const plugin of catalog.plugins) {
|
|
301
395
|
if (!isRecord(plugin) || plugin.name !== root.plugin) continue;
|
|
@@ -304,10 +398,27 @@ function readMarketplaceLspConfig(root: ClaudePluginRoot): NormalizedConfig | nu
|
|
|
304
398
|
if (typeof lspServers === "string") {
|
|
305
399
|
const configPath = path.resolve(root.path, lspServers);
|
|
306
400
|
if (!pathIsWithin(root.path, configPath)) return null;
|
|
307
|
-
|
|
401
|
+
const canonicalRootPath = canonicalExistingPath(root.path);
|
|
402
|
+
const canonicalConfigPath = canonicalExistingPath(configPath);
|
|
403
|
+
if (
|
|
404
|
+
!canonicalRootPath ||
|
|
405
|
+
!canonicalConfigPath ||
|
|
406
|
+
!pathIsWithin(canonicalRootPath, canonicalConfigPath)
|
|
407
|
+
) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
const config = readConfigFile(canonicalConfigPath);
|
|
411
|
+
return config
|
|
412
|
+
? {
|
|
413
|
+
config,
|
|
414
|
+
projectControlled:
|
|
415
|
+
catalogIsProjectControlled || isProjectControlledPath(canonicalConfigPath, cwd),
|
|
416
|
+
}
|
|
417
|
+
: null;
|
|
308
418
|
}
|
|
309
419
|
if (isRecord(lspServers)) {
|
|
310
|
-
|
|
420
|
+
const config = normalizeConfig({ servers: lspServers });
|
|
421
|
+
return config ? { config, projectControlled: catalogIsProjectControlled } : null;
|
|
311
422
|
}
|
|
312
423
|
return null;
|
|
313
424
|
}
|
|
@@ -317,12 +428,17 @@ function readMarketplaceLspConfig(root: ClaudePluginRoot): NormalizedConfig | nu
|
|
|
317
428
|
return null;
|
|
318
429
|
}
|
|
319
430
|
|
|
320
|
-
function marketplaceConfigSource(root: ClaudePluginRoot): ConfigSource {
|
|
431
|
+
function marketplaceConfigSource(root: ClaudePluginRoot, cwd: string, allowProcessOverrides: boolean): ConfigSource {
|
|
321
432
|
return {
|
|
322
|
-
|
|
433
|
+
allowProcessOverrides,
|
|
434
|
+
read: () => readMarketplaceLspConfig(root, cwd),
|
|
323
435
|
};
|
|
324
436
|
}
|
|
325
437
|
|
|
438
|
+
function pluginCanOverrideProcess(root: ClaudePluginRoot, cwd: string): boolean {
|
|
439
|
+
return root.scope !== "project" && !isProjectControlledPath(root.path, cwd);
|
|
440
|
+
}
|
|
441
|
+
|
|
326
442
|
/**
|
|
327
443
|
* Configuration sources in priority order.
|
|
328
444
|
* Supports both visible and hidden variants at each config location.
|
|
@@ -333,14 +449,14 @@ function getConfigSources(cwd: string): ConfigSource[] {
|
|
|
333
449
|
|
|
334
450
|
// Project root files (highest priority)
|
|
335
451
|
for (const filename of filenames) {
|
|
336
|
-
sources.push(fileConfigSource(path.join(cwd, filename)));
|
|
452
|
+
sources.push(fileConfigSource(path.join(cwd, filename), cwd, false));
|
|
337
453
|
}
|
|
338
454
|
|
|
339
455
|
// Project config directories (.gjc/, .gemini/)
|
|
340
456
|
const projectDirs = getConfigDirPaths("", { user: false, project: true, cwd });
|
|
341
457
|
for (const dir of projectDirs) {
|
|
342
458
|
for (const filename of filenames) {
|
|
343
|
-
sources.push(fileConfigSource(path.join(dir, filename)));
|
|
459
|
+
sources.push(fileConfigSource(path.join(dir, filename), cwd, false));
|
|
344
460
|
}
|
|
345
461
|
}
|
|
346
462
|
|
|
@@ -348,22 +464,23 @@ function getConfigSources(cwd: string): ConfigSource[] {
|
|
|
348
464
|
const userDirs = getConfigDirPaths("", { user: true, project: false });
|
|
349
465
|
for (const dir of userDirs) {
|
|
350
466
|
for (const filename of filenames) {
|
|
351
|
-
sources.push(fileConfigSource(path.join(dir, filename)));
|
|
467
|
+
sources.push(fileConfigSource(path.join(dir, filename), cwd, true));
|
|
352
468
|
}
|
|
353
469
|
}
|
|
354
470
|
|
|
355
|
-
// Plugin LSP configs
|
|
471
|
+
// Plugin LSP configs
|
|
356
472
|
const pluginRoots = getPreloadedPluginRoots();
|
|
357
473
|
for (const root of pluginRoots) {
|
|
474
|
+
const allowProcessOverrides = pluginCanOverrideProcess(root, cwd);
|
|
358
475
|
for (const filename of filenames) {
|
|
359
|
-
sources.push(fileConfigSource(path.join(root.path, filename)));
|
|
476
|
+
sources.push(fileConfigSource(path.join(root.path, filename), cwd, allowProcessOverrides));
|
|
360
477
|
}
|
|
361
|
-
sources.push(marketplaceConfigSource(root));
|
|
478
|
+
sources.push(marketplaceConfigSource(root, cwd, allowProcessOverrides));
|
|
362
479
|
}
|
|
363
480
|
|
|
364
481
|
// User home root files (lowest priority fallback)
|
|
365
482
|
for (const filename of filenames) {
|
|
366
|
-
sources.push(fileConfigSource(path.join(os.homedir(), filename)));
|
|
483
|
+
sources.push(fileConfigSource(path.join(os.homedir(), filename), cwd, true));
|
|
367
484
|
}
|
|
368
485
|
|
|
369
486
|
return sources;
|
|
@@ -408,12 +525,14 @@ export function loadConfig(cwd: string): LspConfig {
|
|
|
408
525
|
|
|
409
526
|
let idleTimeoutMs: number | undefined;
|
|
410
527
|
for (const source of configSources) {
|
|
411
|
-
const
|
|
412
|
-
if (!
|
|
528
|
+
const loaded = source.read();
|
|
529
|
+
if (!loaded) continue;
|
|
530
|
+
const parsed = loaded.config;
|
|
531
|
+
const allowProcessOverrides = source.allowProcessOverrides && !loaded.projectControlled;
|
|
413
532
|
const hasServerOverrides = Object.keys(parsed.servers).length > 0;
|
|
414
533
|
if (hasServerOverrides) {
|
|
415
534
|
hasOverrides = true;
|
|
416
|
-
mergedServers = mergeServers(mergedServers, parsed.servers);
|
|
535
|
+
mergedServers = mergeServers(mergedServers, parsed.servers, allowProcessOverrides);
|
|
417
536
|
}
|
|
418
537
|
if (parsed.idleTimeoutMs !== undefined) {
|
|
419
538
|
idleTimeoutMs = parsed.idleTimeoutMs;
|
|
@@ -430,7 +549,7 @@ export function loadConfig(cwd: string): LspConfig {
|
|
|
430
549
|
if (!hasRootMarkers(cwd, config.rootMarkers)) continue;
|
|
431
550
|
|
|
432
551
|
// Check if the language server binary is available (local or $PATH)
|
|
433
|
-
const resolved =
|
|
552
|
+
const resolved = resolveTrustedLspCommand(config.command, cwd);
|
|
434
553
|
if (!resolved) continue;
|
|
435
554
|
|
|
436
555
|
detected[name] = { ...config, resolvedCommand: resolved };
|
|
@@ -445,7 +564,7 @@ export function loadConfig(cwd: string): LspConfig {
|
|
|
445
564
|
for (const [name, config] of Object.entries(mergedWithRuntime)) {
|
|
446
565
|
if (config.disabled) continue;
|
|
447
566
|
if (!hasRootMarkers(cwd, config.rootMarkers)) continue;
|
|
448
|
-
const resolved =
|
|
567
|
+
const resolved = resolveTrustedLspCommand(config.command, cwd);
|
|
449
568
|
if (!resolved) continue;
|
|
450
569
|
available[name] = { ...config, resolvedCommand: resolved };
|
|
451
570
|
}
|
package/src/lsp/index.ts
CHANGED
|
@@ -1213,7 +1213,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1213
1213
|
// Status action doesn't need a file
|
|
1214
1214
|
if (action === "status") {
|
|
1215
1215
|
const servers = Object.keys(config.servers);
|
|
1216
|
-
const lspmuxState = await detectLspmux();
|
|
1216
|
+
const lspmuxState = await detectLspmux(this.session.cwd);
|
|
1217
1217
|
const lspmuxStatus = lspmuxState.available
|
|
1218
1218
|
? lspmuxState.running
|
|
1219
1219
|
? "lspmux: active (multiplexing enabled)"
|
package/src/lsp/lspmux.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as os from "node:os";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { $flag, $which, logger } from "@gajae-code/utils";
|
|
4
5
|
import { TOML } from "bun";
|
|
5
6
|
import { spawnOwnedProcess } from "../runtime/process-lifecycle";
|
|
7
|
+
import { isProjectControlledPath } from "./path-trust";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* lspmux integration for LSP server multiplexing.
|
|
@@ -81,6 +83,7 @@ function getConfigPath(): string {
|
|
|
81
83
|
|
|
82
84
|
let cachedState: LspmuxState | null = null;
|
|
83
85
|
let cacheTimestamp = 0;
|
|
86
|
+
let cacheCwd: string | null = null;
|
|
84
87
|
|
|
85
88
|
/**
|
|
86
89
|
* Parse lspmux config.toml file.
|
|
@@ -110,7 +113,7 @@ async function checkServerRunning(binaryPath: string): Promise<boolean> {
|
|
|
110
113
|
drainStream(proc.stdout);
|
|
111
114
|
drainStream(proc.stderr);
|
|
112
115
|
|
|
113
|
-
let timer:
|
|
116
|
+
let timer: NodeJS.Timeout | undefined;
|
|
114
117
|
const timeout = new Promise<null>(resolve => {
|
|
115
118
|
timer = setTimeout(() => resolve(null), LIVENESS_TIMEOUT_MS);
|
|
116
119
|
});
|
|
@@ -147,28 +150,42 @@ function drainStream(stream: ReadableStream<Uint8Array> | null | undefined): voi
|
|
|
147
150
|
})();
|
|
148
151
|
}
|
|
149
152
|
|
|
153
|
+
function resolveTrustedLspmuxBinary(cwd: string): string | null {
|
|
154
|
+
const discoveredPath = $which("lspmux");
|
|
155
|
+
if (!discoveredPath) return null;
|
|
156
|
+
if (isProjectControlledPath(discoveredPath, cwd)) return null;
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
return fs.realpathSync(discoveredPath);
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
150
165
|
/**
|
|
151
166
|
* Detect lspmux availability and state.
|
|
152
167
|
* Results are cached for STATE_CACHE_TTL_MS.
|
|
153
168
|
*
|
|
154
|
-
* Set PI_DISABLE_LSPMUX=1 to disable.
|
|
169
|
+
* Set GJC_DISABLE_LSPMUX=1 or PI_DISABLE_LSPMUX=1 to disable.
|
|
155
170
|
*/
|
|
156
|
-
export async function detectLspmux(): Promise<LspmuxState> {
|
|
171
|
+
export async function detectLspmux(cwd = process.cwd()): Promise<LspmuxState> {
|
|
157
172
|
const now = Date.now();
|
|
158
|
-
if (
|
|
173
|
+
if ($flag("GJC_DISABLE_LSPMUX") || $flag("PI_DISABLE_LSPMUX")) {
|
|
174
|
+
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
175
|
+
cacheTimestamp = now;
|
|
176
|
+
cacheCwd = cwd;
|
|
159
177
|
return cachedState;
|
|
160
178
|
}
|
|
161
179
|
|
|
162
|
-
if (
|
|
163
|
-
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
164
|
-
cacheTimestamp = now;
|
|
180
|
+
if (cachedState && cacheCwd === cwd && now - cacheTimestamp < STATE_CACHE_TTL_MS) {
|
|
165
181
|
return cachedState;
|
|
166
182
|
}
|
|
167
183
|
|
|
168
|
-
const binaryPath =
|
|
184
|
+
const binaryPath = resolveTrustedLspmuxBinary(cwd);
|
|
169
185
|
if (!binaryPath) {
|
|
170
186
|
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
171
187
|
cacheTimestamp = now;
|
|
188
|
+
cacheCwd = cwd;
|
|
172
189
|
return cachedState;
|
|
173
190
|
}
|
|
174
191
|
|
|
@@ -176,6 +193,7 @@ export async function detectLspmux(): Promise<LspmuxState> {
|
|
|
176
193
|
|
|
177
194
|
cachedState = { available: true, running, binaryPath, config };
|
|
178
195
|
cacheTimestamp = now;
|
|
196
|
+
cacheCwd = cwd;
|
|
179
197
|
|
|
180
198
|
if (running) {
|
|
181
199
|
logger.debug("lspmux detected and running", { binaryPath });
|
|
@@ -184,6 +202,13 @@ export async function detectLspmux(): Promise<LspmuxState> {
|
|
|
184
202
|
return cachedState;
|
|
185
203
|
}
|
|
186
204
|
|
|
205
|
+
/** Reset cached lspmux detection state for focused tests. */
|
|
206
|
+
export function resetLspmuxStateForTesting(): void {
|
|
207
|
+
cachedState = null;
|
|
208
|
+
cacheTimestamp = 0;
|
|
209
|
+
cacheCwd = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
187
212
|
// =============================================================================
|
|
188
213
|
// Command Wrapping
|
|
189
214
|
// =============================================================================
|
|
@@ -251,7 +276,11 @@ export function wrapWithLspmux(
|
|
|
251
276
|
* @param args - Original command arguments
|
|
252
277
|
* @returns Command and args to use (possibly wrapped with lspmux)
|
|
253
278
|
*/
|
|
254
|
-
export async function getLspmuxCommand(
|
|
255
|
-
|
|
279
|
+
export async function getLspmuxCommand(
|
|
280
|
+
command: string,
|
|
281
|
+
args?: string[],
|
|
282
|
+
cwd = process.cwd(),
|
|
283
|
+
): Promise<LspmuxWrappedCommand> {
|
|
284
|
+
const state = await detectLspmux(cwd);
|
|
256
285
|
return wrapWithLspmux(command, args, state);
|
|
257
286
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { CONFIG_DIR_NAME, pathIsWithin, relativePathEscapesRoot } from "@gajae-code/utils";
|
|
5
|
+
|
|
6
|
+
function normalizePathForComparison(candidate: string): string {
|
|
7
|
+
const resolved = path.resolve(candidate);
|
|
8
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function pathIsLexicallyWithin(root: string, candidate: string): boolean {
|
|
12
|
+
const relative = path.relative(normalizePathForComparison(root), normalizePathForComparison(candidate));
|
|
13
|
+
return !relativePathEscapesRoot(relative);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function canonicalPath(candidate: string): string {
|
|
17
|
+
try {
|
|
18
|
+
return fs.realpathSync(candidate);
|
|
19
|
+
} catch {
|
|
20
|
+
return path.resolve(candidate);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function canonicalParentPath(candidate: string): string {
|
|
25
|
+
const resolved = path.resolve(candidate);
|
|
26
|
+
return path.join(canonicalPath(path.dirname(resolved)), path.basename(resolved));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isDirectory(candidate: string): boolean {
|
|
30
|
+
try {
|
|
31
|
+
return fs.statSync(candidate).isDirectory();
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pathMatchesStop(candidate: string, stopPaths: ReadonlySet<string>): boolean {
|
|
38
|
+
return (
|
|
39
|
+
stopPaths.has(normalizePathForComparison(candidate)) ||
|
|
40
|
+
stopPaths.has(normalizePathForComparison(canonicalPath(candidate)))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findProjectTrustRoot(start: string, stopPaths: ReadonlySet<string>): string | undefined {
|
|
45
|
+
const fallback = path.resolve(start);
|
|
46
|
+
let nearestConfigRoot: string | undefined;
|
|
47
|
+
let current = fallback;
|
|
48
|
+
for (;;) {
|
|
49
|
+
if (pathMatchesStop(current, stopPaths)) {
|
|
50
|
+
return current === fallback ? undefined : (nearestConfigRoot ?? fallback);
|
|
51
|
+
}
|
|
52
|
+
if (fs.existsSync(path.join(current, ".git"))) return current;
|
|
53
|
+
if (nearestConfigRoot === undefined && isDirectory(path.join(current, CONFIG_DIR_NAME))) {
|
|
54
|
+
nearestConfigRoot = current;
|
|
55
|
+
}
|
|
56
|
+
const parent = path.dirname(current);
|
|
57
|
+
if (parent === current) return nearestConfigRoot ?? fallback;
|
|
58
|
+
current = parent;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isProjectControlledPath(candidate: string, cwd: string): boolean {
|
|
63
|
+
const home = os.homedir();
|
|
64
|
+
const stopPaths = new Set([path.resolve(home), canonicalPath(home)].map(normalizePathForComparison));
|
|
65
|
+
const lexicalTrustRoot = findProjectTrustRoot(cwd, stopPaths);
|
|
66
|
+
if (lexicalTrustRoot !== undefined && pathIsLexicallyWithin(lexicalTrustRoot, candidate)) return true;
|
|
67
|
+
|
|
68
|
+
const canonicalTrustRoots = new Set<string>();
|
|
69
|
+
if (lexicalTrustRoot !== undefined) canonicalTrustRoots.add(canonicalPath(lexicalTrustRoot));
|
|
70
|
+
const canonicalTrustRoot = findProjectTrustRoot(canonicalPath(cwd), stopPaths);
|
|
71
|
+
if (canonicalTrustRoot !== undefined) canonicalTrustRoots.add(canonicalTrustRoot);
|
|
72
|
+
for (const trustRoot of canonicalTrustRoots) {
|
|
73
|
+
if (
|
|
74
|
+
pathIsLexicallyWithin(trustRoot, canonicalParentPath(candidate)) ||
|
|
75
|
+
pathIsWithin(trustRoot, canonicalPath(candidate))
|
|
76
|
+
) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|