@gajae-code/coding-agent 0.11.0 → 0.11.2
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 +63 -1
- package/dist/types/async/job-manager.d.ts +46 -7
- package/dist/types/cli/config-cli.d.ts +11 -1
- 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 +56 -1
- 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-receipt-freshness.d.ts +11 -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 +3 -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/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -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 +21 -19
- package/dist/types/modes/jobs-observer.d.ts +2 -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 +21 -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 +22 -15
- 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/ensure.d.ts +9 -0
- 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/discord-daemon.d.ts +5 -0
- package/dist/types/sdk/bus/html-format.d.ts +3 -2
- package/dist/types/sdk/bus/index.d.ts +27 -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 +69 -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/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 +3 -0
- package/dist/types/tools/search-tool-bm25.d.ts +3 -4
- package/dist/types/tools/subagent-render.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +6 -1
- package/dist/types/utils/event-bus.d.ts +1 -1
- package/dist/types/web/search/index.d.ts +3 -1
- package/package.json +21 -9
- package/scripts/build-sdk-package-smoke.ts +39 -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 +455 -69
- package/src/cli/config-cli.ts +75 -2
- 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 +159 -2
- 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 +320 -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 +83 -12
- package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +156 -1319
- 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 +28 -8
- 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/command-controller.ts +9 -15
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +72 -25
- 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 +302 -1151
- package/src/modes/jobs-observer.ts +3 -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 +78 -19
- 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 +47 -20
- 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 +21 -2
- package/src/sdk/broker/ensure.ts +85 -25
- 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 +14 -4
- 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/discord-daemon.ts +25 -8
- package/src/sdk/bus/html-format.ts +20 -3
- package/src/sdk/bus/index.ts +834 -113
- package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
- package/src/sdk/bus/native-runtime-compatibility.ts +1 -1
- 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 +1491 -247
- 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 +31 -1
- 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 +458 -479
- package/src/session/agent-session.ts +1901 -637
- 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 +62 -37
- 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 +13 -5
- package/src/tools/read.ts +37 -1
- package/src/tools/resource-gc.ts +2 -2
- package/src/tools/search-tool-bm25.ts +11 -74
- package/src/tools/subagent-render.ts +19 -8
- package/src/tools/subagent.ts +84 -32
- package/src/utils/event-bus.ts +9 -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
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -57,6 +57,7 @@ import type { AgentSession } from "./session/agent-session";
|
|
|
57
57
|
import {
|
|
58
58
|
type ResumeSessionIdentity,
|
|
59
59
|
resolveResumableSession,
|
|
60
|
+
type SessionDirectoryMigrationPolicy,
|
|
60
61
|
type SessionInfo,
|
|
61
62
|
SessionManager,
|
|
62
63
|
type StrictSessionOpenResult,
|
|
@@ -510,6 +511,7 @@ type SelectResumeSession = (sessions: SessionInfo[]) => Promise<SessionSelection
|
|
|
510
511
|
type OpenExistingSessionStrict = (
|
|
511
512
|
identity: ResumeSessionIdentity,
|
|
512
513
|
sessionDir?: string,
|
|
514
|
+
migrationPolicy?: SessionDirectoryMigrationPolicy,
|
|
513
515
|
) => Promise<StrictSessionOpenResult>;
|
|
514
516
|
|
|
515
517
|
export const BARE_RESUME_CONFLICT_ERROR =
|
|
@@ -694,6 +696,7 @@ export async function createSessionManager(
|
|
|
694
696
|
cwd: string,
|
|
695
697
|
activeSettings: Settings = settings,
|
|
696
698
|
): Promise<SessionManager | undefined> {
|
|
699
|
+
const migrationPolicy = activeSettings.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
|
|
697
700
|
if (parsed.resume === true) {
|
|
698
701
|
return undefined;
|
|
699
702
|
}
|
|
@@ -703,13 +706,13 @@ export async function createSessionManager(
|
|
|
703
706
|
}
|
|
704
707
|
const forkSource = parsed.fork;
|
|
705
708
|
if (forkSource.includes("/") || forkSource.includes("\\") || forkSource.endsWith(".jsonl")) {
|
|
706
|
-
return await SessionManager.forkFrom(forkSource, cwd, parsed.sessionDir);
|
|
709
|
+
return await SessionManager.forkFrom(forkSource, cwd, parsed.sessionDir, undefined, migrationPolicy);
|
|
707
710
|
}
|
|
708
711
|
const match = await resolveResumableSession(forkSource, cwd, parsed.sessionDir);
|
|
709
712
|
if (!match) {
|
|
710
713
|
throw new Error(`Session "${forkSource}" not found.`);
|
|
711
714
|
}
|
|
712
|
-
return await SessionManager.forkFrom(match.session.path, cwd, parsed.sessionDir);
|
|
715
|
+
return await SessionManager.forkFrom(match.session.path, cwd, parsed.sessionDir, undefined, migrationPolicy);
|
|
713
716
|
}
|
|
714
717
|
|
|
715
718
|
if (parsed.noSession) {
|
|
@@ -718,7 +721,7 @@ export async function createSessionManager(
|
|
|
718
721
|
if (typeof parsed.resume === "string") {
|
|
719
722
|
const sessionArg = parsed.resume;
|
|
720
723
|
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
|
|
721
|
-
return await SessionManager.open(sessionArg, parsed.sessionDir);
|
|
724
|
+
return await SessionManager.open(sessionArg, parsed.sessionDir, undefined, migrationPolicy);
|
|
722
725
|
}
|
|
723
726
|
const match = await resolveResumableSession(sessionArg, cwd, parsed.sessionDir);
|
|
724
727
|
if (!match) {
|
|
@@ -732,13 +735,19 @@ export async function createSessionManager(
|
|
|
732
735
|
if (!shouldFork) {
|
|
733
736
|
throw new Error(`Session "${sessionArg}" is in another project (${match.session.cwd}).`);
|
|
734
737
|
}
|
|
735
|
-
return await SessionManager.forkFrom(
|
|
738
|
+
return await SessionManager.forkFrom(
|
|
739
|
+
match.session.path,
|
|
740
|
+
cwd,
|
|
741
|
+
parsed.sessionDir,
|
|
742
|
+
undefined,
|
|
743
|
+
migrationPolicy,
|
|
744
|
+
);
|
|
736
745
|
}
|
|
737
746
|
}
|
|
738
|
-
return await SessionManager.open(match.session.path, parsed.sessionDir);
|
|
747
|
+
return await SessionManager.open(match.session.path, parsed.sessionDir, undefined, migrationPolicy);
|
|
739
748
|
}
|
|
740
749
|
if (parsed.continue) {
|
|
741
|
-
return await SessionManager.continueRecent(cwd, parsed.sessionDir);
|
|
750
|
+
return await SessionManager.continueRecent(cwd, parsed.sessionDir, undefined, migrationPolicy);
|
|
742
751
|
}
|
|
743
752
|
// --resume without value is handled separately (needs picker UI)
|
|
744
753
|
// If --session-dir provided without --continue/--resume, create new session there
|
|
@@ -761,7 +770,7 @@ export async function createSessionManager(
|
|
|
761
770
|
// buildSessionOptions restores the session's model/thinking instead of
|
|
762
771
|
// overriding them with CLI defaults.
|
|
763
772
|
if (activeSettings.get("autoResume")) {
|
|
764
|
-
const manager = await SessionManager.continueRecent(cwd, parsed.sessionDir);
|
|
773
|
+
const manager = await SessionManager.continueRecent(cwd, parsed.sessionDir, undefined, migrationPolicy);
|
|
765
774
|
if (manager.getEntries().length > 0) {
|
|
766
775
|
parsed.continue = true;
|
|
767
776
|
}
|
|
@@ -1014,6 +1023,7 @@ export interface RunRootCommandDependencies {
|
|
|
1014
1023
|
selectResumeSession?: SelectResumeSession;
|
|
1015
1024
|
openExistingSessionStrict?: OpenExistingSessionStrict;
|
|
1016
1025
|
initializeSettings?: typeof Settings.init;
|
|
1026
|
+
loadSettingsForScope?: typeof Settings.loadForScope;
|
|
1017
1027
|
}
|
|
1018
1028
|
|
|
1019
1029
|
export async function runRootCommand(
|
|
@@ -1054,15 +1064,25 @@ export async function runRootCommand(
|
|
|
1054
1064
|
process.stdout.write(`${chalk.dim("No sessions found")}\n`);
|
|
1055
1065
|
return;
|
|
1056
1066
|
}
|
|
1057
|
-
const selection =
|
|
1067
|
+
const selection = deps.selectResumeSession
|
|
1068
|
+
? await deps.selectResumeSession(sessions)
|
|
1069
|
+
: await selectSession(sessions, parsedArgs.sessionDir);
|
|
1058
1070
|
if (selection.kind === "cancelled") {
|
|
1059
1071
|
return;
|
|
1060
1072
|
}
|
|
1073
|
+
const resumeMigrationPolicy =
|
|
1074
|
+
(await (deps.loadSettingsForScope ?? Settings.loadForScope)({ cwd: resumeCwd })).get(
|
|
1075
|
+
"session.directoryMigration",
|
|
1076
|
+
) === "disabled"
|
|
1077
|
+
? "disabled"
|
|
1078
|
+
: "copy-retain";
|
|
1061
1079
|
let opened: StrictSessionOpenResult;
|
|
1062
1080
|
try {
|
|
1063
1081
|
opened = await (deps.openExistingSessionStrict ?? SessionManager.openExistingStrict)(
|
|
1064
1082
|
selection.identity,
|
|
1065
1083
|
parsedArgs.sessionDir,
|
|
1084
|
+
undefined,
|
|
1085
|
+
resumeMigrationPolicy,
|
|
1066
1086
|
);
|
|
1067
1087
|
} catch {
|
|
1068
1088
|
process.stderr.write(`${BARE_RESUME_OPEN_ERROR}\n`);
|
package/src/memories/index.ts
CHANGED
|
@@ -249,6 +249,7 @@ async function runPhase1(options: {
|
|
|
249
249
|
|
|
250
250
|
const claims = claimStage1Jobs(db, {
|
|
251
251
|
nowSec,
|
|
252
|
+
cwd: session.sessionManager.getCwd(),
|
|
252
253
|
threadScanLimit: config.threadScanLimit,
|
|
253
254
|
maxRolloutsPerStartup: config.maxRolloutsPerStartup,
|
|
254
255
|
maxRolloutAgeDays: config.maxRolloutAgeDays,
|
package/src/memories/storage.ts
CHANGED
|
@@ -137,6 +137,7 @@ export function claimStage1Jobs(
|
|
|
137
137
|
db: Database,
|
|
138
138
|
params: {
|
|
139
139
|
nowSec: number;
|
|
140
|
+
cwd: string;
|
|
140
141
|
threadScanLimit: number;
|
|
141
142
|
maxRolloutsPerStartup: number;
|
|
142
143
|
maxRolloutAgeDays: number;
|
|
@@ -149,6 +150,7 @@ export function claimStage1Jobs(
|
|
|
149
150
|
): Stage1Claim[] {
|
|
150
151
|
const {
|
|
151
152
|
nowSec,
|
|
153
|
+
cwd,
|
|
152
154
|
threadScanLimit,
|
|
153
155
|
maxRolloutsPerStartup,
|
|
154
156
|
maxRolloutAgeDays,
|
|
@@ -168,8 +170,10 @@ export function claimStage1Jobs(
|
|
|
168
170
|
let runningCount = runningCountRow?.count ?? 0;
|
|
169
171
|
if (runningCount >= runningConcurrencyCap) return [];
|
|
170
172
|
const candidateRows = db
|
|
171
|
-
.prepare(
|
|
172
|
-
|
|
173
|
+
.prepare(
|
|
174
|
+
"SELECT id, updated_at, rollout_path, cwd, source_kind FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT ?",
|
|
175
|
+
)
|
|
176
|
+
.all(cwd, threadScanLimit) as Array<{
|
|
173
177
|
id: string;
|
|
174
178
|
updated_at: number;
|
|
175
179
|
rollout_path: string;
|