oh-my-opencode 4.18.0 → 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 +3677 -0
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +3154 -0
- package/.agents/skills/work-with-pr/SKILL.md +16 -37
- package/.agents/skills/work-with-pr-workspace/evals/evals.json +3 -3
- package/.opencode/skills/work-with-pr/SKILL.md +16 -37
- package/.opencode/skills/work-with-pr-workspace/evals/evals.json +3 -3
- package/bin/AGENTS.md +33 -0
- package/dist/cli/index.js +500 -158
- package/dist/cli-node/index.js +500 -158
- 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 +956 -702
- 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 +16 -16
- package/packages/git-bash-mcp/dist/cli.js +81 -19
- package/packages/lsp-core/package.json +4 -0
- package/packages/lsp-core/src/index.ts +1 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.ts +12 -3
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +261 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +63 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +35 -5
- package/packages/lsp-core/src/lsp/client.ts +262 -80
- package/packages/lsp-core/src/lsp/config-loader.ts +5 -17
- package/packages/lsp-core/src/lsp/connection.ts +12 -6
- package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +221 -0
- package/packages/lsp-core/src/lsp/directory-diagnostics.ts +61 -28
- package/packages/lsp-core/src/lsp/errors.ts +11 -0
- package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +215 -0
- package/packages/lsp-core/src/lsp/formatters.ts +3 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection.ts +73 -5
- package/packages/lsp-core/src/lsp/server-install-state.ts +3 -6
- package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
- package/packages/lsp-core/src/lsp/transport.ts +96 -70
- package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +214 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +163 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +67 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +368 -0
- package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +113 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
- package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
- package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
- package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
- package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
- package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
- package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
- package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
- package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
- package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
- package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
- package/packages/lsp-core/src/lsp/workspace-edit.ts +49 -200
- package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
- package/packages/lsp-core/src/mcp.ts +18 -7
- package/packages/lsp-core/src/missing-dependency-result.test.ts +105 -0
- package/packages/lsp-core/src/missing-dependency-result.ts +57 -0
- package/packages/lsp-core/src/post-edit/index.ts +1 -0
- package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
- package/packages/lsp-core/src/post-edit/orchestration.ts +178 -0
- package/packages/lsp-core/src/request-context.test.ts +171 -0
- package/packages/lsp-core/src/request-context.ts +222 -9
- package/packages/lsp-core/src/tool-surface.test.ts +4 -1
- package/packages/lsp-core/src/tools/diagnostics.ts +32 -13
- package/packages/lsp-core/src/tools/navigation.ts +12 -12
- package/packages/lsp-core/src/tools/rename.ts +10 -15
- package/packages/lsp-core/src/tools/symbols.ts +11 -11
- package/packages/lsp-core/src/tools/types.ts +2 -1
- package/packages/lsp-daemon/dist/cli.js +3330 -764
- package/packages/lsp-daemon/dist/client.d.ts +105 -0
- package/packages/lsp-daemon/dist/client.js +5995 -0
- package/packages/lsp-daemon/dist/daemon-client.d.ts +12 -7
- package/packages/lsp-daemon/dist/daemon-client.js +139 -32
- package/packages/lsp-daemon/dist/daemon-server.d.ts +1 -0
- package/packages/lsp-daemon/dist/daemon-server.js +40 -15
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +10 -8
- package/packages/lsp-daemon/dist/ensure-daemon.js +135 -51
- package/packages/lsp-daemon/dist/index.d.ts +2 -2
- package/packages/lsp-daemon/dist/index.js +3093 -786
- package/packages/lsp-daemon/dist/ipc-protocol.d.ts +46 -0
- package/packages/lsp-daemon/dist/ipc-protocol.js +187 -0
- package/packages/lsp-daemon/dist/lock.js +14 -4
- package/packages/lsp-daemon/dist/ownership.d.ts +49 -0
- package/packages/lsp-daemon/dist/ownership.js +168 -0
- package/packages/lsp-daemon/dist/paths.d.ts +33 -9
- package/packages/lsp-daemon/dist/paths.js +72 -33
- package/packages/lsp-daemon/dist/proxy.d.ts +5 -0
- package/packages/lsp-daemon/dist/proxy.js +123 -16
- package/packages/lsp-daemon/dist/request-routing.d.ts +7 -2
- package/packages/lsp-daemon/dist/request-routing.js +71 -22
- package/packages/lsp-daemon/dist/run-daemon.js +9 -2
- package/packages/lsp-daemon/dist/runtime-contract.d.ts +21 -0
- package/packages/lsp-daemon/dist/runtime-contract.js +58 -0
- package/packages/lsp-daemon/dist/socket-jsonrpc.js +6 -1
- package/packages/lsp-daemon/package.json +12 -3
- package/packages/lsp-tools-mcp/dist/cli.js +2189 -454
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +1741 -148
- package/packages/lsp-tools-mcp/dist/mcp.js +2206 -471
- package/packages/lsp-tools-mcp/dist/request-context.js +176 -6
- package/packages/lsp-tools-mcp/dist/tools.js +2119 -447
- 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 +55 -0
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +3033 -936
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook-cli.js +0 -4
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.d.ts +5 -2
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +41 -62
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +24 -15
- package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.d.ts +3 -7
- package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.js +23 -49
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +3 -2
- package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.mjs +31 -1
- package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.test.mjs +76 -0
- package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +201 -0
- package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.test.mjs +55 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +0 -4
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +49 -71
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +26 -15
- package/packages/omo-codex/plugin/components/lsp/src/lsp-session-state.ts +26 -64
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +16 -17
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +30 -4
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +20 -5
- 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/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +6 -5
- 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 +26 -14
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +2 -3
- package/packages/omo-codex/plugin/scripts/build-components.mjs +13 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +9 -1
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +2 -1
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +6 -5
- package/packages/omo-codex/plugin/test/aggregate-build.test.mjs +8 -0
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +128 -15
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +10 -0
- package/packages/omo-codex/plugin/test/lsp-prebuild-layouts.test.mjs +2 -0
- 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 +34 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +328 -63
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { builtinModules } from "node:module";
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
statSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from "node:fs";
|
|
15
|
+
import { dirname, join, relative } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
const SCHEMA_VERSION = 1;
|
|
19
|
+
const componentRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
20
|
+
const distDir = join(componentRoot, "dist");
|
|
21
|
+
const manifestPath = join(distDir, ".omo-runtime-manifest.json");
|
|
22
|
+
const builtinSpecifiers = new Set([
|
|
23
|
+
...builtinModules,
|
|
24
|
+
...builtinModules.map((name) => `node:${name}`),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
if (!existsSync(join(componentRoot, "src"))) {
|
|
28
|
+
validateDistOnlyManifest();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isManifestFresh()) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
rmSync(distDir, { recursive: true, force: true });
|
|
37
|
+
mkdirSync(distDir, { recursive: true });
|
|
38
|
+
run("tsc", ["-p", "tsconfig.build.json"]);
|
|
39
|
+
run("bun", [
|
|
40
|
+
"build",
|
|
41
|
+
"src/cli.ts",
|
|
42
|
+
"--target",
|
|
43
|
+
"node",
|
|
44
|
+
"--format",
|
|
45
|
+
"esm",
|
|
46
|
+
"--outfile",
|
|
47
|
+
"dist/cli.js",
|
|
48
|
+
]);
|
|
49
|
+
assertBuiltinOnlyCli();
|
|
50
|
+
writeManifest();
|
|
51
|
+
validateManifest({ requireFreshInputs: true });
|
|
52
|
+
|
|
53
|
+
function run(command, args) {
|
|
54
|
+
const result = spawnSync(command, args, {
|
|
55
|
+
cwd: componentRoot,
|
|
56
|
+
shell: process.platform === "win32",
|
|
57
|
+
stdio: "inherit",
|
|
58
|
+
});
|
|
59
|
+
if (result.error !== undefined) throw result.error;
|
|
60
|
+
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isManifestFresh() {
|
|
64
|
+
try {
|
|
65
|
+
validateManifest({ requireFreshInputs: true });
|
|
66
|
+
return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof Error) return false;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validateDistOnlyManifest() {
|
|
74
|
+
try {
|
|
75
|
+
validateManifest({ requireFreshInputs: false });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
78
|
+
console.error(`Invalid installed codex-lsp runtime manifest: ${message}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateManifest({ requireFreshInputs }) {
|
|
84
|
+
const manifest = readManifest();
|
|
85
|
+
if (manifest.schemaVersion !== SCHEMA_VERSION) throw new Error("unsupported manifest schemaVersion");
|
|
86
|
+
if (manifest.version !== packageVersion()) throw new Error("manifest version is stale");
|
|
87
|
+
if (requireFreshInputs && manifest.inputDigest !== inputDigest()) throw new Error("manifest inputDigest is stale");
|
|
88
|
+
const outputs = [...manifest.outputs].sort((left, right) => left.path.localeCompare(right.path));
|
|
89
|
+
if (JSON.stringify(outputs) !== JSON.stringify(manifest.outputs)) throw new Error("manifest outputs are not sorted");
|
|
90
|
+
for (const output of outputs) {
|
|
91
|
+
const outputPath = join(distDir, output.path);
|
|
92
|
+
if (!existsSync(outputPath)) throw new Error(`manifest output is missing: ${output.path}`);
|
|
93
|
+
if (sha256File(outputPath) !== output.sha256) throw new Error(`manifest output hash mismatch: ${output.path}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readManifest() {
|
|
98
|
+
if (!existsSync(manifestPath)) throw new Error("manifest is missing");
|
|
99
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
100
|
+
if (!isRecord(parsed)) throw new Error("manifest must be an object");
|
|
101
|
+
if (parsed.schemaVersion !== SCHEMA_VERSION) throw new Error("manifest schemaVersion must be 1");
|
|
102
|
+
if (typeof parsed.version !== "string") throw new Error("manifest version must be a string");
|
|
103
|
+
if (typeof parsed.inputDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(parsed.inputDigest)) {
|
|
104
|
+
throw new Error("manifest inputDigest must be sha256:<64hex>");
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(parsed.outputs) || !parsed.outputs.every(isManifestOutput)) {
|
|
107
|
+
throw new Error("manifest outputs are malformed");
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isManifestOutput(value) {
|
|
113
|
+
return (
|
|
114
|
+
isRecord(value) &&
|
|
115
|
+
typeof value.path === "string" &&
|
|
116
|
+
value.path.length > 0 &&
|
|
117
|
+
!value.path.includes("..") &&
|
|
118
|
+
typeof value.sha256 === "string" &&
|
|
119
|
+
/^[0-9a-f]{64}$/.test(value.sha256)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function writeManifest() {
|
|
124
|
+
const manifest = {
|
|
125
|
+
schemaVersion: SCHEMA_VERSION,
|
|
126
|
+
version: packageVersion(),
|
|
127
|
+
inputDigest: inputDigest(),
|
|
128
|
+
outputs: outputFiles().map((path) => ({ path, sha256: sha256File(join(distDir, path)) })),
|
|
129
|
+
};
|
|
130
|
+
manifest.outputs.sort((left, right) => left.path.localeCompare(right.path));
|
|
131
|
+
const tempPath = join(distDir, `.omo-runtime-manifest.${process.pid}.tmp`);
|
|
132
|
+
writeFileSync(tempPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
133
|
+
renameSync(tempPath, manifestPath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function inputDigest() {
|
|
137
|
+
const hash = createHash("sha256");
|
|
138
|
+
for (const path of inputFiles()) {
|
|
139
|
+
hash.update(path);
|
|
140
|
+
hash.update("\0");
|
|
141
|
+
hash.update(sha256File(join(componentRoot, path)));
|
|
142
|
+
hash.update("\0");
|
|
143
|
+
}
|
|
144
|
+
return `sha256:${hash.digest("hex")}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function inputFiles() {
|
|
148
|
+
return [
|
|
149
|
+
"package.json",
|
|
150
|
+
"tsconfig.json",
|
|
151
|
+
"tsconfig.build.json",
|
|
152
|
+
"scripts/build-runtime.mjs",
|
|
153
|
+
...walk(join(componentRoot, "src")).map((path) => relative(componentRoot, path)),
|
|
154
|
+
].sort();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function outputFiles() {
|
|
158
|
+
return walk(distDir)
|
|
159
|
+
.map((path) => relative(distDir, path))
|
|
160
|
+
.filter((path) => path !== ".omo-runtime-manifest.json")
|
|
161
|
+
.sort();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function walk(root) {
|
|
165
|
+
const entries = [];
|
|
166
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
167
|
+
const path = join(root, entry.name);
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
entries.push(...walk(path));
|
|
170
|
+
} else if (entry.isFile()) {
|
|
171
|
+
entries.push(path);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function assertBuiltinOnlyCli() {
|
|
178
|
+
const source = readFileSync(join(distDir, "cli.js"), "utf8");
|
|
179
|
+
const specifiers = [
|
|
180
|
+
...source.matchAll(/\bimport\s+(?:[^'";]+?\s+from\s+)?["']([^"']+)["']/g),
|
|
181
|
+
...source.matchAll(/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g),
|
|
182
|
+
].map((match) => match[1]);
|
|
183
|
+
const invalid = specifiers.filter((specifier) => !builtinSpecifiers.has(specifier));
|
|
184
|
+
if (invalid.length > 0) {
|
|
185
|
+
throw new Error(`dist/cli.js has non-builtin runtime imports: ${invalid.join(", ")}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function packageVersion() {
|
|
190
|
+
const parsed = JSON.parse(readFileSync(join(componentRoot, "package.json"), "utf8"));
|
|
191
|
+
if (!isRecord(parsed) || typeof parsed.version !== "string") throw new Error("package.json version is missing");
|
|
192
|
+
return parsed.version;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function sha256File(path) {
|
|
196
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isRecord(value) {
|
|
200
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
201
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
|
|
8
|
+
const SCRIPT_URL = new URL("./build-runtime.mjs", import.meta.url);
|
|
9
|
+
|
|
10
|
+
test("#given a built component runtime #when the manifest is inspected #then it pins sorted output hashes", async () => {
|
|
11
|
+
const manifest = JSON.parse(await readFile(new URL("../dist/.omo-runtime-manifest.json", import.meta.url), "utf8"));
|
|
12
|
+
assert.equal(manifest.schemaVersion, 1);
|
|
13
|
+
assert.match(manifest.inputDigest, /^sha256:[0-9a-f]{64}$/u);
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
manifest.outputs.map((output) => output.path),
|
|
16
|
+
[...manifest.outputs.map((output) => output.path)].sort(),
|
|
17
|
+
);
|
|
18
|
+
assert(manifest.outputs.some((output) => output.path === "cli.js"));
|
|
19
|
+
assert(manifest.outputs.every((output) => /^[0-9a-f]{64}$/u.test(output.sha256)));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("#given a dist-only installed component #when output bytes are tampered #then validation fails actionably", async () => {
|
|
23
|
+
const root = await mkdtemp(join(tmpdir(), "codex-lsp-dist-only-"));
|
|
24
|
+
try {
|
|
25
|
+
const componentRoot = join(root, "component");
|
|
26
|
+
await mkdir(join(componentRoot, "scripts"), { recursive: true });
|
|
27
|
+
await copyFile(SCRIPT_URL, join(componentRoot, "scripts", "build-runtime.mjs"));
|
|
28
|
+
await writeFile(join(componentRoot, "package.json"), JSON.stringify({ version: "4.17.0" }));
|
|
29
|
+
await mkdir(join(componentRoot, "dist"), { recursive: true });
|
|
30
|
+
await writeFile(join(componentRoot, "dist", "cli.js"), "console.log('tampered')\n");
|
|
31
|
+
await writeFile(
|
|
32
|
+
join(componentRoot, "dist", ".omo-runtime-manifest.json"),
|
|
33
|
+
JSON.stringify(
|
|
34
|
+
{
|
|
35
|
+
schemaVersion: 1,
|
|
36
|
+
version: "4.17.0",
|
|
37
|
+
inputDigest: `sha256:${"0".repeat(64)}`,
|
|
38
|
+
outputs: [{ path: "cli.js", sha256: "1".repeat(64) }],
|
|
39
|
+
},
|
|
40
|
+
null,
|
|
41
|
+
2,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const result = spawnSync(process.execPath, [join(componentRoot, "scripts", "build-runtime.mjs")], {
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
assert.notEqual(result.status, 0);
|
|
50
|
+
assert.match(result.stderr, /Invalid installed codex-lsp runtime manifest/u);
|
|
51
|
+
assert.match(result.stderr, /hash mismatch/u);
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(root, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { stdin as processStdin } from "node:process";
|
|
2
2
|
|
|
3
|
-
import { disposeDefaultLspManager } from "@code-yeongyu/lsp-daemon";
|
|
4
|
-
|
|
5
3
|
import { isRecord, runLspPostCompactHook, runLspPostToolUseHook } from "./codex-hook.js";
|
|
6
4
|
|
|
7
5
|
export async function runPostToolUseHookCli(stdin: NodeJS.ReadStream = processStdin): Promise<void> {
|
|
@@ -26,8 +24,6 @@ async function runHookCli(
|
|
|
26
24
|
} catch {
|
|
27
25
|
// LSP feedback is best-effort: stderr or a non-zero exit here surfaces as harness noise on every edit.
|
|
28
26
|
return;
|
|
29
|
-
} finally {
|
|
30
|
-
await disposeDefaultLspManager().catch(() => undefined);
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
2
4
|
|
|
3
|
-
import { callDiagnosticsViaDaemon
|
|
5
|
+
import { callDiagnosticsViaDaemon } from "@code-yeongyu/lsp-daemon/client";
|
|
6
|
+
import { collectPostEditDiagnostics, type PostEditDiagnosticsOutcome } from "@oh-my-opencode/lsp-core/post-edit";
|
|
7
|
+
import { parseLspRequestContext, type LspRequestContext } from "@oh-my-opencode/lsp-core/request-context";
|
|
4
8
|
|
|
5
9
|
import { ensureLspDaemonCliEnv } from "./daemon-cli-path.js";
|
|
6
10
|
import {
|
|
7
11
|
isLspDaemonUnreachableDiagnostics,
|
|
8
|
-
isUnavailableLspDiagnostics,
|
|
9
12
|
markLspSessionCompacted,
|
|
10
|
-
|
|
13
|
+
readLspPostEditCache,
|
|
11
14
|
sessionIdFrom,
|
|
12
|
-
|
|
15
|
+
writeLspPostEditCache,
|
|
13
16
|
} from "./lsp-session-state.js";
|
|
14
17
|
import { extractMutatedFilePaths } from "./mutated-file-paths.js";
|
|
15
18
|
|
|
16
19
|
export { extractMutatedFilePaths } from "./mutated-file-paths.js";
|
|
17
20
|
|
|
18
|
-
export type DiagnosticsRunner = (filePath: string) => Promise<
|
|
21
|
+
export type DiagnosticsRunner = (filePath: string) => Promise<PostEditDiagnosticsOutcome>;
|
|
19
22
|
|
|
20
23
|
export interface CodexPostToolUseInput {
|
|
21
24
|
session_id?: unknown;
|
|
@@ -43,13 +46,10 @@ interface PostToolUseHookOutput {
|
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
const CLEAN_DIAGNOSTICS_TEXT = "No diagnostics found";
|
|
47
|
-
const UNSUPPORTED_EXTENSION_TEXT = "No LSP server configured for extension:";
|
|
48
49
|
const DIAGNOSTIC_START_PATTERN = /(?:error|warning|information|hint)\[[^\]\r\n]+\] \(\d+\) at \d+:\d+:/g;
|
|
49
50
|
const DIAGNOSTIC_CHUNK_PATTERN = /^(?:error|warning|information|hint)\[[^\]\r\n]+\] \(\d+\) at \d+:\d+:/;
|
|
50
51
|
const DEFAULT_MAX_HOOK_FEEDBACK_CHARS = 8000;
|
|
51
52
|
const CONTEXT_PRESSURE_MAX_HOOK_FEEDBACK_CHARS = 1200;
|
|
52
|
-
const MAX_CONCURRENT_DIAGNOSTICS = 4;
|
|
53
53
|
const CONTEXT_PRESSURE_MARKERS = [
|
|
54
54
|
"context compacted",
|
|
55
55
|
"context_length_exceeded",
|
|
@@ -60,35 +60,57 @@ const CONTEXT_PRESSURE_MARKERS = [
|
|
|
60
60
|
"long threads and multiple compactions",
|
|
61
61
|
] as const;
|
|
62
62
|
|
|
63
|
-
export async function runLspDiagnosticsText(filePath: string): Promise<
|
|
63
|
+
export async function runLspDiagnosticsText(filePath: string): Promise<PostEditDiagnosticsOutcome> {
|
|
64
64
|
ensureLspDaemonCliEnv();
|
|
65
|
-
const result = await callDiagnosticsViaDaemon(filePath, { context:
|
|
65
|
+
const result = await callDiagnosticsViaDaemon(filePath, { context: codexLspRequestContext() });
|
|
66
|
+
return postEditOutcomeFromDaemonResult(result);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function postEditOutcomeFromDaemonResult(result: {
|
|
70
|
+
readonly content: readonly { readonly type: string; readonly text?: string }[];
|
|
71
|
+
readonly details?: unknown;
|
|
72
|
+
}): PostEditDiagnosticsOutcome {
|
|
73
|
+
const availability = notConfiguredAvailability(result.details);
|
|
74
|
+
if (availability !== undefined) return { kind: "not_configured", extension: availability.extension };
|
|
66
75
|
return result.content.map((block) => block.text).join("\n");
|
|
67
76
|
}
|
|
68
77
|
|
|
78
|
+
function notConfiguredAvailability(details: unknown): { readonly extension: string } | undefined {
|
|
79
|
+
if (!isRecord(details)) return undefined;
|
|
80
|
+
const availability = details["availability"];
|
|
81
|
+
if (!isRecord(availability)) return undefined;
|
|
82
|
+
if (availability["kind"] !== "not_configured") return undefined;
|
|
83
|
+
const extension = availability["extension"];
|
|
84
|
+
return typeof extension === "string" && extension.length > 0 ? { extension } : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function codexLspRequestContext(
|
|
88
|
+
env: Record<string, string | undefined> = process.env,
|
|
89
|
+
cwd: string = process.cwd(),
|
|
90
|
+
): LspRequestContext {
|
|
91
|
+
const canonicalCwd = realpathSync(resolve(cwd));
|
|
92
|
+
const codexHome = resolve(env["CODEX_HOME"]?.trim() || join(homedir(), ".codex"));
|
|
93
|
+
return parseLspRequestContext({
|
|
94
|
+
cwd: canonicalCwd,
|
|
95
|
+
projectConfigPaths: [join(canonicalCwd, ".codex", "lsp-client.json")],
|
|
96
|
+
userConfigPath: join(codexHome, "lsp-client.json"),
|
|
97
|
+
installDecisionsPath: join(codexHome, "lsp-install-decisions.json"),
|
|
98
|
+
capabilities: { installDecisionTool: true },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
export async function runLspPostToolUseHook(
|
|
70
103
|
input: CodexPostToolUseInput,
|
|
71
104
|
runDiagnostics: DiagnosticsRunner = runLspDiagnosticsText,
|
|
72
105
|
): Promise<string> {
|
|
73
106
|
const sessionId = sessionIdFrom(input);
|
|
74
|
-
const filePaths = extractMutatedFilePaths(input)
|
|
75
|
-
(filePath) => !shouldSkipUnavailableLspDiagnostics(filePath, sessionId),
|
|
76
|
-
);
|
|
107
|
+
const filePaths = extractMutatedFilePaths(input);
|
|
77
108
|
if (filePaths.length === 0) return "";
|
|
78
109
|
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// recording it would silence this extension for the rest of the session.
|
|
84
|
-
if (isLspDaemonUnreachableDiagnostics(diagnostics)) continue;
|
|
85
|
-
const unavailable = isUnavailableLspDiagnostics(diagnostics);
|
|
86
|
-
observations.push({ filePath, unavailable });
|
|
87
|
-
if (isCleanDiagnostics(diagnostics)) continue;
|
|
88
|
-
if (unavailable) continue;
|
|
89
|
-
blocks.push({ filePath, diagnostics });
|
|
90
|
-
}
|
|
91
|
-
recordLspDiagnosticsObservations(sessionId, observations);
|
|
110
|
+
const cache = readLspPostEditCache(sessionId);
|
|
111
|
+
const result = await collectPostEditDiagnostics({ filePaths, runDiagnostics, cache });
|
|
112
|
+
writeLspPostEditCache(sessionId, cache);
|
|
113
|
+
const blocks = result.blocks.filter(({ diagnostics }) => !isLspDaemonUnreachableDiagnostics(diagnostics));
|
|
92
114
|
|
|
93
115
|
if (blocks.length === 0) return "";
|
|
94
116
|
|
|
@@ -110,42 +132,6 @@ export async function runLspPostCompactHook(input: CodexPostCompactInput): Promi
|
|
|
110
132
|
return "";
|
|
111
133
|
}
|
|
112
134
|
|
|
113
|
-
async function collectDiagnostics(
|
|
114
|
-
filePaths: readonly string[],
|
|
115
|
-
runDiagnostics: DiagnosticsRunner,
|
|
116
|
-
): Promise<DiagnosticBlock[]> {
|
|
117
|
-
const results: DiagnosticBlock[] = [];
|
|
118
|
-
let nextIndex = 0;
|
|
119
|
-
const workerCount = Math.min(MAX_CONCURRENT_DIAGNOSTICS, filePaths.length);
|
|
120
|
-
async function worker(): Promise<void> {
|
|
121
|
-
for (;;) {
|
|
122
|
-
const index = nextIndex;
|
|
123
|
-
nextIndex += 1;
|
|
124
|
-
const filePath = filePaths[index];
|
|
125
|
-
if (filePath === undefined) return;
|
|
126
|
-
results[index] = { filePath, diagnostics: await collectFileDiagnostics(filePath, runDiagnostics) };
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
130
|
-
return results;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function collectFileDiagnostics(filePath: string, runDiagnostics: DiagnosticsRunner): Promise<string> {
|
|
134
|
-
try {
|
|
135
|
-
return (await runDiagnostics(filePath)).trim();
|
|
136
|
-
} catch (error) {
|
|
137
|
-
return formatDiagnosticsError(error);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function formatDiagnosticsError(error: unknown): string {
|
|
142
|
-
if (error instanceof Error) {
|
|
143
|
-
const message = error.message.trim();
|
|
144
|
-
if (message.length > 0) return message;
|
|
145
|
-
}
|
|
146
|
-
return String(error).trim();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
135
|
function formatDiagnosticBlock({ filePath, diagnostics }: DiagnosticBlock): string {
|
|
150
136
|
return `LSP diagnostics after editing ${filePath}:\n\n${formatDiagnosticsForDisplay(diagnostics)}`;
|
|
151
137
|
}
|
|
@@ -221,14 +207,6 @@ function limitHookText(text: string, maxChars: number): string {
|
|
|
221
207
|
return `${head}${marker}`;
|
|
222
208
|
}
|
|
223
209
|
|
|
224
|
-
function isCleanDiagnostics(diagnostics: string): boolean {
|
|
225
|
-
return (
|
|
226
|
-
diagnostics.length === 0 ||
|
|
227
|
-
diagnostics === CLEAN_DIAGNOSTICS_TEXT ||
|
|
228
|
-
diagnostics.startsWith(UNSUPPORTED_EXTENSION_TEXT)
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
210
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
233
211
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
234
212
|
}
|
|
@@ -3,28 +3,32 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
+
import {
|
|
7
|
+
OMO_LSP_DAEMON_CLI,
|
|
8
|
+
OMO_LSP_DAEMON_VERSION,
|
|
9
|
+
resolveDaemonRuntime,
|
|
10
|
+
} from "@code-yeongyu/lsp-daemon/client";
|
|
11
|
+
|
|
6
12
|
const requireFromHere = createRequire(import.meta.url);
|
|
7
13
|
|
|
8
|
-
const PACKAGE_LSP_DAEMON_CLI = "@code-yeongyu/lsp-daemon/
|
|
9
|
-
const CODEX_LSP_DAEMON_CLI_ENV = "CODEX_LSP_DAEMON_CLI";
|
|
10
|
-
const CODEX_LSP_DAEMON_VERSION_ENV = "CODEX_LSP_DAEMON_VERSION";
|
|
14
|
+
const PACKAGE_LSP_DAEMON_CLI = "@code-yeongyu/lsp-daemon/cli";
|
|
11
15
|
|
|
12
16
|
interface LspDaemonCliResolution {
|
|
13
17
|
cliPath: string;
|
|
14
|
-
version: string
|
|
18
|
+
version: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export function ensureLspDaemonCliEnv(env: NodeJS.ProcessEnv = process.env): void {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!env[CODEX_LSP_DAEMON_VERSION_ENV]?.trim() && resolution.version !== null) {
|
|
22
|
-
env[CODEX_LSP_DAEMON_VERSION_ENV] = resolution.version;
|
|
23
|
-
}
|
|
22
|
+
const runtime = resolveDaemonRuntime(env, resolveLspDaemonCli());
|
|
23
|
+
env[OMO_LSP_DAEMON_CLI] = runtime.cliPath;
|
|
24
|
+
env[OMO_LSP_DAEMON_VERSION] = runtime.version;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export function resolveLspDaemonCliPath(): string {
|
|
27
|
-
|
|
27
|
+
export function resolveLspDaemonCliPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
28
|
+
const runtime = resolveDaemonRuntime(env, resolveLspDaemonCli());
|
|
29
|
+
if (env[OMO_LSP_DAEMON_CLI] === undefined) env[OMO_LSP_DAEMON_CLI] = runtime.cliPath;
|
|
30
|
+
if (env[OMO_LSP_DAEMON_VERSION] === undefined) env[OMO_LSP_DAEMON_VERSION] = runtime.version;
|
|
31
|
+
return runtime.cliPath;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function resolveLspDaemonCli(): LspDaemonCliResolution {
|
|
@@ -38,15 +42,20 @@ function resolveLspDaemonCli(): LspDaemonCliResolution {
|
|
|
38
42
|
function resolvePackageLspDaemonCliPath(): string | null {
|
|
39
43
|
try {
|
|
40
44
|
return requireFromHere.resolve(PACKAGE_LSP_DAEMON_CLI);
|
|
41
|
-
} catch {
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (!(error instanceof Error)) throw error;
|
|
42
47
|
return null;
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
function resolveConfiguredLspDaemonCli(cliPath: string): LspDaemonCliResolution {
|
|
52
|
+
const version = readDaemonPackageVersion(cliPath);
|
|
53
|
+
if (version === null) {
|
|
54
|
+
throw new Error(`Unable to determine packaged LSP daemon version beside ${cliPath}`);
|
|
55
|
+
}
|
|
47
56
|
return {
|
|
48
57
|
cliPath,
|
|
49
|
-
version
|
|
58
|
+
version,
|
|
50
59
|
};
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -56,7 +65,9 @@ function readDaemonPackageVersion(cliPath: string): string | null {
|
|
|
56
65
|
if (isRecord(parsed) && typeof parsed["version"] === "string" && parsed["version"].length > 0) {
|
|
57
66
|
return parsed["version"];
|
|
58
67
|
}
|
|
59
|
-
} catch {
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (!(error instanceof Error)) throw error;
|
|
70
|
+
}
|
|
60
71
|
return null;
|
|
61
72
|
}
|
|
62
73
|
|
|
@@ -1,71 +1,33 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname,
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
readonly unavailableExtensions: readonly string[];
|
|
7
|
-
readonly postCompactProbePending?: boolean;
|
|
8
|
-
}
|
|
5
|
+
import type { PostEditNotConfiguredCache } from "@oh-my-opencode/lsp-core/post-edit";
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
readonly
|
|
12
|
-
readonly unavailable: boolean;
|
|
7
|
+
interface LspSessionState {
|
|
8
|
+
readonly notConfiguredExtensions: readonly string[];
|
|
13
9
|
}
|
|
14
10
|
|
|
15
11
|
export function sessionIdFrom(input: { readonly session_id?: unknown }): string | undefined {
|
|
16
12
|
return typeof input.session_id === "string" && input.session_id.length > 0 ? input.session_id : undefined;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
export function
|
|
20
|
-
if (sessionId === undefined) return
|
|
15
|
+
export function readLspPostEditCache(sessionId: string | undefined): PostEditNotConfiguredCache {
|
|
16
|
+
if (sessionId === undefined) return { notConfiguredExtensions: new Set() };
|
|
21
17
|
const state = readSessionState(sessionStatePath(sessionId));
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
extension !== undefined &&
|
|
25
|
-
state.postCompactProbePending !== true &&
|
|
26
|
-
state.unavailableExtensions.includes(extension)
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function recordLspDiagnosticsObservations(
|
|
31
|
-
sessionId: string | undefined,
|
|
32
|
-
observations: readonly DiagnosticsObservation[],
|
|
33
|
-
): void {
|
|
34
|
-
if (sessionId === undefined || observations.length === 0) return;
|
|
35
|
-
const state = readSessionState(sessionStatePath(sessionId));
|
|
36
|
-
const unavailableExtensions = new Set(state.unavailableExtensions);
|
|
37
|
-
|
|
38
|
-
for (const observation of observations) {
|
|
39
|
-
const extension = extensionKey(observation.filePath);
|
|
40
|
-
if (extension === undefined) continue;
|
|
41
|
-
if (observation.unavailable) {
|
|
42
|
-
unavailableExtensions.add(extension);
|
|
43
|
-
} else {
|
|
44
|
-
unavailableExtensions.delete(extension);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
writeSessionState(sessionStatePath(sessionId), { unavailableExtensions: [...unavailableExtensions].sort() });
|
|
18
|
+
return { notConfiguredExtensions: new Set(state.notConfiguredExtensions) };
|
|
49
19
|
}
|
|
50
20
|
|
|
51
|
-
export function
|
|
21
|
+
export function writeLspPostEditCache(sessionId: string | undefined, cache: PostEditNotConfiguredCache): void {
|
|
52
22
|
if (sessionId === undefined) return;
|
|
53
|
-
const state = readSessionState(sessionStatePath(sessionId));
|
|
54
|
-
if (state.unavailableExtensions.length === 0) return;
|
|
55
23
|
writeSessionState(sessionStatePath(sessionId), {
|
|
56
|
-
|
|
57
|
-
postCompactProbePending: true,
|
|
24
|
+
notConfiguredExtensions: [...cache.notConfiguredExtensions].sort(),
|
|
58
25
|
});
|
|
59
26
|
}
|
|
60
27
|
|
|
61
|
-
export function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
normalized.includes("LSP request timeout (method: initialize)") ||
|
|
65
|
-
normalized.includes("LSP server is still initializing") ||
|
|
66
|
-
normalized.includes("NOT INSTALLED") ||
|
|
67
|
-
normalized.includes("Command not found:")
|
|
68
|
-
);
|
|
28
|
+
export function markLspSessionCompacted(sessionId: string | undefined): void {
|
|
29
|
+
if (sessionId === undefined) return;
|
|
30
|
+
writeSessionState(sessionStatePath(sessionId), emptyState());
|
|
69
31
|
}
|
|
70
32
|
|
|
71
33
|
export function isLspDaemonUnreachableDiagnostics(diagnostics: string): boolean {
|
|
@@ -80,7 +42,7 @@ function sessionStatePath(sessionId: string): string {
|
|
|
80
42
|
function readSessionState(path: string): LspSessionState {
|
|
81
43
|
try {
|
|
82
44
|
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
83
|
-
if (isLspSessionState(parsed)) return parsed;
|
|
45
|
+
if (isRecord(parsed) && isLspSessionState(parsed)) return normalizeSessionState(parsed);
|
|
84
46
|
return emptyState();
|
|
85
47
|
} catch (error) {
|
|
86
48
|
if (error instanceof SyntaxError || (isRecord(error) && error["code"] === "ENOENT")) return emptyState();
|
|
@@ -94,25 +56,25 @@ function writeSessionState(path: string, state: LspSessionState): void {
|
|
|
94
56
|
}
|
|
95
57
|
|
|
96
58
|
function emptyState(): LspSessionState {
|
|
97
|
-
return {
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function extensionKey(filePath: string): string | undefined {
|
|
101
|
-
const extension = extname(filePath).toLowerCase();
|
|
102
|
-
return extension.length === 0 ? undefined : extension;
|
|
59
|
+
return { notConfiguredExtensions: [] };
|
|
103
60
|
}
|
|
104
61
|
|
|
105
62
|
function safePathSegment(value: string): string {
|
|
106
63
|
return value.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120) || "unknown-session";
|
|
107
64
|
}
|
|
108
65
|
|
|
109
|
-
function isLspSessionState(value: unknown):
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
66
|
+
function isLspSessionState(value: Record<string, unknown>): boolean {
|
|
67
|
+
const notConfiguredExtensions = value["notConfiguredExtensions"] ?? value["unavailableExtensions"];
|
|
68
|
+
return Array.isArray(notConfiguredExtensions) && notConfiguredExtensions.every((item) => typeof item === "string");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeSessionState(value: Record<string, unknown>): LspSessionState {
|
|
72
|
+
const notConfiguredExtensions = value["notConfiguredExtensions"] ?? value["unavailableExtensions"];
|
|
73
|
+
return {
|
|
74
|
+
notConfiguredExtensions: Array.isArray(notConfiguredExtensions)
|
|
75
|
+
? notConfiguredExtensions.filter((item) => typeof item === "string").sort()
|
|
76
|
+
: [],
|
|
77
|
+
};
|
|
116
78
|
}
|
|
117
79
|
|
|
118
80
|
function isRecord(value: unknown): value is Record<string, unknown> {
|