oh-my-opencode 4.18.1 → 4.18.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.
Files changed (116) hide show
  1. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +39 -16
  2. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +111 -28
  3. package/bin/AGENTS.md +33 -0
  4. package/dist/cli/index.js +57 -18
  5. package/dist/cli-node/index.js +57 -18
  6. package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.d.ts +6 -0
  7. package/dist/hooks/category-skill-reminder/hook.d.ts +9 -1
  8. package/dist/hooks/comment-checker/hook.d.ts +8 -1
  9. package/dist/hooks/todo-continuation-enforcer/types.d.ts +3 -0
  10. package/dist/index.js +560 -333
  11. package/dist/plugin/messages-transform.d.ts +1 -0
  12. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +2 -0
  13. package/dist/tui.js +43 -4
  14. package/package.json +13 -13
  15. package/packages/git-bash-mcp/dist/cli.js +81 -19
  16. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +4 -4
  17. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +118 -1
  18. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +1 -1
  19. package/packages/lsp-daemon/dist/cli.js +262 -63
  20. package/packages/lsp-daemon/dist/client.js +194 -50
  21. package/packages/lsp-daemon/dist/daemon-client.d.ts +2 -2
  22. package/packages/lsp-daemon/dist/daemon-client.js +26 -2
  23. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +5 -4
  24. package/packages/lsp-daemon/dist/ensure-daemon.js +79 -18
  25. package/packages/lsp-daemon/dist/index.js +262 -63
  26. package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
  27. package/packages/lsp-daemon/dist/proxy.js +79 -23
  28. package/packages/lsp-tools-mcp/dist/cli.js +82 -20
  29. package/packages/lsp-tools-mcp/dist/mcp.js +82 -20
  30. package/packages/lsp-tools-mcp/dist/tools.js +1 -1
  31. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  32. package/packages/omo-codex/plugin/.mcp.json +2 -1
  33. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +100 -28
  36. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +100 -28
  37. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +21 -9
  39. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +35 -0
  40. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +69 -0
  41. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +57 -1
  42. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  43. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  44. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  45. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/lsp/.mcp.json +2 -1
  49. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  50. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -31
  51. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  52. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +1 -0
  54. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
  55. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -3
  56. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  57. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  58. package/packages/omo-codex/plugin/components/start-work-continuation/README.md +2 -2
  59. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +3 -3
  60. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +2 -2
  61. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  62. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +1 -1
  64. package/packages/omo-codex/plugin/components/start-work-continuation/src/codex-hook.ts +1 -1
  65. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +15 -1
  66. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +20 -0
  67. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  68. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  70. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/ultrawork/directive.md +50 -33
  72. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  73. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  74. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +50 -33
  75. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +50 -33
  76. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +3 -3
  77. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +5 -6
  78. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  79. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  80. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
  81. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +5 -6
  82. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +2 -2
  83. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  94. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  95. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  98. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  99. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  106. package/packages/omo-codex/plugin/package-lock.json +13 -13
  107. package/packages/omo-codex/plugin/package.json +1 -1
  108. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +10 -2
  109. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
  110. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +3 -2
  111. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
  112. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
  113. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -0
  114. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +7 -0
  115. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +35 -4
  116. package/packages/omo-codex/scripts/install-dist/install-local.mjs +1 -1
