@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.
Files changed (413) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/launch-disposition.d.ts +23 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +141 -11
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  54. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  55. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  56. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  57. package/dist/types/goals/runtime.d.ts +1 -0
  58. package/dist/types/goals/state.d.ts +8 -0
  59. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  60. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  61. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  62. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  63. package/dist/types/hindsight/state.d.ts +11 -4
  64. package/dist/types/lsp/lspmux.d.ts +5 -3
  65. package/dist/types/lsp/path-trust.d.ts +1 -0
  66. package/dist/types/main.d.ts +4 -2
  67. package/dist/types/memories/storage.d.ts +1 -0
  68. package/dist/types/modes/action-registry.d.ts +29 -0
  69. package/dist/types/modes/components/chrome.d.ts +14 -0
  70. package/dist/types/modes/components/command-palette.d.ts +27 -0
  71. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  72. package/dist/types/modes/components/index.d.ts +2 -1
  73. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  74. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  75. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  76. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  77. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  78. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  79. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  80. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  81. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  82. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  83. package/dist/types/modes/components/status-line.d.ts +1 -94
  84. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  85. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  86. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  87. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  88. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  89. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  90. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  91. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  92. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  93. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  94. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  95. package/dist/types/modes/interactive-mode.d.ts +19 -17
  96. package/dist/types/modes/jobs-observer.d.ts +2 -0
  97. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  98. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  99. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  100. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  101. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  102. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  103. package/dist/types/modes/types.d.ts +20 -13
  104. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  105. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  106. package/dist/types/sdk/broker/broker.d.ts +78 -12
  107. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  108. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  109. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  110. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  111. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  112. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  113. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  114. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  117. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  118. package/dist/types/sdk/bus/index.d.ts +29 -0
  119. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  120. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  121. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  122. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  123. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  124. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  125. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  126. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  127. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  128. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  129. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  130. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  131. package/dist/types/sdk/host/host.d.ts +2 -0
  132. package/dist/types/sdk/index.d.ts +1 -0
  133. package/dist/types/sdk/session-directory.d.ts +56 -0
  134. package/dist/types/sdk/session.d.ts +2 -0
  135. package/dist/types/session/agent-session.d.ts +82 -47
  136. package/dist/types/session/agent-storage.d.ts +7 -0
  137. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  138. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  139. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  140. package/dist/types/session/messages.d.ts +16 -0
  141. package/dist/types/session/session-manager-internal.d.ts +14 -0
  142. package/dist/types/session/session-manager.d.ts +82 -12
  143. package/dist/types/session/session-storage.d.ts +43 -3
  144. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  145. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  146. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  147. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  148. package/dist/types/system-prompt.d.ts +2 -11
  149. package/dist/types/task/executor.d.ts +3 -1
  150. package/dist/types/thinking.d.ts +7 -0
  151. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  152. package/dist/types/tools/ask.d.ts +64 -2
  153. package/dist/types/tools/bash.d.ts +3 -0
  154. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  155. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  156. package/dist/types/tools/cron.d.ts +2 -0
  157. package/dist/types/tools/fetch.d.ts +1 -0
  158. package/dist/types/tools/index.d.ts +30 -15
  159. package/dist/types/tools/output-meta.d.ts +1 -0
  160. package/dist/types/tools/read.d.ts +5 -0
  161. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  162. package/dist/types/tools/subagent.d.ts +1 -0
  163. package/dist/types/utils/event-bus.d.ts +1 -1
  164. package/dist/types/utils/file-mentions.d.ts +3 -3
  165. package/dist/types/web/search/index.d.ts +3 -1
  166. package/package.json +21 -9
  167. package/scripts/build-sdk-package-smoke.ts +34 -1
  168. package/scripts/generate-hotkeys-docs.ts +74 -0
  169. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  170. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  171. package/src/async/job-manager.ts +49 -6
  172. package/src/cli/config-cli.ts +75 -2
  173. package/src/cli/launch-disposition.ts +42 -0
  174. package/src/cli/notify-cli.ts +92 -26
  175. package/src/cli/session-picker.ts +20 -3
  176. package/src/cli/setup-cli.ts +1 -1
  177. package/src/cli.ts +1 -1
  178. package/src/commands/config.ts +1 -1
  179. package/src/commands/harness.ts +34 -106
  180. package/src/commands/launch.ts +6 -4
  181. package/src/commands/mcp-serve.ts +85 -8
  182. package/src/commands/mcp.ts +2 -2
  183. package/src/commands/notify.ts +8 -4
  184. package/src/commands/sdk.ts +61 -40
  185. package/src/commands/ultragoal.ts +4 -1
  186. package/src/config/atomic-yaml-patch.ts +80 -13
  187. package/src/config/file-lock.ts +105 -43
  188. package/src/config/keybindings.ts +64 -20
  189. package/src/config/model-auth.ts +9 -0
  190. package/src/config/model-bindings-applier.ts +139 -0
  191. package/src/config/model-discovery-manager.ts +224 -0
  192. package/src/config/model-equivalence.ts +66 -0
  193. package/src/config/model-registry.ts +169 -302
  194. package/src/config/model-resolver.ts +361 -369
  195. package/src/config/models-config-schema.ts +5 -4
  196. package/src/config/settings-schema.ts +214 -12
  197. package/src/config/settings.ts +458 -482
  198. package/src/coordinator-mcp/policy.ts +76 -11
  199. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  200. package/src/coordinator-mcp/question-state.ts +778 -0
  201. package/src/coordinator-mcp/server.ts +1702 -238
  202. package/src/debug/index.ts +1 -1
  203. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  204. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  205. package/src/discovery/helpers.ts +1 -50
  206. package/src/edit/index.ts +64 -0
  207. package/src/eval/py/env.ts +26 -0
  208. package/src/eval/py/kernel.ts +5 -3
  209. package/src/extensibility/extensions/loader.ts +9 -1
  210. package/src/extensibility/extensions/runner.ts +12 -3
  211. package/src/extensibility/extensions/types.ts +39 -1
  212. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  213. package/src/extensibility/hooks/runner.ts +2 -2
  214. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  215. package/src/extensibility/shared-events.ts +2 -0
  216. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  217. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  218. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  219. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  220. package/src/gjc-runtime/launch-tmux.ts +2 -5
  221. package/src/gjc-runtime/launch-worktree.ts +5 -5
  222. package/src/gjc-runtime/linux-proc.ts +71 -0
  223. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  224. package/src/gjc-runtime/session-resolution.ts +18 -3
  225. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  226. package/src/gjc-runtime/state-migrations.ts +1 -1
  227. package/src/gjc-runtime/state-runtime.ts +327 -249
  228. package/src/gjc-runtime/state-writer.ts +36 -40
  229. package/src/gjc-runtime/team-launch.ts +319 -0
  230. package/src/gjc-runtime/team-notify.ts +326 -0
  231. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  232. package/src/gjc-runtime/team-store.ts +828 -0
  233. package/src/gjc-runtime/team-workers.ts +962 -0
  234. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  235. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  236. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  237. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  238. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  239. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  240. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  241. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  242. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  243. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  244. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  245. package/src/goals/continuation-timeout-guard.ts +79 -0
  246. package/src/goals/runtime.ts +7 -5
  247. package/src/goals/state.ts +18 -0
  248. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  249. package/src/harness-control-plane/phase-rollup.ts +4 -7
  250. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  251. package/src/harness-control-plane/receipt-spool.ts +51 -7
  252. package/src/harness-control-plane/session-lease.ts +12 -130
  253. package/src/hindsight/backend.ts +26 -27
  254. package/src/hindsight/state.ts +101 -32
  255. package/src/hooks/skill-state.ts +17 -11
  256. package/src/internal-urls/docs-index.generated.ts +21 -18
  257. package/src/lsp/client.ts +1 -1
  258. package/src/lsp/config.ts +145 -26
  259. package/src/lsp/index.ts +1 -1
  260. package/src/lsp/lspmux.ts +39 -10
  261. package/src/lsp/path-trust.ts +81 -0
  262. package/src/main.ts +127 -30
  263. package/src/memories/index.ts +1 -0
  264. package/src/memories/storage.ts +6 -2
  265. package/src/modes/action-registry.ts +165 -0
  266. package/src/modes/components/chrome.ts +40 -0
  267. package/src/modes/components/command-palette.ts +168 -0
  268. package/src/modes/components/custom-editor.ts +1 -1
  269. package/src/modes/components/custom-provider-wizard.ts +51 -14
  270. package/src/modes/components/index.ts +2 -1
  271. package/src/modes/components/pet-selector.ts +11 -17
  272. package/src/modes/components/plan-preview-overlay.ts +295 -0
  273. package/src/modes/components/queue-mode-selector.ts +10 -27
  274. package/src/modes/components/queue-pane.ts +79 -0
  275. package/src/modes/components/read-tool-group.ts +13 -0
  276. package/src/modes/components/session-observer-overlay.ts +207 -772
  277. package/src/modes/components/sessions-dashboard.ts +181 -0
  278. package/src/modes/components/settings-selector.ts +1 -1
  279. package/src/modes/components/show-images-selector.ts +10 -24
  280. package/src/modes/components/status-line/types.ts +1 -1
  281. package/src/modes/components/status-line.ts +1 -969
  282. package/src/modes/components/tasks-pane.ts +73 -0
  283. package/src/modes/components/theme-selector.ts +11 -31
  284. package/src/modes/components/tool-execution.ts +22 -0
  285. package/src/modes/components/tool-status-header.ts +1056 -0
  286. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  287. package/src/modes/controllers/btw-controller.ts +1 -0
  288. package/src/modes/controllers/event-controller.ts +3 -2
  289. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  290. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  291. package/src/modes/controllers/input-controller.ts +764 -229
  292. package/src/modes/controllers/mode-gate.ts +24 -0
  293. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  294. package/src/modes/controllers/selector-controller.ts +225 -10
  295. package/src/modes/controllers/stt-controller.ts +125 -0
  296. package/src/modes/interactive-mode.ts +289 -1150
  297. package/src/modes/jobs-observer.ts +3 -0
  298. package/src/modes/prompt-action-autocomplete.ts +7 -0
  299. package/src/modes/runtime-init.ts +8 -0
  300. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  301. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  302. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  303. package/src/modes/tasks-aggregator.ts +199 -0
  304. package/src/modes/transcript-item-registry.ts +229 -0
  305. package/src/modes/types.ts +45 -18
  306. package/src/modes/utils/ui-helpers.ts +17 -9
  307. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  308. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  309. package/src/prompts/agents/architect.md +2 -8
  310. package/src/prompts/agents/critic.md +2 -8
  311. package/src/prompts/agents/executor.md +3 -0
  312. package/src/prompts/agents/planner.md +4 -12
  313. package/src/prompts/system/custom-system-prompt.md +1 -23
  314. package/src/prompts/system/plan-mode-approved.md +8 -0
  315. package/src/prompts/system/system-prompt.md +17 -96
  316. package/src/prompts/tools/bash.md +4 -4
  317. package/src/prompts/tools/browser.md +15 -52
  318. package/src/prompts/tools/find.md +2 -2
  319. package/src/prompts/tools/hashline.md +13 -55
  320. package/src/prompts/tools/read.md +3 -6
  321. package/src/prompts/tools/search-tool-bm25.md +0 -2
  322. package/src/prompts/tools/search.md +2 -4
  323. package/src/prompts/tools/skill-discovery.md +1 -2
  324. package/src/prompts/tools/task.md +1 -1
  325. package/src/runtime-mcp/manager.ts +1 -1
  326. package/src/sdk/broker/broker.ts +206 -78
  327. package/src/sdk/broker/discovery.ts +1 -1
  328. package/src/sdk/broker/ensure.ts +3 -1
  329. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  330. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  331. package/src/sdk/broker/lifecycle.ts +1399 -142
  332. package/src/sdk/broker/process-incarnation.ts +5 -13
  333. package/src/sdk/broker/session-index.ts +145 -56
  334. package/src/sdk/broker/state-version.ts +21 -0
  335. package/src/sdk/broker/transport.ts +1 -1
  336. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  337. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  338. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  339. package/src/sdk/bus/config-commands.ts +5 -4
  340. package/src/sdk/bus/config.ts +98 -0
  341. package/src/sdk/bus/conversation-store.ts +5 -29
  342. package/src/sdk/bus/daemon-paths.ts +2 -0
  343. package/src/sdk/bus/html-format.ts +20 -3
  344. package/src/sdk/bus/index.ts +782 -94
  345. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  346. package/src/sdk/bus/natives-augment.d.ts +26 -1
  347. package/src/sdk/bus/notification-service.ts +168 -20
  348. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  349. package/src/sdk/bus/recent-activity.ts +188 -74
  350. package/src/sdk/bus/rich-document.ts +258 -0
  351. package/src/sdk/bus/rich-render.ts +62 -70
  352. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  353. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  354. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  355. package/src/sdk/bus/threaded-inbound.ts +8 -3
  356. package/src/sdk/bus/threaded-render.ts +57 -1
  357. package/src/sdk/bus/topic-registry.ts +120 -13
  358. package/src/sdk/host/control/dispatch.ts +14 -2
  359. package/src/sdk/host/control/operations.ts +6 -1
  360. package/src/sdk/host/host.ts +44 -2
  361. package/src/sdk/host/query/handlers.ts +20 -22
  362. package/src/sdk/host/query/revision-store.ts +80 -57
  363. package/src/sdk/index.ts +1 -0
  364. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  365. package/src/sdk/session-directory.ts +141 -0
  366. package/src/sdk/session.ts +462 -480
  367. package/src/session/agent-session.ts +1700 -583
  368. package/src/session/agent-storage.ts +18 -0
  369. package/src/session/cache-economics.ts +25 -8
  370. package/src/session/fallback-chain-controller.ts +53 -3
  371. package/src/session/internal/managed-session-scope.ts +2226 -0
  372. package/src/session/internal/managed-session-storage.ts +453 -0
  373. package/src/session/messages.ts +91 -2
  374. package/src/session/session-manager-internal.ts +33 -0
  375. package/src/session/session-manager.ts +1112 -333
  376. package/src/session/session-storage.ts +444 -39
  377. package/src/session/volatile-context-pruning.ts +78 -0
  378. package/src/setup/credential-auto-import.ts +2 -1
  379. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  380. package/src/setup/provider-onboarding.ts +36 -61
  381. package/src/setup/provider-presets.json +5 -0
  382. package/src/skill-state/active-state.ts +3 -13
  383. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  384. package/src/skill-state/workflow-state-contract.ts +6 -41
  385. package/src/slash-commands/builtin-registry.ts +46 -11
  386. package/src/system-prompt.ts +37 -44
  387. package/src/task/agents.ts +19 -1
  388. package/src/task/executor.ts +90 -6
  389. package/src/task/index.ts +36 -21
  390. package/src/thinking.ts +21 -6
  391. package/src/tool-discovery/tool-index.ts +2 -134
  392. package/src/tools/ask.ts +157 -21
  393. package/src/tools/ast-edit.ts +1 -1
  394. package/src/tools/bash.ts +38 -0
  395. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  396. package/src/tools/browser/tab-supervisor.ts +282 -36
  397. package/src/tools/computer.ts +10 -3
  398. package/src/tools/cron.ts +30 -16
  399. package/src/tools/eval.ts +1 -2
  400. package/src/tools/fetch.ts +11 -4
  401. package/src/tools/index.ts +76 -24
  402. package/src/tools/output-meta.ts +19 -7
  403. package/src/tools/read.ts +219 -64
  404. package/src/tools/resource-gc.ts +2 -2
  405. package/src/tools/search-tool-bm25.ts +11 -74
  406. package/src/tools/subagent.ts +43 -2
  407. package/src/utils/event-bus.ts +9 -4
  408. package/src/utils/file-mentions.ts +4 -4
  409. package/src/web/search/index.ts +3 -3
  410. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  411. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  412. package/src/discovery/plugin-dir-roots.ts +0 -28
  413. 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 normalized = normalizeServerConfig(name, config);
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], ...config };
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, config);
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
- read(): NormalizedConfig | null;
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 fileConfigSource(filePath: string): ConfigSource {
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
- read: () => readConfigFile(filePath),
375
+ allowProcessOverrides,
376
+ read: () => readCanonicalConfigFile(filePath, cwd),
286
377
  };
287
378
  }
288
379
 
289
- function readMarketplaceLspConfig(root: ClaudePluginRoot): NormalizedConfig | null {
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 catalog = JSON.parse(fs.readFileSync(catalogPath, "utf-8")) as unknown;
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
- return readConfigFile(configPath);
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
- return normalizeConfig({ servers: lspServers });
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
- read: () => readMarketplaceLspConfig(root),
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 (from marketplace/--plugin-dir roots)
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 parsed = source.read();
412
- if (!parsed) continue;
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 = resolveCommand(config.command, cwd);
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 = resolveCommand(config.command, cwd);
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: ReturnType<typeof setTimeout> | undefined;
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 (cachedState && now - cacheTimestamp < STATE_CACHE_TTL_MS) {
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 ($flag("PI_DISABLE_LSPMUX")) {
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 = $which("lspmux");
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(command: string, args?: string[]): Promise<LspmuxWrappedCommand> {
255
- const state = await detectLspmux();
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
+ }