oh-my-opencode 4.17.1 → 4.18.1

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 (213) hide show
  1. package/.agents/skills/codex-qa/SKILL.md +2 -0
  2. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +3654 -0
  3. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +3071 -0
  4. package/.agents/skills/work-with-pr/SKILL.md +16 -37
  5. package/.agents/skills/work-with-pr-workspace/evals/evals.json +3 -3
  6. package/.opencode/skills/work-with-pr/SKILL.md +16 -37
  7. package/.opencode/skills/work-with-pr-workspace/evals/evals.json +3 -3
  8. package/dist/cli/get-local-version/types.d.ts +1 -1
  9. package/dist/cli/index.js +498 -165
  10. package/dist/cli-node/index.js +498 -165
  11. package/dist/index.js +425 -392
  12. package/dist/skills/frontend/SKILL.md +1 -1
  13. package/dist/skills/frontend/references/design/README.md +9 -0
  14. package/dist/skills/frontend/references/design/design-system-architecture.md +4 -2
  15. package/dist/skills/frontend/references/design/layout-skill.md +107 -0
  16. package/dist/skills/programming/SKILL.md +12 -2
  17. package/package.json +17 -16
  18. package/packages/lsp-core/package.json +4 -0
  19. package/packages/lsp-core/src/index.ts +1 -0
  20. package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
  21. package/packages/lsp-core/src/lsp/cleanup-errors.ts +12 -3
  22. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +261 -0
  23. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +63 -0
  24. package/packages/lsp-core/src/lsp/client-wrapper.ts +35 -5
  25. package/packages/lsp-core/src/lsp/client.ts +262 -80
  26. package/packages/lsp-core/src/lsp/config-loader.ts +5 -17
  27. package/packages/lsp-core/src/lsp/connection.ts +12 -6
  28. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +104 -0
  29. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +60 -27
  30. package/packages/lsp-core/src/lsp/errors.ts +11 -0
  31. package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
  32. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
  33. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +215 -0
  34. package/packages/lsp-core/src/lsp/formatters.ts +3 -0
  35. package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
  36. package/packages/lsp-core/src/lsp/json-rpc-connection.ts +73 -5
  37. package/packages/lsp-core/src/lsp/server-install-state.ts +3 -6
  38. package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
  39. package/packages/lsp-core/src/lsp/transport.ts +96 -70
  40. package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
  41. package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +214 -0
  42. package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
  43. package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +163 -0
  44. package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
  45. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +67 -0
  46. package/packages/lsp-core/src/lsp/workspace-document-state.ts +368 -0
  47. package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +113 -0
  48. package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
  49. package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
  50. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
  51. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
  52. package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
  53. package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
  54. package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
  55. package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
  56. package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
  57. package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
  58. package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
  59. package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
  60. package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
  61. package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
  62. package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
  63. package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
  64. package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
  65. package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
  66. package/packages/lsp-core/src/lsp/workspace-edit.ts +49 -200
  67. package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
  68. package/packages/lsp-core/src/mcp.ts +18 -7
  69. package/packages/lsp-core/src/missing-dependency-result.test.ts +105 -0
  70. package/packages/lsp-core/src/missing-dependency-result.ts +57 -0
  71. package/packages/lsp-core/src/post-edit/index.ts +1 -0
  72. package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
  73. package/packages/lsp-core/src/post-edit/orchestration.ts +178 -0
  74. package/packages/lsp-core/src/request-context.test.ts +171 -0
  75. package/packages/lsp-core/src/request-context.ts +222 -9
  76. package/packages/lsp-core/src/tool-surface.test.ts +4 -1
  77. package/packages/lsp-core/src/tools/diagnostics.ts +32 -13
  78. package/packages/lsp-core/src/tools/navigation.ts +12 -12
  79. package/packages/lsp-core/src/tools/rename.ts +10 -15
  80. package/packages/lsp-core/src/tools/symbols.ts +11 -11
  81. package/packages/lsp-core/src/tools/types.ts +2 -1
  82. package/packages/lsp-daemon/dist/cli.js +3114 -747
  83. package/packages/lsp-daemon/dist/client.d.ts +105 -0
  84. package/packages/lsp-daemon/dist/client.js +5851 -0
  85. package/packages/lsp-daemon/dist/daemon-client.d.ts +11 -6
  86. package/packages/lsp-daemon/dist/daemon-client.js +113 -30
  87. package/packages/lsp-daemon/dist/daemon-server.d.ts +1 -0
  88. package/packages/lsp-daemon/dist/daemon-server.js +40 -15
  89. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +8 -7
  90. package/packages/lsp-daemon/dist/ensure-daemon.js +67 -44
  91. package/packages/lsp-daemon/dist/index.d.ts +2 -2
  92. package/packages/lsp-daemon/dist/index.js +2862 -754
  93. package/packages/lsp-daemon/dist/ipc-protocol.d.ts +46 -0
  94. package/packages/lsp-daemon/dist/ipc-protocol.js +187 -0
  95. package/packages/lsp-daemon/dist/lock.js +14 -4
  96. package/packages/lsp-daemon/dist/ownership.d.ts +49 -0
  97. package/packages/lsp-daemon/dist/ownership.js +168 -0
  98. package/packages/lsp-daemon/dist/paths.d.ts +33 -9
  99. package/packages/lsp-daemon/dist/paths.js +72 -33
  100. package/packages/lsp-daemon/dist/proxy.d.ts +3 -0
  101. package/packages/lsp-daemon/dist/proxy.js +54 -3
  102. package/packages/lsp-daemon/dist/request-routing.d.ts +7 -2
  103. package/packages/lsp-daemon/dist/request-routing.js +71 -22
  104. package/packages/lsp-daemon/dist/run-daemon.js +9 -2
  105. package/packages/lsp-daemon/dist/runtime-contract.d.ts +21 -0
  106. package/packages/lsp-daemon/dist/runtime-contract.js +58 -0
  107. package/packages/lsp-daemon/dist/socket-jsonrpc.js +6 -1
  108. package/packages/lsp-daemon/package.json +12 -3
  109. package/packages/lsp-tools-mcp/dist/cli.js +2115 -442
  110. package/packages/lsp-tools-mcp/dist/lsp/manager.js +1741 -148
  111. package/packages/lsp-tools-mcp/dist/mcp.js +2127 -454
  112. package/packages/lsp-tools-mcp/dist/request-context.js +176 -6
  113. package/packages/lsp-tools-mcp/dist/tools.js +2118 -446
  114. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  115. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  116. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  117. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  118. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  119. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  120. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  121. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  122. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  123. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  124. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +55 -0
  125. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +2959 -944
  126. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook-cli.js +0 -4
  127. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.d.ts +5 -2
  128. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +41 -62
  129. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +1 -1
  130. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +24 -15
  131. package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.d.ts +3 -7
  132. package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.js +23 -49
  133. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  134. package/packages/omo-codex/plugin/components/lsp/package.json +3 -2
  135. package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.mjs +31 -1
  136. package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.test.mjs +76 -0
  137. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +201 -0
  138. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.test.mjs +55 -0
  139. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +0 -4
  140. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +49 -71
  141. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +26 -15
  142. package/packages/omo-codex/plugin/components/lsp/src/lsp-session-state.ts +26 -64
  143. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +16 -17
  144. package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +30 -4
  145. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +19 -5
  146. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
  147. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +8 -6
  148. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  149. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  150. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
  151. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  152. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  153. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  154. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  155. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  156. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  157. package/packages/omo-codex/plugin/components/ultrawork/directive.md +37 -10
  158. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  159. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  160. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +37 -10
  161. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +37 -10
  162. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  163. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  164. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -2
  165. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +10 -9
  166. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  167. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  168. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  169. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  170. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  171. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  189. package/packages/omo-codex/plugin/package-lock.json +26 -14
  190. package/packages/omo-codex/plugin/package.json +1 -1
  191. package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +2 -3
  192. package/packages/omo-codex/plugin/scripts/build-components.mjs +13 -1
  193. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +1 -1
  194. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
  195. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +9 -0
  196. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +4 -2
  197. package/packages/omo-codex/plugin/skills/frontend/references/design/layout-skill.md +107 -0
  198. package/packages/omo-codex/plugin/skills/programming/SKILL.md +12 -2
  199. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +1 -1
  200. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +37 -10
  201. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -2
  202. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +10 -9
  203. package/packages/omo-codex/plugin/test/aggregate-build.test.mjs +8 -0
  204. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +128 -15
  205. package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +10 -0
  206. package/packages/omo-codex/plugin/test/lsp-prebuild-layouts.test.mjs +2 -0
  207. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +1 -1
  208. package/packages/omo-codex/scripts/install-dist/install-local.mjs +351 -74
  209. package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
  210. package/packages/shared-skills/skills/frontend/references/design/README.md +9 -0
  211. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +4 -2
  212. package/packages/shared-skills/skills/frontend/references/design/layout-skill.md +107 -0
  213. package/packages/shared-skills/skills/programming/SKILL.md +12 -2