@@ -1897,6 +1897,9 @@ function errorResponse(id, code, message, data) {
1897
1897
  function jsonRpcId(value) {
1898
1898
  return typeof value === "string" || typeof value === "number" || value === null ? value : null;
1899
1899
  }
1900
+ function messageFromError(error) {
1901
+ return error instanceof Error ? error.message : String(error);
1902
+ }
1900
1903
  // ../../../../mcp-stdio-core/src/transport.ts
1901
1904
  var HEADER_SEPARATOR = Buffer.from(`\r
1902
1905
  \r
@@ -1919,16 +1922,45 @@ async function* readStdioJsonRpcMessages(input) {
1919
1922
  yield parseJsonPayload(trailing, "line");
1920
1923
  }
1921
1924
  }
1922
- function writeStdioJsonRpcResponse(output, response, responseMode) {
1925
+ async function writeStdioJsonRpcResponse(output, response, responseMode) {
1923
1926
  const body = JSON.stringify(response);
1924
- if (responseMode === "framed") {
1925
- output.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r
1927
+ const payload = responseMode === "framed" ? `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
1926
1928
  \r
1927
- ${body}`);
1928
- return;
1929
- }
1930
- output.write(`${body}
1931
- `);
1929
+ ${body}` : `${body}
1930
+ `;
1931
+ await writeChunk(output, payload);
1932
+ }
1933
+ function writeChunk(output, chunk) {
1934
+ return new Promise((resolve3, reject) => {
1935
+ let settled = false;
1936
+ const onError = (error) => {
1937
+ if (settled)
1938
+ return;
1939
+ settled = true;
1940
+ reject(error);
1941
+ };
1942
+ output.once("error", onError);
1943
+ try {
1944
+ output.write(chunk, (error) => {
1945
+ if (settled)
1946
+ return;
1947
+ settled = true;
1948
+ if (error) {
1949
+ queueMicrotask(() => output.removeListener("error", onError));
1950
+ reject(error);
1951
+ return;
1952
+ }
1953
+ output.removeListener("error", onError);
1954
+ resolve3();
1955
+ });
1956
+ } catch (error) {
1957
+ output.removeListener("error", onError);
1958
+ if (settled)
1959
+ return;
1960
+ settled = true;
1961
+ reject(error);
1962
+ }
1963
+ });
1932
1964
  }
1933
1965
  function readNextMessage(buffer) {
1934
1966
  if (buffer.length === 0)
@@ -2024,40 +2056,70 @@ async function runJsonRpcStdioServer(config) {
2024
2056
  break;
2025
2057
  idleTimer.arm();
2026
2058
  if (message.kind === "parse_error") {
2027
- handleParseError(message, config, log);
2059
+ if (!await handleParseError(message, config, log))
2060
+ break;
2028
2061
  continue;
2029
2062
  }
2030
- await handleRequest(message, config, log);
2063
+ if (!await handleRequest(message, config, log))
2064
+ break;
2031
2065
  }
2032
2066
  } finally {
2033
2067
  idleTimer.clear();
2034
2068
  log("stdio_stopped");
2035
2069
  }
2036
2070
  }
2037
- function handleParseError(message, config, log) {
2071
+ async function handleParseError(message, config, log) {
2038
2072
  log("parse_error", { message: message.message });
2039
2073
  const response = config.parseErrorResponse?.(message.message) ?? errorResponse(null, -32700, "Parse error", message.message);
2040
- if (response !== undefined) {
2041
- writeStdioJsonRpcResponse(config.output, response, message.responseMode);
2042
- }
2074
+ if (response === undefined)
2075
+ return true;
2076
+ return writeResponse(response, {
2077
+ output: config.output,
2078
+ responseMode: message.responseMode,
2079
+ log
2080
+ });
2043
2081
  }
2044
2082
  async function handleRequest(message, config, log) {
2045
2083
  const parsed = message.payload;
2046
2084
  const id = isPlainRecord(parsed) ? jsonRpcId(parsed["id"]) : null;
2047
2085
  const method = isPlainRecord(parsed) && typeof parsed["method"] === "string" ? parsed["method"] : null;
2048
2086
  log("request", { id: id === null ? null : String(id), method });
2087
+ let response;
2049
2088
  try {
2050
- const response = await config.handler(parsed, config.handlerOptions);
2051
- if (response === undefined)
2052
- return;
2053
- writeStdioJsonRpcResponse(config.output, response, message.responseMode);
2054
- log("response", { id: String(response.id), method, is_error: response.error !== undefined });
2089
+ response = await config.handler(parsed, config.handlerOptions);
2055
2090
  } catch (error) {
2056
2091
  if (config.onHandlerError === undefined)
2057
2092
  throw error;
2058
2093
  config.onHandlerError(error);
2094
+ return true;
2095
+ }
2096
+ if (response === undefined)
2097
+ return true;
2098
+ if (!await writeResponse(response, {
2099
+ output: config.output,
2100
+ responseMode: message.responseMode,
2101
+ log
2102
+ }))
2103
+ return false;
2104
+ log("response", { id: String(response.id), method, is_error: response.error !== undefined });
2105
+ return true;
2106
+ }
2107
+ async function writeResponse(response, context) {
2108
+ try {
2109
+ await writeStdioJsonRpcResponse(context.output, response, context.responseMode);
2110
+ return true;
2111
+ } catch (error) {
2112
+ if (!isTerminalOutputError(error))
2113
+ throw error;
2114
+ context.log("output_error", { message: messageFromError(error) });
2115
+ return false;
2059
2116
  }
2060
2117
  }
2118
+ function isTerminalOutputError(error) {
2119
+ if (!(error instanceof Error) || !("code" in error))
2120
+ return false;
2121
+ return error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED" || error.code === "ERR_STREAM_WRITE_AFTER_END";
2122
+ }
2061
2123
  function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
2062
2124
  let timer = null;
2063
2125
  let isClosed = false;
@@ -2138,18 +2200,28 @@ async function runBridgedCodegraphProcess(command, args, options) {
2138
2200
  resolveExit(signal === null ? 0 : 1);
2139
2201
  });
