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.
- package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +39 -16
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +111 -28
- package/bin/AGENTS.md +33 -0
- package/dist/cli/index.js +57 -18
- package/dist/cli-node/index.js +57 -18
- package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.d.ts +6 -0
- package/dist/hooks/category-skill-reminder/hook.d.ts +9 -1
- package/dist/hooks/comment-checker/hook.d.ts +8 -1
- package/dist/hooks/todo-continuation-enforcer/types.d.ts +3 -0
- package/dist/index.js +560 -333
- package/dist/plugin/messages-transform.d.ts +1 -0
- package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +2 -0
- package/dist/tui.js +43 -4
- package/package.json +13 -13
- package/packages/git-bash-mcp/dist/cli.js +81 -19
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +4 -4
- package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +118 -1
- package/packages/lsp-core/src/lsp/directory-diagnostics.ts +1 -1
- package/packages/lsp-daemon/dist/cli.js +262 -63
- package/packages/lsp-daemon/dist/client.js +194 -50
- package/packages/lsp-daemon/dist/daemon-client.d.ts +2 -2
- package/packages/lsp-daemon/dist/daemon-client.js +26 -2
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +5 -4
- package/packages/lsp-daemon/dist/ensure-daemon.js +79 -18
- package/packages/lsp-daemon/dist/index.js +262 -63
- package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
- package/packages/lsp-daemon/dist/proxy.js +79 -23
- package/packages/lsp-tools-mcp/dist/cli.js +82 -20
- package/packages/lsp-tools-mcp/dist/mcp.js +82 -20
- package/packages/lsp-tools-mcp/dist/tools.js +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/.mcp.json +2 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +100 -28
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +100 -28
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +21 -9
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +35 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +69 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +57 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/.mcp.json +2 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -31
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +1 -0
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -3
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/README.md +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +3 -3
- package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/src/codex-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +15 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +20 -0
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +50 -33
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +50 -33
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +50 -33
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +5 -6
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +5 -6
- package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +2 -2
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +10 -2
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +7 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +35 -4
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +1 -1
|
@@ -3006,7 +3006,7 @@ async function disposeDefaultLspManager() {
|
|
|
3006
3006
|
}
|
|
3007
3007
|
}
|
|
3008
3008
|
// src/daemon-client.ts
|
|
3009
|
-
import { connect
|
|
3009
|
+
import { connect } from "node:net";
|
|
3010
3010
|
import { homedir as homedir3 } from "node:os";
|
|
3011
3011
|
import { join as join7 } from "node:path";
|
|
3012
3012
|
|
|
@@ -3187,7 +3187,7 @@ function isPlainRecord(value) {
|
|
|
3187
3187
|
// src/ensure-daemon.ts
|
|
3188
3188
|
import { spawn as spawn2 } from "node:child_process";
|
|
3189
3189
|
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2 } from "node:fs";
|
|
3190
|
-
import {
|
|
3190
|
+
import { Socket } from "node:net";
|
|
3191
3191
|
import { dirname as dirname6 } from "node:path";
|
|
3192
3192
|
import { execPath } from "node:process";
|
|
3193
3193
|
|
|
@@ -3594,42 +3594,48 @@ class DaemonUnreachableError extends Error {
|
|
|
3594
3594
|
async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
|
|
3595
3595
|
const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
3596
3596
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3597
|
-
|
|
3597
|
+
const signal = options.signal;
|
|
3598
|
+
throwIfAborted(signal);
|
|
3599
|
+
if (await awaitWithSignal2(deps.probe(paths, signal), signal))
|
|
3598
3600
|
return;
|
|
3601
|
+
throwIfAborted(signal);
|
|
3599
3602
|
deps.spawnDaemon(paths);
|
|
3600
|
-
await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs);
|
|
3603
|
+
await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
|
|
3601
3604
|
}
|
|
3602
|
-
async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs) {
|
|
3605
|
+
async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
|
|
3603
3606
|
const deadline = deps.now() + readyTimeoutMs;
|
|
3604
3607
|
for (;; ) {
|
|
3605
|
-
|
|
3608
|
+
throwIfAborted(signal);
|
|
3609
|
+
if (await awaitWithSignal2(deps.probe(paths, signal), signal))
|
|
3606
3610
|
return;
|
|
3607
3611
|
if (deps.now() >= deadline)
|
|
3608
3612
|
throw new DaemonUnreachableError(paths.socket);
|
|
3609
|
-
await deps.sleep(pollIntervalMs);
|
|
3613
|
+
await awaitWithSignal2(deps.sleep(pollIntervalMs, signal), signal);
|
|
3610
3614
|
}
|
|
3611
3615
|
}
|
|
3612
|
-
async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
3616
|
+
async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS, signal) {
|
|
3613
3617
|
const token = readAuthToken(paths);
|
|
3614
3618
|
if (!token)
|
|
3615
3619
|
return false;
|
|
3616
|
-
return await pingDaemon(paths, token, timeoutMs) !== null;
|
|
3620
|
+
return await pingDaemon(paths, token, timeoutMs, signal) !== null;
|
|
3617
3621
|
}
|
|
3618
|
-
function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
3622
|
+
function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
|
|
3619
3623
|
return new Promise((resolve7) => {
|
|
3620
|
-
const socket =
|
|
3624
|
+
const socket = new Socket;
|
|
3621
3625
|
let settled = false;
|
|
3626
|
+
let timer;
|
|
3622
3627
|
const finish = (value) => {
|
|
3623
3628
|
if (settled)
|
|
3624
3629
|
return;
|
|
3625
3630
|
settled = true;
|
|
3631
|
+
if (timer !== undefined)
|
|
3632
|
+
clearTimeout(timer);
|
|
3633
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3626
3634
|
socket.destroy();
|
|
3627
3635
|
resolve7(value);
|
|
3628
3636
|
};
|
|
3629
|
-
const
|
|
3630
|
-
timer.unref?.();
|
|
3637
|
+
const onAbort = () => finish(null);
|
|
3631
3638
|
const decoder = createLineDecoder((message) => {
|
|
3632
|
-
clearTimeout(timer);
|
|
3633
3639
|
finish(parsePingResponse(message));
|
|
3634
3640
|
});
|
|
3635
3641
|
socket.once("connect", () => {
|
|
@@ -3637,9 +3643,16 @@ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
|
3637
3643
|
});
|
|
3638
3644
|
socket.on("data", (chunk) => decoder.push(chunk));
|
|
3639
3645
|
socket.once("error", () => {
|
|
3640
|
-
clearTimeout(timer);
|
|
3641
3646
|
finish(null);
|
|
3642
3647
|
});
|
|
3648
|
+
timer = setTimeout(() => finish(null), timeoutMs);
|
|
3649
|
+
timer.unref?.();
|
|
3650
|
+
if (signal?.aborted) {
|
|
3651
|
+
onAbort();
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3655
|
+
socket.connect(paths.socket);
|
|
3643
3656
|
});
|
|
3644
3657
|
}
|
|
3645
3658
|
function spawnDaemonProcess(paths) {
|
|
@@ -3657,14 +3670,62 @@ function spawnDaemonProcess(paths) {
|
|
|
3657
3670
|
}
|
|
3658
3671
|
function defaultEnsureDaemonDeps() {
|
|
3659
3672
|
return {
|
|
3660
|
-
probe: (paths) => probeDaemon(paths),
|
|
3673
|
+
probe: (paths, signal) => probeDaemon(paths, PROBE_TIMEOUT_MS, signal),
|
|
3661
3674
|
spawnDaemon: (paths) => spawnDaemonProcess(paths),
|
|
3662
|
-
sleep: (ms) =>
|
|
3663
|
-
setTimeout(resolve7, ms);
|
|
3664
|
-
}),
|
|
3675
|
+
sleep: (ms, signal) => sleepWithSignal(ms, signal),
|
|
3665
3676
|
now: () => Date.now()
|
|
3666
3677
|
};
|
|
3667
3678
|
}
|
|
3679
|
+
function sleepWithSignal(ms, signal) {
|
|
3680
|
+
return new Promise((resolve7) => {
|
|
3681
|
+
let settled = false;
|
|
3682
|
+
const finish = () => {
|
|
3683
|
+
if (settled)
|
|
3684
|
+
return;
|
|
3685
|
+
settled = true;
|
|
3686
|
+
clearTimeout(timer);
|
|
3687
|
+
signal?.removeEventListener("abort", finish);
|
|
3688
|
+
resolve7();
|
|
3689
|
+
};
|
|
3690
|
+
const timer = setTimeout(finish, ms);
|
|
3691
|
+
if (signal?.aborted) {
|
|
3692
|
+
finish();
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
function awaitWithSignal2(promise, signal) {
|
|
3699
|
+
if (!signal)
|
|
3700
|
+
return promise;
|
|
3701
|
+
if (signal.aborted)
|
|
3702
|
+
return Promise.reject(abortError3(signal));
|
|
3703
|
+
return new Promise((resolve7, reject) => {
|
|
3704
|
+
let settled = false;
|
|
3705
|
+
const finish = (run) => {
|
|
3706
|
+
if (settled)
|
|
3707
|
+
return;
|
|
3708
|
+
settled = true;
|
|
3709
|
+
signal.removeEventListener("abort", onAbort);
|
|
3710
|
+
run();
|
|
3711
|
+
};
|
|
3712
|
+
const onAbort = () => finish(() => reject(abortError3(signal)));
|
|
3713
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3714
|
+
promise.then((value) => finish(() => resolve7(value)), (error) => finish(() => reject(error)));
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
function throwIfAborted(signal) {
|
|
3718
|
+
if (signal?.aborted)
|
|
3719
|
+
throw abortError3(signal);
|
|
3720
|
+
}
|
|
3721
|
+
function abortError3(signal) {
|
|
3722
|
+
const reason = signal.reason;
|
|
3723
|
+
if (reason instanceof Error)
|
|
3724
|
+
return reason;
|
|
3725
|
+
const error = new Error(typeof reason === "string" ? reason : "daemon startup cancelled");
|
|
3726
|
+
error.name = "AbortError";
|
|
3727
|
+
return error;
|
|
3728
|
+
}
|
|
3668
3729
|
function parsePingResponse(message) {
|
|
3669
3730
|
if (!message || typeof message !== "object" || Array.isArray(message))
|
|
3670
3731
|
return null;
|
|
@@ -3704,6 +3765,9 @@ function errorResponse(id, code, message, data) {
|
|
|
3704
3765
|
function jsonRpcId2(value) {
|
|
3705
3766
|
return typeof value === "string" || typeof value === "number" || value === null ? value : null;
|
|
3706
3767
|
}
|
|
3768
|
+
function messageFromError(error) {
|
|
3769
|
+
return error instanceof Error ? error.message : String(error);
|
|
3770
|
+
}
|
|
3707
3771
|
// ../mcp-stdio-core/src/transport.ts
|
|
3708
3772
|
var HEADER_SEPARATOR2 = Buffer.from(`\r
|
|
3709
3773
|
\r
|
|
@@ -3726,16 +3790,45 @@ async function* readStdioJsonRpcMessages(input) {
|
|
|
3726
3790
|
yield parseJsonPayload(trailing, "line");
|
|
3727
3791
|
}
|
|
3728
3792
|
}
|
|
3729
|
-
function writeStdioJsonRpcResponse(output, response, responseMode) {
|
|
3793
|
+
async function writeStdioJsonRpcResponse(output, response, responseMode) {
|
|
3730
3794
|
const body = JSON.stringify(response);
|
|
3731
|
-
|
|
3732
|
-
output.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r
|
|
3795
|
+
const payload = responseMode === "framed" ? `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
|
|
3733
3796
|
\r
|
|
3734
|
-
${body}`
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3797
|
+
${body}` : `${body}
|
|
3798
|
+
`;
|
|
3799
|
+
await writeChunk(output, payload);
|
|
3800
|
+
}
|
|
3801
|
+
function writeChunk(output, chunk) {
|
|
3802
|
+
return new Promise((resolve7, reject) => {
|
|
3803
|
+
let settled = false;
|
|
3804
|
+
const onError = (error) => {
|
|
3805
|
+
if (settled)
|
|
3806
|
+
return;
|
|
3807
|
+
settled = true;
|
|
3808
|
+
reject(error);
|
|
3809
|
+
};
|
|
3810
|
+
output.once("error", onError);
|
|
3811
|
+
try {
|
|
3812
|
+
output.write(chunk, (error) => {
|
|
3813
|
+
if (settled)
|
|
3814
|
+
return;
|
|
3815
|
+
settled = true;
|
|
3816
|
+
if (error) {
|
|
3817
|
+
queueMicrotask(() => output.removeListener("error", onError));
|
|
3818
|
+
reject(error);
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
output.removeListener("error", onError);
|
|
3822
|
+
resolve7();
|
|
3823
|
+
});
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
output.removeListener("error", onError);
|
|
3826
|
+
if (settled)
|
|
3827
|
+
return;
|
|
3828
|
+
settled = true;
|
|
3829
|
+
reject(error);
|
|
3830
|
+
}
|
|
3831
|
+
});
|
|
3739
3832
|
}
|
|
3740
3833
|
function readNextMessage(buffer) {
|
|
3741
3834
|
if (buffer.length === 0)
|
|
@@ -3831,40 +3924,70 @@ async function runJsonRpcStdioServer(config) {
|
|
|
3831
3924
|
break;
|
|
3832
3925
|
idleTimer.arm();
|
|
3833
3926
|
if (message.kind === "parse_error") {
|
|
3834
|
-
handleParseError(message, config, log)
|
|
3927
|
+
if (!await handleParseError(message, config, log))
|
|
3928
|
+
break;
|
|
3835
3929
|
continue;
|
|
3836
3930
|
}
|
|
3837
|
-
await handleRequest(message, config, log)
|
|
3931
|
+
if (!await handleRequest(message, config, log))
|
|
3932
|
+
break;
|
|
3838
3933
|
}
|
|
3839
3934
|
} finally {
|
|
3840
3935
|
idleTimer.clear();
|
|
3841
3936
|
log("stdio_stopped");
|
|
3842
3937
|
}
|
|
3843
3938
|
}
|
|
3844
|
-
function handleParseError(message, config, log) {
|
|
3939
|
+
async function handleParseError(message, config, log) {
|
|
3845
3940
|
log("parse_error", { message: message.message });
|
|
3846
3941
|
const response = config.parseErrorResponse?.(message.message) ?? errorResponse(null, -32700, "Parse error", message.message);
|
|
3847
|
-
if (response
|
|
3848
|
-
|
|
3849
|
-
|
|
3942
|
+
if (response === undefined)
|
|
3943
|
+
return true;
|
|
3944
|
+
return writeResponse(response, {
|
|
3945
|
+
output: config.output,
|
|
3946
|
+
responseMode: message.responseMode,
|
|
3947
|
+
log
|
|
3948
|
+
});
|
|
3850
3949
|
}
|
|
3851
3950
|
async function handleRequest(message, config, log) {
|
|
3852
3951
|
const parsed = message.payload;
|
|
3853
3952
|
const id = isPlainRecord(parsed) ? jsonRpcId2(parsed["id"]) : null;
|
|
3854
3953
|
const method = isPlainRecord(parsed) && typeof parsed["method"] === "string" ? parsed["method"] : null;
|
|
3855
3954
|
log("request", { id: id === null ? null : String(id), method });
|
|
3955
|
+
let response;
|
|
3856
3956
|
try {
|
|
3857
|
-
|
|
3858
|
-
if (response === undefined)
|
|
3859
|
-
return;
|
|
3860
|
-
writeStdioJsonRpcResponse(config.output, response, message.responseMode);
|
|
3861
|
-
log("response", { id: String(response.id), method, is_error: response.error !== undefined });
|
|
3957
|
+
response = await config.handler(parsed, config.handlerOptions);
|
|
3862
3958
|
} catch (error) {
|
|
3863
3959
|
if (config.onHandlerError === undefined)
|
|
3864
3960
|
throw error;
|
|
3865
3961
|
config.onHandlerError(error);
|
|
3962
|
+
return true;
|
|
3963
|
+
}
|
|
3964
|
+
if (response === undefined)
|
|
3965
|
+
return true;
|
|
3966
|
+
if (!await writeResponse(response, {
|
|
3967
|
+
output: config.output,
|
|
3968
|
+
responseMode: message.responseMode,
|
|
3969
|
+
log
|
|
3970
|
+
}))
|
|
3971
|
+
return false;
|
|
3972
|
+
log("response", { id: String(response.id), method, is_error: response.error !== undefined });
|
|
3973
|
+
return true;
|
|
3974
|
+
}
|
|
3975
|
+
async function writeResponse(response, context) {
|
|
3976
|
+
try {
|
|
3977
|
+
await writeStdioJsonRpcResponse(context.output, response, context.responseMode);
|
|
3978
|
+
return true;
|
|
3979
|
+
} catch (error) {
|
|
3980
|
+
if (!isTerminalOutputError(error))
|
|
3981
|
+
throw error;
|
|
3982
|
+
context.log("output_error", { message: messageFromError(error) });
|
|
3983
|
+
return false;
|
|
3866
3984
|
}
|
|
3867
3985
|
}
|
|
3986
|
+
function isTerminalOutputError(error) {
|
|
3987
|
+
if (!(error instanceof Error) || !("code" in error))
|
|
3988
|
+
return false;
|
|
3989
|
+
return error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED" || error.code === "ERR_STREAM_WRITE_AFTER_END";
|
|
3990
|
+
}
|
|
3868
3991
|
function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
|
|
3869
3992
|
let timer = null;
|
|
3870
3993
|
let isClosed = false;
|
|
@@ -4747,7 +4870,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
|
|
|
4747
4870
|
const fileErrors = [];
|
|
4748
4871
|
const maxConcurrency = Math.max(1, options.maxConcurrency ?? DIRECTORY_DIAGNOSTICS_MAX_CONCURRENCY);
|
|
4749
4872
|
options.signal?.throwIfAborted();
|
|
4750
|
-
const client = await manager.getClient(root, server2);
|
|
4873
|
+
const client = await manager.getClient(root, server2, options.signal);
|
|
4751
4874
|
try {
|
|
4752
4875
|
let nextIndex = 0;
|
|
4753
4876
|
const workers = Array.from({ length: Math.min(maxConcurrency, filesToProcess.length) }, async () => {
|
|
@@ -5617,14 +5740,14 @@ class DaemonRequestCancelledError extends DaemonRequestError {
|
|
|
5617
5740
|
async function callToolViaDaemon(name, args, options) {
|
|
5618
5741
|
const context = requireContext(options.context);
|
|
5619
5742
|
const paths = options.paths ?? daemonPaths();
|
|
5620
|
-
const ensure = options.ensure ?? ensureDaemonRunning;
|
|
5743
|
+
const ensure = options.ensure ?? ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
|
|
5621
5744
|
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
5622
5745
|
const requestArgs = withContext(args, context);
|
|
5623
5746
|
let lastError;
|
|
5624
5747
|
let authRefreshUsed = false;
|
|
5625
5748
|
for (let attempt = 0;attempt < 3; attempt += 1) {
|
|
5626
5749
|
try {
|
|
5627
|
-
await
|
|
5750
|
+
await ensureDaemonAvailable(paths, ensure, options.signal);
|
|
5628
5751
|
const token = readAuthToken(paths);
|
|
5629
5752
|
if (!token)
|
|
5630
5753
|
throw new DaemonRequestError("daemon auth token missing", false);
|
|
@@ -5666,6 +5789,27 @@ function requireContext(context) {
|
|
|
5666
5789
|
function withContext(args, context) {
|
|
5667
5790
|
return { ...args, [CONTEXT_KEY]: context };
|
|
5668
5791
|
}
|
|
5792
|
+
function ensureDaemonAvailable(paths, ensure, signal) {
|
|
5793
|
+
if (!signal)
|
|
5794
|
+
return ensure(paths);
|
|
5795
|
+
return new Promise((resolve9, reject) => {
|
|
5796
|
+
let settled = false;
|
|
5797
|
+
const finish = (run) => {
|
|
5798
|
+
if (settled)
|
|
5799
|
+
return;
|
|
5800
|
+
settled = true;
|
|
5801
|
+
signal.removeEventListener("abort", onAbort);
|
|
5802
|
+
run();
|
|
5803
|
+
};
|
|
5804
|
+
const onAbort = () => finish(() => reject(new DaemonRequestCancelledError(false)));
|
|
5805
|
+
if (signal.aborted) {
|
|
5806
|
+
onAbort();
|
|
5807
|
+
return;
|
|
5808
|
+
}
|
|
5809
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5810
|
+
Promise.resolve().then(() => ensure(paths, signal)).then(() => finish(() => resolve9()), (error) => finish(() => reject(signal.aborted ? new DaemonRequestCancelledError(false) : error)));
|
|
5811
|
+
});
|
|
5812
|
+
}
|
|
5669
5813
|
function daemonUnreachableResult(paths, error) {
|
|
5670
5814
|
const text2 = [
|
|
5671
5815
|
`LSP daemon unreachable: ${errorText(error)}.`,
|
|
@@ -5679,7 +5823,7 @@ function daemonUnreachableResult(paths, error) {
|
|
|
5679
5823
|
}
|
|
5680
5824
|
function sendToolCall(paths, token, name, args, options) {
|
|
5681
5825
|
return new Promise((resolve9, reject) => {
|
|
5682
|
-
const socket =
|
|
5826
|
+
const socket = connect(paths.socket);
|
|
5683
5827
|
const requestId = allocateProxyRequestId();
|
|
5684
5828
|
let settled = false;
|
|
5685
5829
|
let requestWritten = false;
|
|
@@ -5781,38 +5925,93 @@ function errorText(error) {
|
|
|
5781
5925
|
// src/proxy.ts
|
|
5782
5926
|
import { existsSync as existsSync11, realpathSync as realpathSync4 } from "node:fs";
|
|
5783
5927
|
import { basename as basename3, delimiter as delimiter4, dirname as dirname9, isAbsolute as isAbsolute4 } from "node:path";
|
|
5928
|
+
var DEFAULT_STARTUP_TIMEOUT_MS = 1e4;
|
|
5784
5929
|
async function runMcpStdioProxy(options = {}) {
|
|
5785
5930
|
const input = options.input ?? process.stdin;
|
|
5786
5931
|
const output = options.output ?? process.stdout;
|
|
5787
|
-
const
|
|
5788
|
-
const
|
|
5789
|
-
const
|
|
5790
|
-
const
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5932
|
+
const stderr = options.stderr ?? process.stderr;
|
|
5933
|
+
const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
5934
|
+
const lifecycleController = new AbortController;
|
|
5935
|
+
const abortFromInput = () => lifecycleController.abort();
|
|
5936
|
+
let startupExpired = false;
|
|
5937
|
+
let startupTimer;
|
|
5938
|
+
const clearStartupWatchdog = () => {
|
|
5939
|
+
if (startupTimer === undefined)
|
|
5940
|
+
return;
|
|
5941
|
+
clearTimeout(startupTimer);
|
|
5942
|
+
startupTimer = undefined;
|
|
5795
5943
|
};
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
onHandlerError: (error) => {
|
|
5805
|
-
process.stderr.write(`[lsp-daemon] proxy error: ${error instanceof Error ? error.message : String(error)}
|
|
5944
|
+
input.once("end", abortFromInput);
|
|
5945
|
+
input.once("close", abortFromInput);
|
|
5946
|
+
if (startupTimeoutMs > 0) {
|
|
5947
|
+
startupTimer = setTimeout(() => {
|
|
5948
|
+
startupExpired = true;
|
|
5949
|
+
input.destroy();
|
|
5950
|
+
try {
|
|
5951
|
+
stderr.write(`[lsp-daemon] no MCP request received within ${startupTimeoutMs}ms; exiting
|
|
5806
5952
|
`);
|
|
5807
|
-
|
|
5808
|
-
|
|
5953
|
+
} catch (error) {
|
|
5954
|
+
if (stderr !== process.stderr) {
|
|
5955
|
+
process.stderr.write(`[lsp-daemon] startup diagnostic failed: ${error instanceof Error ? error.message : String(error)}
|
|
5956
|
+
`);
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
}, startupTimeoutMs);
|
|
5960
|
+
}
|
|
5961
|
+
try {
|
|
5962
|
+
const paths = options.paths ?? daemonPaths();
|
|
5963
|
+
const env = options.env ?? process.env;
|
|
5964
|
+
const cwd = options.cwd ?? inferOpenCodeProjectCwd(env["LSP_TOOLS_MCP_PROJECT_CONFIG"]);
|
|
5965
|
+
const contextEnv = cwd === undefined ? env : canonicalizeContextEnv(env);
|
|
5966
|
+
const contextInput = {
|
|
5967
|
+
env: contextEnv,
|
|
5968
|
+
...cwd === undefined ? {} : { cwd },
|
|
5969
|
+
...options.homeDir === undefined ? {} : { homeDir: options.homeDir }
|
|
5970
|
+
};
|
|
5971
|
+
const context = options.context ?? createStandaloneMcpRequestContext(contextInput);
|
|
5972
|
+
const callOptions = {
|
|
5973
|
+
paths,
|
|
5974
|
+
context,
|
|
5975
|
+
...options.ensure ? { ensure: options.ensure } : {},
|
|
5976
|
+
signal: lifecycleController.signal
|
|
5977
|
+
};
|
|
5978
|
+
await runJsonRpcStdioServer({
|
|
5979
|
+
input,
|
|
5980
|
+
output,
|
|
5981
|
+
idleTimeoutMs: 0,
|
|
5982
|
+
handler: (request, requestOptions) => {
|
|
5983
|
+
clearStartupWatchdog();
|
|
5984
|
+
return runWithRequestContext(context, () => handleProxyRequest(request, requestOptions));
|
|
5985
|
+
},
|
|
5986
|
+
handlerOptions: callOptions,
|
|
5987
|
+
onHandlerError: (error) => {
|
|
5988
|
+
stderr.write(`[lsp-daemon] proxy error: ${error instanceof Error ? error.message : String(error)}
|
|
5989
|
+
`);
|
|
5990
|
+
}
|
|
5991
|
+
});
|
|
5992
|
+
} catch (error) {
|
|
5993
|
+
if (!startupExpired || !isPrematureCloseError(error))
|
|
5994
|
+
throw error;
|
|
5995
|
+
} finally {
|
|
5996
|
+
if (startupTimer !== undefined)
|
|
5997
|
+
clearTimeout(startupTimer);
|
|
5998
|
+
input.removeListener("end", abortFromInput);
|
|
5999
|
+
input.removeListener("close", abortFromInput);
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
function isPrematureCloseError(error) {
|
|
6003
|
+
return error instanceof Error && Reflect.get(error, "code") === "ERR_STREAM_PREMATURE_CLOSE";
|
|
5809
6004
|
}
|
|
5810
6005
|
async function handleProxyRequest(parsed, callOptions) {
|
|
5811
6006
|
const toolCall = asToolCall(parsed);
|
|
5812
6007
|
if (!toolCall)
|
|
5813
6008
|
return handleLspMcpRequest(parsed);
|
|
5814
6009
|
const result = await callToolViaDaemon(toolCall.name, toolCall.args, callOptions);
|
|
5815
|
-
return successResponse(toolCall.id, {
|
|
6010
|
+
return successResponse(toolCall.id, {
|
|
6011
|
+
content: result.content,
|
|
6012
|
+
isError: result.isError ?? false,
|
|
6013
|
+
details: result.details
|
|
6014
|
+
});
|
|
5816
6015
|
}
|
|
5817
6016
|
function asToolCall(parsed) {
|
|
5818
6017
|
if (!isPlainRecord(parsed) || parsed["method"] !== "tools/call")
|
|
@@ -4,11 +4,13 @@ import { type DaemonPaths } from "./paths.js";
|
|
|
4
4
|
export interface ProxyOptions {
|
|
5
5
|
input?: Readable;
|
|
6
6
|
output?: Writable;
|
|
7
|
+
stderr?: Writable;
|
|
7
8
|
paths?: DaemonPaths;
|
|
8
9
|
context?: DaemonToolContext;
|
|
9
10
|
cwd?: string;
|
|
10
11
|
env?: Record<string, string | undefined>;
|
|
11
12
|
homeDir?: string;
|
|
12
13
|
ensure?: CallToolOptions["ensure"];
|
|
14
|
+
startupTimeoutMs?: number;
|
|
13
15
|
}
|
|
14
16
|
export declare function runMcpStdioProxy(options?: ProxyOptions): Promise<void>;
|
|
@@ -1,42 +1,98 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from "node:fs";
|
|
2
2
|
import { basename, delimiter, dirname, isAbsolute } from "node:path";
|
|
3
|
-
import { jsonRpcId, runJsonRpcStdioServer, successResponse } from "@oh-my-opencode/mcp-stdio-core";
|
|
4
|
-
import { isPlainRecord } from "@oh-my-opencode/mcp-stdio-core/record";
|
|
5
3
|
import { handleLspMcpRequest } from "@oh-my-opencode/lsp-core/mcp";
|
|
6
4
|
import { createStandaloneMcpRequestContext, runWithRequestContext } from "@oh-my-opencode/lsp-core/request-context";
|
|
5
|
+
import { jsonRpcId, runJsonRpcStdioServer, successResponse } from "@oh-my-opencode/mcp-stdio-core";
|
|
6
|
+
import { isPlainRecord } from "@oh-my-opencode/mcp-stdio-core/record";
|
|
7
7
|
import { callToolViaDaemon } from "./daemon-client.js";
|
|
8
8
|
import { daemonPaths } from "./paths.js";
|
|
9
|
+
const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
|
|
9
10
|
export async function runMcpStdioProxy(options = {}) {
|
|
10
11
|
const input = options.input ?? process.stdin;
|
|
11
12
|
const output = options.output ?? process.stdout;
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const stderr = options.stderr ?? process.stderr;
|
|
14
|
+
const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
15
|
+
const lifecycleController = new AbortController();
|
|
16
|
+
const abortFromInput = () => lifecycleController.abort();
|
|
17
|
+
let startupExpired = false;
|
|
18
|
+
let startupTimer;
|
|
19
|
+
const clearStartupWatchdog = () => {
|
|
20
|
+
if (startupTimer === undefined)
|
|
21
|
+
return;
|
|
22
|
+
clearTimeout(startupTimer);
|
|
23
|
+
startupTimer = undefined;
|
|
20
24
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
input.once("end", abortFromInput);
|
|
26
|
+
input.once("close", abortFromInput);
|
|
27
|
+
if (startupTimeoutMs > 0) {
|
|
28
|
+
startupTimer = setTimeout(() => {
|
|
29
|
+
startupExpired = true;
|
|
30
|
+
input.destroy();
|
|
31
|
+
try {
|
|
32
|
+
stderr.write(`[lsp-daemon] no MCP request received within ${startupTimeoutMs}ms; exiting\n`);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (stderr !== process.stderr) {
|
|
36
|
+
process.stderr.write(`[lsp-daemon] startup diagnostic failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, startupTimeoutMs);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const paths = options.paths ?? daemonPaths();
|
|
43
|
+
const env = options.env ?? process.env;
|
|
44
|
+
const cwd = options.cwd ?? inferOpenCodeProjectCwd(env["LSP_TOOLS_MCP_PROJECT_CONFIG"]);
|
|
45
|
+
const contextEnv = cwd === undefined ? env : canonicalizeContextEnv(env);
|
|
46
|
+
const contextInput = {
|
|
47
|
+
env: contextEnv,
|
|
48
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
49
|
+
...(options.homeDir === undefined ? {} : { homeDir: options.homeDir }),
|
|
50
|
+
};
|
|
51
|
+
const context = options.context ?? createStandaloneMcpRequestContext(contextInput);
|
|
52
|
+
const callOptions = {
|
|
53
|
+
paths,
|
|
54
|
+
context,
|
|
55
|
+
...(options.ensure ? { ensure: options.ensure } : {}),
|
|
56
|
+
signal: lifecycleController.signal,
|
|
57
|
+
};
|
|
58
|
+
await runJsonRpcStdioServer({
|
|
59
|
+
input,
|
|
60
|
+
output,
|
|
61
|
+
idleTimeoutMs: 0,
|
|
62
|
+
handler: (request, requestOptions) => {
|
|
63
|
+
clearStartupWatchdog();
|
|
64
|
+
return runWithRequestContext(context, () => handleProxyRequest(request, requestOptions));
|
|
65
|
+
},
|
|
66
|
+
handlerOptions: callOptions,
|
|
67
|
+
onHandlerError: (error) => {
|
|
68
|
+
stderr.write(`[lsp-daemon] proxy error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (!startupExpired || !isPrematureCloseError(error))
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
if (startupTimer !== undefined)
|
|
78
|
+
clearTimeout(startupTimer);
|
|
79
|
+
input.removeListener("end", abortFromInput);
|
|
80
|
+
input.removeListener("close", abortFromInput);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isPrematureCloseError(error) {
|
|
84
|
+
return error instanceof Error && Reflect.get(error, "code") === "ERR_STREAM_PREMATURE_CLOSE";
|
|
33
85
|
}
|
|
34
86
|
async function handleProxyRequest(parsed, callOptions) {
|
|
35
87
|
const toolCall = asToolCall(parsed);
|
|
36
88
|
if (!toolCall)
|
|
37
89
|
return handleLspMcpRequest(parsed);
|
|
38
90
|
const result = await callToolViaDaemon(toolCall.name, toolCall.args, callOptions);
|
|
39
|
-
return successResponse(toolCall.id, {
|
|
91
|
+
return successResponse(toolCall.id, {
|
|
92
|
+
content: result.content,
|
|
93
|
+
isError: result.isError ?? false,
|
|
94
|
+
details: result.details,
|
|
95
|
+
});
|
|
40
96
|
}
|
|
41
97
|
function asToolCall(parsed) {
|
|
42
98
|
if (!isPlainRecord(parsed) || parsed["method"] !== "tools/call")
|