@@ -1,19 +1,30 @@
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { basename, delimiter, dirname, isAbsolute } from "node:path";
1
3
  import { jsonRpcId, runJsonRpcStdioServer, successResponse } from "@oh-my-opencode/mcp-stdio-core";
2
4
  import { isPlainRecord } from "@oh-my-opencode/mcp-stdio-core/record";
3
5
  import { handleLspMcpRequest } from "@oh-my-opencode/lsp-core/mcp";
4
- import { callToolViaDaemon, currentRequestContext, } from "./daemon-client.js";
6
+ import { createStandaloneMcpRequestContext, runWithRequestContext } from "@oh-my-opencode/lsp-core/request-context";
7
+ import { callToolViaDaemon } from "./daemon-client.js";
5
8
  import { daemonPaths } from "./paths.js";
6
9
  export async function runMcpStdioProxy(options = {}) {
7
10
  const input = options.input ?? process.stdin;
8
11
  const output = options.output ?? process.stdout;
9
12
  const paths = options.paths ?? daemonPaths();
10
- const context = options.context ?? currentRequestContext();
13
+ const env = options.env ?? process.env;
14
+ const cwd = options.cwd ?? inferOpenCodeProjectCwd(env["LSP_TOOLS_MCP_PROJECT_CONFIG"]);
15
+ const contextEnv = cwd === undefined ? env : canonicalizeContextEnv(env);
16
+ const contextInput = {
17
+ env: contextEnv,
18
+ ...(cwd === undefined ? {} : { cwd }),
19
+ ...(options.homeDir === undefined ? {} : { homeDir: options.homeDir }),
20
+ };
21
+ const context = options.context ?? createStandaloneMcpRequestContext(contextInput);
11
22
  const callOptions = { paths, context, ...(options.ensure ? { ensure: options.ensure } : {}) };
12
23
  await runJsonRpcStdioServer({
13
24
  input,
14
25
  output,
15
26
  idleTimeoutMs: 0,
16
- handler: handleProxyRequest,
27
+ handler: (request, requestOptions) => runWithRequestContext(context, () => handleProxyRequest(request, requestOptions)),
17
28
  handlerOptions: callOptions,
18
29
  onHandlerError: (error) => {
19
30
  process.stderr.write(`[lsp-daemon] proxy error: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -36,3 +47,43 @@ function asToolCall(parsed) {
36
47
  const args = params["arguments"];
37
48
  return { id: jsonRpcId(parsed["id"]), name: params["name"], args: isPlainRecord(args) ? args : {} };
38
49
  }
50
+ function inferOpenCodeProjectCwd(projectConfigEnv) {
51
+ if (!projectConfigEnv)
52
+ return undefined;
53
+ for (const entry of projectConfigEnv.split(delimiter)) {
54
+ const projectRoot = projectRootFromOpenCodeConfigPath(entry);
55
+ if (projectRoot)
56
+ return projectRoot;
57
+ }
58
+ return undefined;
59
+ }
60
+ function canonicalizeContextEnv(env) {
61
+ return {
62
+ ...env,
63
+ LSP_TOOLS_MCP_PROJECT_CONFIG: canonicalizePathList(env["LSP_TOOLS_MCP_PROJECT_CONFIG"]),
64
+ LSP_TOOLS_MCP_USER_CONFIG: canonicalizePath(env["LSP_TOOLS_MCP_USER_CONFIG"]),
65
+ LSP_TOOLS_MCP_INSTALL_DECISIONS: canonicalizePath(env["LSP_TOOLS_MCP_INSTALL_DECISIONS"]),
66
+ };
67
+ }
68
+ function canonicalizePathList(value) {
69
+ if (value === undefined)
70
+ return undefined;
71
+ return value
72
+ .split(delimiter)
73
+ .map((entry) => canonicalizePath(entry) ?? entry)
74
+ .join(delimiter);
75
+ }
76
+ function canonicalizePath(value) {
77
+ if (value === undefined || !isAbsolute(value) || !existsSync(value))
78
+ return value;
79
+ return realpathSync(value);
80
+ }
81
+ function projectRootFromOpenCodeConfigPath(path) {
82
+ if (basename(path) !== "lsp.json" && basename(path) !== "lsp-client.json")
83
+ return undefined;
84
+ const configDir = dirname(path);
85
+ const configDirName = basename(configDir);
86
+ if (configDirName !== ".opencode" && configDirName !== ".omo")
87
+ return undefined;
88
+ return dirname(configDir);
89
+ }
@@ -1,9 +1,14 @@
1
- import { type JsonRpcResponse } from "@oh-my-opencode/lsp-core/mcp";
2
1
  import { type RequestContext } from "@oh-my-opencode/lsp-core/request-context";
2
+ import type { DaemonOwner } from "./ownership.js";
3
3
  export declare const CONTEXT_KEY = "_context";
4
4
  export interface RoutedRequest {
5
5
  input: unknown;
6
6
  context: RequestContext | undefined;
7
7
  }
8
8
  export declare function extractRequestContext(raw: unknown): RoutedRequest;
9
- export declare function handleDaemonMessage(raw: unknown): Promise<JsonRpcResponse | undefined>;
9
+ export type DaemonRouteState = {
10
+ readonly token: string;
11
+ readonly owner: DaemonOwner;
12
+ readonly activeRequests?: Map<string, AbortController>;
13
+ };
14
+ export declare function handleDaemonMessage(raw: unknown, state: DaemonRouteState): Promise<unknown>;
@@ -1,42 +1,91 @@
1
1
  import { handleLspMcpRequest } from "@oh-my-opencode/lsp-core/mcp";
2
- import { runWithRequestContext } from "@oh-my-opencode/lsp-core/request-context";
2
+ import { parseLspRequestContext, runWithRequestContext } from "@oh-my-opencode/lsp-core/request-context";
3
3
  import { isPlainRecord } from "@oh-my-opencode/mcp-stdio-core/record";
4
+ import { authenticateMessage, OMO_DAEMON_PROTOCOL_VERSION } from "./ipc-protocol.js";
4
5
  export const CONTEXT_KEY = "_context";
6
+ class InvalidDaemonRequestError extends Error {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.name = "InvalidDaemonRequestError";
10
+ }
11
+ }
5
12
  export function extractRequestContext(raw) {
6
13
  if (!isPlainRecord(raw) || raw["method"] !== "tools/call")
7
14
  return { input: raw, context: undefined };
8
15
  const params = raw["params"];
9
16
  if (!isPlainRecord(params))
10
- return { input: raw, context: undefined };
17
+ throw new InvalidDaemonRequestError("Daemon tools/call params must be an object.");
11
18
  const args = params["arguments"];
12
19
  if (!isPlainRecord(args))
13
- return { input: raw, context: undefined };
20
+ throw new InvalidDaemonRequestError("Daemon tools/call arguments must be an object.");
21
+ if (!Object.hasOwn(args, CONTEXT_KEY)) {
22
+ throw new InvalidDaemonRequestError("Daemon tools/call arguments must include _context.");
23
+ }
14
24
  const context = parseContext(args[CONTEXT_KEY]);
15
- if (!context)
16
- return { input: raw, context: undefined };
17
25
  const cleanedArgs = { ...args };
18
26
  delete cleanedArgs[CONTEXT_KEY];
19
27
  const cleaned = { ...raw, params: { ...params, arguments: cleanedArgs } };
20
28
  return { input: cleaned, context };
21
29
  }
22
- export function handleDaemonMessage(raw) {
23
- const { input, context } = extractRequestContext(raw);
24
- if (context)
25
- return runWithRequestContext(context, () => handleLspMcpRequest(input));
26
- return handleLspMcpRequest(input);
30
+ export function handleDaemonMessage(raw, state) {
31
+ const authenticated = authenticateMessage(raw, state.token);
32
+ if ("error" in authenticated)
33
+ return Promise.resolve(authenticated);
34
+ if (authenticated.method === "omo/ping") {
35
+ return Promise.resolve({
36
+ jsonrpc: "2.0",
37
+ id: authenticated.id,
38
+ result: { protocolVersion: OMO_DAEMON_PROTOCOL_VERSION, ...state.owner },
39
+ });
40
+ }
41
+ if (authenticated.method === "$/cancelRequest") {
42
+ const targetId = cancellationTargetId(authenticated.input);
43
+ if (targetId !== undefined)
44
+ state.activeRequests?.get(String(targetId))?.abort();
45
+ return Promise.resolve(undefined);
46
+ }
47
+ let routed;
48
+ try {
49
+ routed = extractRequestContext(authenticated.input);
50
+ }
51
+ catch (error) {
52
+ const message = error instanceof Error ? error.message : "invalid daemon request";
53
+ return Promise.resolve({
54
+ jsonrpc: "2.0",
55
+ id: authenticated.id,
56
+ error: { code: -32602, message, data: { code: "invalid_daemon_request" } },
57
+ });
58
+ }
59
+ const { input, context } = routed;
60
+ const key = routeRequestKey(authenticated.id);
61
+ if (key === undefined || !state.activeRequests) {
62
+ if (context)
63
+ return runWithRequestContext(context, () => handleLspMcpRequest(input));
64
+ return handleLspMcpRequest(input);
65
+ }
66
+ const controller = new AbortController();
67
+ state.activeRequests.set(key, controller);
68
+ const options = { signal: controller.signal };
69
+ const run = context
70
+ ? runWithRequestContext(context, () => handleLspMcpRequest(input, options))
71
+ : handleLspMcpRequest(input, options);
72
+ return run.finally(() => {
73
+ if (state.activeRequests?.get(key) === controller)
74
+ state.activeRequests.delete(key);
75
+ });
27
76
  }
28
- function parseContext(value) {
29
- if (!isPlainRecord(value))
77
+ function routeRequestKey(id) {
78
+ return typeof id === "string" || typeof id === "number" ? String(id) : undefined;
79
+ }
80
+ function cancellationTargetId(input) {
81
+ const params = input["params"];
82
+ if (!isPlainRecord(params))
30
83
  return undefined;
31
- const context = {};
32
- const cwd = value["cwd"];
33
- if (typeof cwd === "string")
34
- context.cwd = cwd;
35
- const env = value["env"];
36
- if (isStringRecord(env))
37
- context.env = env;
38
- return context.cwd === undefined && context.env === undefined ? undefined : context;
84
+ const id = params["id"];
85
+ return typeof id === "string" || typeof id === "number" ? id : undefined;
39
86
  }
40
- function isStringRecord(value) {
41
- return isPlainRecord(value) && Object.values(value).every((item) => typeof item === "string");
87
+ function parseContext(value) {
88
+ if (!isPlainRecord(value))
89
+ throw new InvalidDaemonRequestError("LSP request _context must be an object.");
90
+ return parseLspRequestContext(value);
42
91
  }
@@ -1,9 +1,16 @@
1
- import { startDaemonServer } from "./daemon-server.js";
1
+ import { DaemonAlreadyRunningError, startDaemonServer } from "./daemon-server.js";
2
2
  import { daemonPaths } from "./paths.js";
3
3
  export async function runDaemon() {
4
4
  process.on("uncaughtException", (error) => logNonFatal("uncaughtException", error));
5
5
  process.on("unhandledRejection", (reason) => logNonFatal("unhandledRejection", reason));
6
- await startDaemonServer(daemonPaths());
6
+ try {
7
+ await startDaemonServer(daemonPaths());
8
+ }
9
+ catch (error) {
10
+ if (error instanceof DaemonAlreadyRunningError)
11
+ return;
12
+ throw error;
13
+ }
7
14
  }
8
15
  function logNonFatal(kind, error) {
9
16
  const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
@@ -0,0 +1,21 @@
1
+ export declare const OMO_LSP_DAEMON_DIR = "OMO_LSP_DAEMON_DIR";
2
+ export declare const OMO_LSP_DAEMON_CLI = "OMO_LSP_DAEMON_CLI";
3
+ export declare const OMO_LSP_DAEMON_VERSION = "OMO_LSP_DAEMON_VERSION";
4
+ export interface DaemonRuntime {
5
+ readonly cliPath: string;
6
+ readonly version: string;
7
+ }
8
+ export type DaemonRuntimeDefaults = DaemonRuntime;
9
+ export type InvalidRuntimeOverrideReason = "paired_values_required" | "cli_must_be_absolute" | "cli_not_found" | "cli_not_file" | "packaged_cli_must_be_absolute";
10
+ export declare class InvalidRuntimeOverrideError extends Error {
11
+ readonly code = "invalid_runtime_override";
12
+ readonly reason: InvalidRuntimeOverrideReason;
13
+ constructor(reason: InvalidRuntimeOverrideReason, message: string);
14
+ }
15
+ export declare class InvalidDaemonVersionError extends Error {
16
+ readonly code = "invalid_daemon_version";
17
+ readonly version: string;
18
+ constructor(version: string);
19
+ }
20
+ export declare function validateDaemonVersion(version: string): string;
21
+ export declare function resolveDaemonRuntime(env: NodeJS.ProcessEnv, defaults: DaemonRuntimeDefaults): DaemonRuntime;
@@ -0,0 +1,58 @@
1
+ import { statSync } from "node:fs";
2
+ import { isAbsolute } from "node:path";
3
+ export const OMO_LSP_DAEMON_DIR = "OMO_LSP_DAEMON_DIR";
4
+ export const OMO_LSP_DAEMON_CLI = "OMO_LSP_DAEMON_CLI";
5
+ export const OMO_LSP_DAEMON_VERSION = "OMO_LSP_DAEMON_VERSION";
6
+ const DAEMON_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
7
+ export class InvalidRuntimeOverrideError extends Error {
8
+ constructor(reason, message) {
9
+ super(message);
10
+ this.code = "invalid_runtime_override";
11
+ this.name = "InvalidRuntimeOverrideError";
12
+ this.reason = reason;
13
+ }
14
+ }
15
+ export class InvalidDaemonVersionError extends Error {
16
+ constructor(version) {
17
+ super("LSP daemon version must match [A-Za-z0-9][A-Za-z0-9._+-]{0,127}");
18
+ this.code = "invalid_daemon_version";
19
+ this.name = "InvalidDaemonVersionError";
20
+ this.version = version;
21
+ }
22
+ }
23
+ export function validateDaemonVersion(version) {
24
+ if (!DAEMON_VERSION_PATTERN.test(version))
25
+ throw new InvalidDaemonVersionError(version);
26
+ return version;
27
+ }
28
+ export function resolveDaemonRuntime(env, defaults) {
29
+ const cliOverride = env[OMO_LSP_DAEMON_CLI];
30
+ const versionOverride = env[OMO_LSP_DAEMON_VERSION];
31
+ const hasCliOverride = cliOverride !== undefined;
32
+ const hasVersionOverride = versionOverride !== undefined;
33
+ if (hasCliOverride !== hasVersionOverride) {
34
+ throw new InvalidRuntimeOverrideError("paired_values_required", `${OMO_LSP_DAEMON_CLI} and ${OMO_LSP_DAEMON_VERSION} must be set together`);
35
+ }
36
+ if (!hasCliOverride || !hasVersionOverride) {
37
+ if (!isAbsolute(defaults.cliPath)) {
38
+ throw new InvalidRuntimeOverrideError("packaged_cli_must_be_absolute", "Packaged LSP daemon CLI path must be absolute");
39
+ }
40
+ return { cliPath: defaults.cliPath, version: validateDaemonVersion(defaults.version) };
41
+ }
42
+ if (!isAbsolute(cliOverride)) {
43
+ throw new InvalidRuntimeOverrideError("cli_must_be_absolute", `${OMO_LSP_DAEMON_CLI} must be an absolute path to an existing regular file`);
44
+ }
45
+ let cliStats;
46
+ try {
47
+ cliStats = statSync(cliOverride);
48
+ }
49
+ catch (error) {
50
+ if (!(error instanceof Error))
51
+ throw error;
52
+ throw new InvalidRuntimeOverrideError("cli_not_found", `${OMO_LSP_DAEMON_CLI} must name an existing regular file`);
53
+ }
54
+ if (!cliStats.isFile()) {
55
+ throw new InvalidRuntimeOverrideError("cli_not_file", `${OMO_LSP_DAEMON_CLI} must name an existing regular file`);
56
+ }
57
+ return { cliPath: cliOverride, version: validateDaemonVersion(versionOverride) };
58
+ }
@@ -15,7 +15,12 @@ export function createLineDecoder(onMessage, onParseError) {
15
15
  onMessage(JSON.parse(raw));
16
16
  }
17
17
  catch (error) {
18
- onParseError?.(raw, error);
18
+ if (error instanceof Error) {
19
+ onParseError?.(raw, error);
20
+ }
21
+ else {
22
+ throw error;
23
+ }
19
24
  }
20
25
  }
21
26
  index = buffer.indexOf("\n");
@@ -5,8 +5,16 @@
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.16.0",
7
7
  "license": "MIT",
8
- "main": "./dist/index.js",
9
- "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ "./client": {
10
+ "types": "./dist/client.d.ts",
11
+ "default": "./dist/client.js"
12
+ },
13
+ "./cli": {
14
+ "types": "./dist/cli.d.ts",
15
+ "default": "./dist/cli.js"
16
+ }
17
+ },
10
18
  "bin": {
11
19
  "omo-lsp-daemon": "./dist/cli.js"
12
20
  },
@@ -17,7 +25,8 @@
17
25
  "README.md"
18
26
  ],
19
27
  "scripts": {
20
- "build": "tsc -p tsconfig.build.json && bun build src/cli.ts src/index.ts --outdir dist --target node --format esm && node scripts/stamp-dist-version.mjs",
28
+ "build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json && bun build src/cli.ts src/index.ts src/client.ts --outdir dist --target node --format esm && node scripts/stamp-dist-version.mjs",
29
+ "smoke:client-package": "node scripts/client-package-smoke.mjs",
21
30
  "test": "vitest --run",
22
31
  "test:watch": "vitest",
23
32
  "typecheck": "tsc --noEmit",