2140
2202
  });
2141
- const bridgeDone = Promise.all([
2142
- forwardClientToCodegraph(options.input, childInput, pendingResponses, (mode) => {
2143
- defaultResponseMode = mode;
2144
- }),
2145
- forwardCodegraphToClient(childOutput, options.output, pendingResponses, () => defaultResponseMode)
2146
- ]);
2203
+ const clientForwardingDone = forwardClientToCodegraph(options.input, childInput, pendingResponses, (mode) => {
2204
+ defaultResponseMode = mode;
2205
+ });
2206
+ const responseForwardingDone = forwardCodegraphToClient(childOutput, options.output, pendingResponses, () => defaultResponseMode);
2207
+ const bridgeDone = Promise.all([clientForwardingDone, responseForwardingDone]);
2208
+ const childAndResponsesDone = Promise.all([childExit, responseForwardingDone]).then(([exitCode]) => exitCode);
2147
2209
  const destroyChildPipes = () => {
2148
2210
  childInput.destroy();
2149
2211
  childOutput.destroy();
2150
2212
  };
2151
2213
  childExit.then(destroyChildPipes, destroyChildPipes);
2152
- return Promise.race([childExit, bridgeDone.then(() => childExit)]);
2214
+ try {
2215
+ return await Promise.race([childAndResponsesDone, bridgeDone.then(() => childExit)]);
2216
+ } catch (error) {
2217
+ destroyChildPipes();
2218
+ if (child.exitCode === null && child.signalCode === null)
2219
+ child.kill("SIGKILL");
2220
+ await childExit.catch(() => {
2221
+ return;
2222
+ });
2223
+ throw error;
2224
+ }
2153
2225
  }
2154
2226
  async function forwardClientToCodegraph(input, childInput, pendingResponses, setDefaultResponseMode) {
2155
2227
  for await (const message of readStdioJsonRpcMessages(input)) {
@@ -2173,7 +2245,7 @@ async function forwardClientToCodegraph(input, childInput, pendingResponses, set
2173
2245
  async function forwardCodegraphToClient(childOutput, output, pendingResponses, defaultResponseMode) {
2174
2246
  for await (const message of readStdioJsonRpcMessages(childOutput)) {
2175
2247
  if (message.kind === "parse_error") {
2176
- writeStdioJsonRpcResponse(output, errorResponse(null, -32700, "Parse error", message.message), defaultResponseMode());
2248
+ await writeStdioJsonRpcResponse(output, errorResponse(null, -32700, "Parse error", message.message), defaultResponseMode());
2177
2249
  continue;
2178
2250
  }
2179
2251
  const key = responseModeKey(message.payload);
@@ -2181,7 +2253,7 @@ async function forwardCodegraphToClient(childOutput, output, pendingResponses, d
2181
2253
  const responseMode = pendingResponse?.responseMode ?? defaultResponseMode();
2182
2254
  if (key !== null)
2183
2255
  pendingResponses.delete(key);
2184
- writeStdioJsonRpcResponse(output, clarifyCodegraphResponse(message.payload, pendingResponse), responseMode);
2256
+ await writeStdioJsonRpcResponse(output, clarifyCodegraphResponse(message.payload, pendingResponse), responseMode);
2185
2257
  }
2186
2258
  }
2187
2259
  function responseModeKey(payload) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-codegraph",
3
- "version": "4.18.1",
3
+ "version": "4.18.2",
4
4
  "description": "Codex plugin MCP wrapper for CodeGraph.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -63,18 +63,30 @@ export async function runBridgedCodegraphProcess(
63
63
  resolveExit(signal === null ? 0 : 1);
64
64
  });
65
65
  });
66
- const bridgeDone = Promise.all([
67
- forwardClientToCodegraph(options.input, childInput, pendingResponses, (mode) => {
68
- defaultResponseMode = mode;
69
- }),
70
- forwardCodegraphToClient(childOutput, options.output, pendingResponses, () => defaultResponseMode),
71
- ]);
66
+ const clientForwardingDone = forwardClientToCodegraph(options.input, childInput, pendingResponses, (mode) => {
67
+ defaultResponseMode = mode;
68
+ });
69
+ const responseForwardingDone = forwardCodegraphToClient(
70
+ childOutput,
71
+ options.output,
72
+ pendingResponses,
73
+ () => defaultResponseMode,
74
+ );
75
+ const bridgeDone = Promise.all([clientForwardingDone, responseForwardingDone]);
76
+ const childAndResponsesDone = Promise.all([childExit, responseForwardingDone]).then(([exitCode]) => exitCode);
72
77
  const destroyChildPipes = (): void => {
73
78
  childInput.destroy();
74
79
  childOutput.destroy();
75
80
  };
76
81
  void childExit.then(destroyChildPipes, destroyChildPipes);
77
- return Promise.race([childExit, bridgeDone.then(() => childExit)]);
82
+ try {
83
+ return await Promise.race([childAndResponsesDone, bridgeDone.then(() => childExit)]);
84
+ } catch (error) {
85
+ destroyChildPipes();
86
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
87
+ await childExit.catch(() => undefined);
88
+ throw error;
89
+ }
78
90
  }
79
91
 
80
92
  async function forwardClientToCodegraph(
@@ -110,14 +122,14 @@ async function forwardCodegraphToClient(
110
122
  ): Promise<void> {
111
123
  for await (const message of readStdioJsonRpcMessages(childOutput)) {
112
124
  if (message.kind === "parse_error") {
113
- writeStdioJsonRpcResponse(output, errorResponse(null, -32700, "Parse error", message.message), defaultResponseMode());
125
+ await writeStdioJsonRpcResponse(output, errorResponse(null, -32700, "Parse error", message.message), defaultResponseMode());
114
126
  continue;
115
127
  }
116
128
  const key = responseModeKey(message.payload);
117
129
  const pendingResponse = key === null ? undefined : pendingResponses.get(key);
118
130
  const responseMode = pendingResponse?.responseMode ?? defaultResponseMode();
119
131
  if (key !== null) pendingResponses.delete(key);
120
- writeStdioJsonRpcResponse(output, clarifyCodegraphResponse(message.payload, pendingResponse), responseMode);
132
+ await writeStdioJsonRpcResponse(output, clarifyCodegraphResponse(message.payload, pendingResponse), responseMode);
121
133
  }
122
134
  }
123
135
 
@@ -49,6 +49,41 @@ export function writeFakeContractCodegraph(filePath: string): void {
49
49
  chmodSync(filePath, 0o755);
50
50
  }
51
51
 
52
+ export function writeFakeHeldOpenCodegraph(filePath: string): void {
53
+ writeFileSync(
54
+ filePath,
55
+ [
56
+ "#!/usr/bin/env node",
57
+ "const fs = require('node:fs');",
58
+ "const readline = require('node:readline');",
59
+ "fs.writeFileSync(process.env.CODEGRAPH_FAKE_LOG, String(process.pid));",
60
+ "const rl = readline.createInterface({ input: process.stdin });",
61
+ "rl.once('line', (line) => {",
62
+ " const request = JSON.parse(line);",
63
+ " process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { ok: true } }) + '\\n');",
64
+ "});",
65
+ "setInterval(() => {}, 1000);",
66
+ ].join("\n"),
67
+ );
68
+ chmodSync(filePath, 0o755);
69
+ }
70
+
71
+ export function writeFakeExitingCodegraph(filePath: string): void {
72
+ writeFileSync(
73
+ filePath,
74
+ [
75
+ "#!/usr/bin/env node",
76
+ "const readline = require('node:readline');",
77
+ "const rl = readline.createInterface({ input: process.stdin });",
78
+ "rl.once('line', (line) => {",
79
+ " const request = JSON.parse(line);",
80
+ " process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { ok: true } }) + '\\n', () => process.exit(0));",
81
+ "});",
82
+ ].join("\n"),
83
+ );
84
+ chmodSync(filePath, 0o755);
85
+ }
86
+
52
87
  export function frameMcpRequest(request: {
53
88
  readonly id: number;
54
89
  readonly method: string;
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { PassThrough, Writable } from "node:stream";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import { runCodegraphServe } from "../src/serve.ts";
8
+ import {
9
+ frameMcpRequest,
10
+ writeFakeExitingCodegraph,
11
+ } from "./mcp-bridge-fixtures.ts";
12
+
13
+ const componentRoot = realpathSync(
14
+ fileURLToPath(new URL("..", import.meta.url)),
15
+ );
16
+
17
+ describe("runCodegraphServe MCP bridge lifecycle", () => {
18
+ it("#given an exiting Codegraph child and delayed closed parent output #when the final response write fails #then the output error is preserved", async () => {
19
+ const tempRoot = mkdtempSync(
20
+ join(componentRoot, ".tmp-codegraph-exit-output-failure-"),
21
+ );
22
+ const projectRoot = join(tempRoot, "project");
23
+ const fakeCodegraph = join(tempRoot, "codegraph-exiting.cjs");
24
+ const input = new PassThrough();
25
+ const outputError = Object.assign(
26
+ new Error("parent output closed after child exit"),
27
+ { code: "EPIPE" },
28
+ );
29
+ const writeStarted = Promise.withResolvers<void>();
30
+ const output = new Writable({
31
+ write(_chunk, _encoding, callback) {
32
+ writeStarted.resolve();
33
+ setTimeout(() => callback(outputError), 150);
34
+ },
35
+ });
36
+
37
+ try {
38
+ mkdirSync(projectRoot, { recursive: true });
39
+ writeFakeExitingCodegraph(fakeCodegraph);
40
+ const run = runCodegraphServe({
41
+ cwd: tempRoot,
42
+ env: {
43
+ CODEGRAPH_ALLOW_UNSAFE_NODE: "1",
44
+ OMO_CODEGRAPH_BIN: fakeCodegraph,
45
+ OMO_CODEGRAPH_PROJECT_CWD: projectRoot,
46
+ PATH: process.env["PATH"],
47
+ },
48
+ nodeVersion: "22.14.0",
49
+ buildEnv: () => ({}),
50
+ resolve: () => ({
51
+ argsPrefix: [],
52
+ command: fakeCodegraph,
53
+ exists: true,
54
+ source: "env",
55
+ }),
56
+ stderr: { write: () => undefined },
57
+ stdin: input,
58
+ stdout: output,
59
+ });
60
+
61
+ input.end(frameMcpRequest({ id: 1, method: "tools/list", params: {} }));
62
+
63
+ await writeStarted.promise;
64
+ await expect(run).rejects.toBe(outputError);
65
+ } finally {
66
+ rmSync(tempRoot, { recursive: true, force: true });
67
+ }
68
+ });
69
+ });
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { PassThrough } from "node:stream";
4
+ import { PassThrough, Writable } from "node:stream";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  import { runCodegraphServe } from "../src/serve.ts";
@@ -14,6 +14,7 @@ import {
14
14
  recordValue,
15
15
  stringProperty,
16
16
  writeFakeContractCodegraph,
17
+ writeFakeHeldOpenCodegraph,
17
18
  writeFakeNewlineCodegraph,
18
19
  } from "./mcp-bridge-fixtures.ts";
19
20
 
@@ -190,4 +191,59 @@ describe("runCodegraphServe MCP protocol bridge", () => {
190
191
  rmSync(tempRoot, { recursive: true, force: true });
191
192
  }
192
193
  });
194
+
195
+ it("#given a held-open Codegraph child and closed parent output #when response forwarding fails #then the child is terminated before rejection", async () => {
196
+ const tempRoot = mkdtempSync(join(componentRoot, ".tmp-codegraph-output-failure-"));
197
+ const projectRoot = join(tempRoot, "project");
198
+ const fakeCodegraph = join(tempRoot, "codegraph-held-open.cjs");
199
+ const childPidFile = join(tempRoot, "child.pid");
200
+ const input = new PassThrough();
201
+ const outputError = Object.assign(new Error("parent output closed"), { code: "EPIPE" });
202
+ const output = new Writable({
203
+ write(_chunk, _encoding, callback) {
204
+ callback(outputError);
205
+ },
206
+ });
207
+ let childPid: number | undefined;
208
+
209
+ try {
210
+ mkdirSync(projectRoot, { recursive: true });
211
+ writeFakeHeldOpenCodegraph(fakeCodegraph);
212
+ const run = runCodegraphServe({
213
+ cwd: tempRoot,
214
+ env: {
215
+ CODEGRAPH_ALLOW_UNSAFE_NODE: "1",
216
+ CODEGRAPH_FAKE_LOG: childPidFile,
217
+ OMO_CODEGRAPH_BIN: fakeCodegraph,
218
+ OMO_CODEGRAPH_PROJECT_CWD: projectRoot,
219
+ PATH: process.env["PATH"],
220
+ },
221
+ nodeVersion: "22.14.0",
222
+ buildEnv: () => ({}),
223
+ resolve: () => ({ argsPrefix: [], command: fakeCodegraph, exists: true, source: "env" }),
224
+ stderr: { write: () => undefined },
225
+ stdin: input,
226
+ stdout: output,
227
+ });
228
+
229
+ input.end(frameMcpRequest({ id: 1, method: "tools/list", params: {} }));
230
+
231
+ await expect(run).rejects.toBe(outputError);
232
+ childPid = Number(readFileSync(childPidFile, "utf8"));
233
+ expect(isProcessAlive(childPid)).toBe(false);
234
+ } finally {
235
+ if (childPid !== undefined && isProcessAlive(childPid)) process.kill(childPid, "SIGKILL");
236
+ rmSync(tempRoot, { recursive: true, force: true });
237
+ }
238
+ });
193
239
  });
240
+
241
+ function isProcessAlive(pid: number): boolean {
242
+ try {
243
+ process.kill(pid, 0);
244
+ return true;
245
+ } catch (error) {
246
+ if (error instanceof Error && "code" in error && error.code === "ESRCH") return false;
247
+ throw error;
248
+ }
249
+ }
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 4.18.1) Checking Comments"
11
+ "statusMessage": "(OmO 4.18.2) Checking Comments"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-comment-checker",
3
- "version": "4.18.1",
3
+ "version": "4.18.2",
4
4
  "description": "Codex plugin that runs comment-checker after edit-like PostToolUse hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 4.18.1) Recommending Git Bash MCP"
11
+ "statusMessage": "(OmO 4.18.2) Recommending Git Bash MCP"
12
12
  }
13
13
  ]
14
14
  }
@@ -20,7 +20,7 @@
20
20
  "type": "command",
21
21
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
22
22
  "timeout": 5,
23
- "statusMessage": "(OmO 4.18.1) Resetting Git Bash MCP Reminder"
23
+ "statusMessage": "(OmO 4.18.2) Resetting Git Bash MCP Reminder"
24
24
  }
25
25
  ]
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-git-bash-hook",
3
- "version": "4.18.1",
3
+ "version": "4.18.2",
4
4
  "description": "Codex hook component that reminds Windows sessions to prefer the OMO git_bash MCP.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 4.18.1) Verifying LazyCodex Executor Evidence"
11
+ "statusMessage": "(OmO 4.18.2) Verifying LazyCodex Executor Evidence"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lazycodex-executor-verify",
3
- "version": "4.18.1",
3
+ "version": "4.18.2",
4
4
  "description": "Codex SubagentStop evidence verifier for LazyCodex executor completions.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -3,7 +3,8 @@
3
3
  "lsp": {
4
4
  "command": "node",
5
5
  "args": ["../../../../lsp-daemon/dist/cli.js", "mcp"],
6
- "cwd": "."
6
+ "cwd": ".",
7
+ "startup_timeout_sec": 10
7
8
  }
8
9
  }
9
10
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "4.18.1",
4
- "inputDigest": "sha256:ce104d23ca60e4a61242e99e54232a9fc82078b6471381511abc3187f8489842",
3
+ "version": "4.18.2",
4
+ "inputDigest": "sha256:5f242410eb12245b588135e29137ee9e7a0fdb2346469bdfdb75727b2abaf248",
5
5
  "outputs": [
6
6
  {
7
7
  "path": "cli.d.ts",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "cli.js",
12
- "sha256": "84e00d94acd6c757613daaf8dde1e9425861ab6aa16923daaebd207934bc8ca1"
12
+ "sha256": "e92c3fee5795bf44de9504b0e85c2be208c78a31e966ec9a7a807d621431c74a"
13
13
  },
14
14
  {
15
15
  "path": "codex-hook-cli.d.ts",