oh-my-opencode 4.6.0 → 4.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/version-mismatch.js +47 -0
- package/bin/version-mismatch.test.ts +120 -0
- package/dist/cli/codex-ulw-loop.d.ts +12 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
- package/dist/cli/index.js +5999 -5542
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -1
- package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
- package/dist/features/background-agent/concurrency.d.ts +1 -0
- package/dist/features/background-agent/process-cleanup.d.ts +6 -0
- package/dist/features/claude-code-session-state/state.d.ts +1 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
- package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
- package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
- package/dist/hooks/comment-checker/cli.d.ts +1 -0
- package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
- package/dist/index.js +4250 -3776
- package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
- package/dist/tools/skill/description-formatter.d.ts +5 -1
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +13 -14
- package/packages/ast-grep-mcp/dist/cli.js +53 -9
- package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +13 -0
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +6 -2
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +13 -2
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +30 -79
- package/packages/omo-codex/plugin/components/lsp/src/lsp-session-state.ts +116 -0
- package/packages/omo-codex/plugin/components/lsp/src/mutated-file-paths.ts +88 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +206 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +5 -3
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
- package/packages/omo-codex/plugin/components/rules/src/codex-hook-options.ts +1 -0
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
- package/packages/omo-codex/plugin/components/rules/src/rules/finder.ts +15 -2
- package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +4 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +28 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +3 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +7 -7
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +5 -4
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
- package/packages/omo-codex/plugin/hooks/hooks.json +24 -2
- package/packages/omo-codex/plugin/model-catalog.json +49 -0
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +4 -9
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -7
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +5 -4
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -3
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -19
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +7 -27
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +27 -1
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +22 -0
- package/packages/omo-codex/scripts/install/cli-args.mjs +1 -1
- package/packages/omo-codex/scripts/install/config.mjs +2 -15
- package/packages/omo-codex/scripts/install/delegated-command.mjs +1 -1
- package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
- package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +11 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +65 -7
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +83 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +82 -3
- package/packages/omo-codex/scripts/install-config.test.mjs +5 -6
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +30 -2
- package/packages/omo-codex/scripts/install-local.mjs +1 -1
- package/packages/omo-codex/scripts/install-local.test.mjs +3 -1
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +7 -7
- package/packages/shared-skills/skills/start-work/SKILL.md +6 -6
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
- package/postinstall.mjs +36 -3
- package/dist/cli/install-codex/codex-config-mcp.d.ts +0 -1
|
@@ -8,6 +8,8 @@ export interface ExecuteHookOptions {
|
|
|
8
8
|
zshPath?: string;
|
|
9
9
|
/** Timeout in milliseconds. Process is killed after this. Default: 30000 */
|
|
10
10
|
timeoutMs?: number;
|
|
11
|
+
/** Grace period before force-killing and resolving timed-out commands. Default: 5000 */
|
|
12
|
+
killGraceMs?: number;
|
|
11
13
|
/** When provided, scrub process.env to only include these vars plus HOME/PATH/etc. Used for plugin-sourced hooks. */
|
|
12
14
|
allowedEnvVars?: string[];
|
|
13
15
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { SkillInfo } from "./types";
|
|
2
2
|
import type { CommandInfo } from "../slashcommand/types";
|
|
3
|
-
|
|
3
|
+
interface CombinedDescriptionOptions {
|
|
4
|
+
includeSkills?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function formatCombinedDescription(skills?: SkillInfo[], commands?: CommandInfo[], options?: CombinedDescriptionOptions): string;
|
|
7
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-opencode",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
4
4
|
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"typecheck:packages": "tsgo --noEmit -p packages/rules-engine/tsconfig.json && tsgo --noEmit -p packages/ast-grep-core/tsconfig.json && tsgo --noEmit -p packages/ast-grep-mcp/tsconfig.json && tsgo --noEmit -p packages/git-bash-mcp/tsconfig.json && tsgo --noEmit -p packages/utils/tsconfig.json && tsgo --noEmit -p packages/model-core/tsconfig.json && tsgo --noEmit -p packages/prompts-core/tsconfig.json && tsgo --noEmit -p packages/comment-checker-core/tsconfig.json && tsgo --noEmit -p packages/hashline-core/tsconfig.json && tsgo --noEmit -p packages/boulder-state/tsconfig.json && tsgo --noEmit -p packages/agents-md-core/tsconfig.json && tsgo --noEmit -p packages/omo-codex/tsconfig.json",
|
|
72
72
|
"typecheck:script": "tsgo --noEmit -p script/tsconfig.json",
|
|
73
73
|
"test": "bun test",
|
|
74
|
-
"test:codex": "bun run build:ast-grep-mcp && bun run build:lsp-tools-mcp && npm --prefix packages/omo-codex/plugin ci && bun run --cwd packages/omo-codex/plugin build && bun test src/cli/cli-installer.platform.test.ts src/cli/install-codex/codex-cache.test.ts src/cli/install-codex/codex-cleanup.test.ts src/cli/install-codex/codex-config-agent-cleanup.test.ts src/cli/install-codex/codex-config-reasoning.test.ts src/cli/install-codex/codex-config-toml.test.ts src/cli/install-codex/codex-project-local-cleanup.test.ts src/cli/install-codex/install-codex-project-local-cleanup.test.ts src/cli/install-codex/install-codex.test.ts src/cli/install-codex/install-codex-packaged.test.ts src/cli/install-codex/link-cached-plugin-agents.test.ts packages/omo-codex/src/**/*.test.ts packages/utils/src/jsonc-parser.test.ts packages/utils/src/frontmatter.test.ts packages/hashline-core/src/hash-computation.test.ts packages/hashline-core/src/smoke-untested-modules.test.ts packages/rules-engine/src/index.test.ts packages/rules-engine/src/security-boundary.test.ts packages/agents-md-core/src/injector.test.ts packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts && node --test packages/omo-codex/plugin/test/*.test.mjs packages/omo-codex/scripts/install-cache-copy.test.mjs packages/omo-codex/scripts/install-cli-args.test.mjs packages/omo-codex/scripts/install-config-autonomous.test.mjs packages/omo-codex/scripts/install-config-reasoning.test.mjs packages/omo-codex/scripts/install-config.test.mjs packages/omo-codex/scripts/install-project-local-cleanup.test.mjs packages/omo-codex/scripts/install-local-entrypoint.test.mjs packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs packages/omo-codex/scripts/install-local.test.mjs packages/omo-codex/scripts/install-mcp-runtime.test.mjs packages/omo-codex/scripts/install-packaged-local.test.mjs packages/omo-codex/scripts/install/git-bash.test.mjs packages/omo-codex/scripts/install-agent-links.test.mjs packages/omo-codex/scripts/install-bin-links.test.mjs packages/omo-codex/scripts/sync-telemetry-component.test.mjs",
|
|
74
|
+
"test:codex": "bun run build:ast-grep-mcp && bun run build:lsp-tools-mcp && npm --prefix packages/omo-codex/plugin ci && bun run --cwd packages/omo-codex/plugin build && bun test src/cli/cli-installer.platform.test.ts src/cli/install-codex/codex-cache.test.ts src/cli/install-codex/codex-cleanup.test.ts src/cli/install-codex/codex-config-agent-cleanup.test.ts src/cli/install-codex/codex-config-autonomous-features.test.ts src/cli/install-codex/codex-config-reasoning.test.ts src/cli/install-codex/codex-config-toml.test.ts src/cli/install-codex/codex-project-local-cleanup.test.ts src/cli/install-codex/install-codex-project-local-cleanup.test.ts src/cli/install-codex/install-codex.test.ts src/cli/install-codex/install-codex-packaged.test.ts src/cli/install-codex/link-cached-plugin-agents.test.ts packages/omo-codex/src/**/*.test.ts packages/utils/src/jsonc-parser.test.ts packages/utils/src/frontmatter.test.ts packages/hashline-core/src/hash-computation.test.ts packages/hashline-core/src/smoke-untested-modules.test.ts packages/rules-engine/src/index.test.ts packages/rules-engine/src/security-boundary.test.ts packages/agents-md-core/src/injector.test.ts packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts && node --test packages/omo-codex/plugin/test/*.test.mjs packages/omo-codex/scripts/install-cache-copy.test.mjs packages/omo-codex/scripts/install-cli-args.test.mjs packages/omo-codex/scripts/install-config-autonomous-features.test.mjs packages/omo-codex/scripts/install-config-autonomous.test.mjs packages/omo-codex/scripts/install-config-reasoning.test.mjs packages/omo-codex/scripts/install-config.test.mjs packages/omo-codex/scripts/install-project-local-cleanup.test.mjs packages/omo-codex/scripts/install-local-entrypoint.test.mjs packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs packages/omo-codex/scripts/install-local.test.mjs packages/omo-codex/scripts/install-mcp-runtime.test.mjs packages/omo-codex/scripts/install-packaged-local.test.mjs packages/omo-codex/scripts/install/git-bash.test.mjs packages/omo-codex/scripts/install-agent-links.test.mjs packages/omo-codex/scripts/install-bin-links.test.mjs packages/omo-codex/scripts/sync-telemetry-component.test.mjs",
|
|
75
75
|
"test:windows-codex": "bun run test:codex",
|
|
76
76
|
"build:ast-grep-mcp": "bun run --cwd packages/ast-grep-mcp build",
|
|
77
77
|
"build:git-bash-mcp": "bun run --cwd packages/git-bash-mcp build"
|
|
@@ -106,7 +106,6 @@
|
|
|
106
106
|
"commander": "^14.0.3",
|
|
107
107
|
"detect-libc": "^2.1.2",
|
|
108
108
|
"diff": "^9.0.0",
|
|
109
|
-
"effect": "4.0.0-beta.65",
|
|
110
109
|
"js-yaml": "^4.1.1",
|
|
111
110
|
"jsonc-parser": "^3.3.1",
|
|
112
111
|
"picocolors": "^1.1.1",
|
|
@@ -136,17 +135,17 @@
|
|
|
136
135
|
"zod": "^4.4.3"
|
|
137
136
|
},
|
|
138
137
|
"optionalDependencies": {
|
|
139
|
-
"oh-my-opencode-darwin-arm64": "4.
|
|
140
|
-
"oh-my-opencode-darwin-x64": "4.
|
|
141
|
-
"oh-my-opencode-darwin-x64-baseline": "4.
|
|
142
|
-
"oh-my-opencode-linux-arm64": "4.
|
|
143
|
-
"oh-my-opencode-linux-arm64-musl": "4.
|
|
144
|
-
"oh-my-opencode-linux-x64": "4.
|
|
145
|
-
"oh-my-opencode-linux-x64-baseline": "4.
|
|
146
|
-
"oh-my-opencode-linux-x64-musl": "4.
|
|
147
|
-
"oh-my-opencode-linux-x64-musl-baseline": "4.
|
|
148
|
-
"oh-my-opencode-windows-x64": "4.
|
|
149
|
-
"oh-my-opencode-windows-x64-baseline": "4.
|
|
138
|
+
"oh-my-opencode-darwin-arm64": "4.7.1",
|
|
139
|
+
"oh-my-opencode-darwin-x64": "4.7.1",
|
|
140
|
+
"oh-my-opencode-darwin-x64-baseline": "4.7.1",
|
|
141
|
+
"oh-my-opencode-linux-arm64": "4.7.1",
|
|
142
|
+
"oh-my-opencode-linux-arm64-musl": "4.7.1",
|
|
143
|
+
"oh-my-opencode-linux-x64": "4.7.1",
|
|
144
|
+
"oh-my-opencode-linux-x64-baseline": "4.7.1",
|
|
145
|
+
"oh-my-opencode-linux-x64-musl": "4.7.1",
|
|
146
|
+
"oh-my-opencode-linux-x64-musl-baseline": "4.7.1",
|
|
147
|
+
"oh-my-opencode-windows-x64": "4.7.1",
|
|
148
|
+
"oh-my-opencode-windows-x64-baseline": "4.7.1"
|
|
150
149
|
},
|
|
151
150
|
"overrides": {
|
|
152
151
|
"hono": "^4.12.18",
|
|
@@ -342,13 +342,44 @@ function errorMessage(error) {
|
|
|
342
342
|
import { createRequire } from "module";
|
|
343
343
|
import { dirname, join as join2 } from "path";
|
|
344
344
|
import { existsSync, statSync as statSync2 } from "fs";
|
|
345
|
+
var WINDOWS_EXECUTABLE_EXTENSIONS = [".exe", ".cmd", ".bat"];
|
|
345
346
|
function isValidBinary(filePath) {
|
|
346
347
|
try {
|
|
347
|
-
|
|
348
|
+
const stats = statSync2(filePath);
|
|
349
|
+
if (!stats.isFile()) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const size = stats.size;
|
|
353
|
+
const lowerPath = filePath.toLowerCase();
|
|
354
|
+
if (lowerPath.endsWith(".cmd") || lowerPath.endsWith(".bat")) {
|
|
355
|
+
return size > 0;
|
|
356
|
+
}
|
|
357
|
+
return size > 1e4;
|
|
348
358
|
} catch {
|
|
349
359
|
return false;
|
|
350
360
|
}
|
|
351
361
|
}
|
|
362
|
+
function executableCandidates(filePath, platform = process.platform) {
|
|
363
|
+
if (platform !== "win32")
|
|
364
|
+
return [filePath];
|
|
365
|
+
const candidates = [filePath];
|
|
366
|
+
const lowerPath = filePath.toLowerCase();
|
|
367
|
+
if (WINDOWS_EXECUTABLE_EXTENSIONS.some((extension) => lowerPath.endsWith(extension))) {
|
|
368
|
+
return candidates;
|
|
369
|
+
}
|
|
370
|
+
for (const extension of WINDOWS_EXECUTABLE_EXTENSIONS) {
|
|
371
|
+
candidates.push(`${filePath}${extension}`);
|
|
372
|
+
}
|
|
373
|
+
return candidates;
|
|
374
|
+
}
|
|
375
|
+
function findValidExecutable(filePath) {
|
|
376
|
+
for (const candidate of executableCandidates(filePath)) {
|
|
377
|
+
if (existsSync(candidate) && isValidBinary(candidate)) {
|
|
378
|
+
return candidate;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
352
383
|
function getPlatformPackageName() {
|
|
353
384
|
const platform = process.platform;
|
|
354
385
|
const arch = process.arch;
|
|
@@ -363,29 +394,42 @@ function getPlatformPackageName() {
|
|
|
363
394
|
};
|
|
364
395
|
return platformMap[`${platform}-${arch}`] ?? null;
|
|
365
396
|
}
|
|
397
|
+
function isModuleResolutionFailure(error) {
|
|
398
|
+
return error instanceof Error && (error.message.includes("Cannot find module") || error.message.includes("Cannot find package"));
|
|
399
|
+
}
|
|
366
400
|
function findSgCliPathSync() {
|
|
367
|
-
const binaryName =
|
|
401
|
+
const binaryName = "sg";
|
|
368
402
|
try {
|
|
369
403
|
const require2 = createRequire(import.meta.url);
|
|
370
404
|
const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
|
|
371
405
|
const cliDirectory = dirname(cliPackageJsonPath);
|
|
372
406
|
const sgPath = join2(cliDirectory, binaryName);
|
|
373
|
-
|
|
374
|
-
|
|
407
|
+
const validSgPath = findValidExecutable(sgPath);
|
|
408
|
+
if (validSgPath) {
|
|
409
|
+
return validSgPath;
|
|
410
|
+
}
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (!isModuleResolutionFailure(error)) {
|
|
413
|
+
throw error;
|
|
375
414
|
}
|
|
376
|
-
}
|
|
415
|
+
}
|
|
377
416
|
const platformPackage = getPlatformPackageName();
|
|
378
417
|
if (platformPackage) {
|
|
379
418
|
try {
|
|
380
419
|
const require2 = createRequire(import.meta.url);
|
|
381
420
|
const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
|
|
382
421
|
const packageDirectory = dirname(packageJsonPath);
|
|
383
|
-
const astGrepBinaryName =
|
|
422
|
+
const astGrepBinaryName = "ast-grep";
|
|
384
423
|
const binaryPath = join2(packageDirectory, astGrepBinaryName);
|
|
385
|
-
|
|
386
|
-
|
|
424
|
+
const validBinaryPath = findValidExecutable(binaryPath);
|
|
425
|
+
if (validBinaryPath) {
|
|
426
|
+
return validBinaryPath;
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (!isModuleResolutionFailure(error)) {
|
|
430
|
+
throw error;
|
|
387
431
|
}
|
|
388
|
-
}
|
|
432
|
+
}
|
|
389
433
|
}
|
|
390
434
|
if (process.platform === "darwin") {
|
|
391
435
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
@@ -97,7 +97,7 @@ function getWindowsPathExtensions(env) {
|
|
|
97
97
|
.map((extension) => extension.trim())
|
|
98
98
|
.filter(Boolean)
|
|
99
99
|
.map((extension) => (extension.startsWith(".") ? extension : `.${extension}`));
|
|
100
|
-
return [...new Set([
|
|
100
|
+
return [...new Set([...extensions, ".exe", ".cmd", ".bat", ""])];
|
|
101
101
|
}
|
|
102
102
|
function resolveWindowsCommand(command, env) {
|
|
103
103
|
const hasPathSeparator = command.includes("/") || command.includes("\\");
|
|
@@ -12,6 +12,19 @@
|
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
14
|
}
|
|
15
|
+
],
|
|
16
|
+
"PostCompact": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "manual|auto",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
|
|
23
|
+
"timeout": 5,
|
|
24
|
+
"statusMessage": "LazyCodex(0.2.0): Resetting LSP Diagnostics Cache"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
15
28
|
]
|
|
16
29
|
}
|
|
17
30
|
}
|
|
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { argv, execPath, stderr } from "node:process";
|
|
5
5
|
|
|
6
|
-
import { runPostToolUseHookCli } from "./codex-hook-cli.js";
|
|
6
|
+
import { runPostCompactHookCli, runPostToolUseHookCli } from "./codex-hook-cli.js";
|
|
7
7
|
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
const PACKAGE_LSP_MCP_CLI = "@code-yeongyu/lsp-tools-mcp/dist/cli.js";
|
|
@@ -15,13 +15,17 @@ async function main(): Promise<void> {
|
|
|
15
15
|
await runPostToolUseHookCli();
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
+
if (command === "hook" && subcommand === "post-compact") {
|
|
19
|
+
await runPostCompactHookCli();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
|
|
19
23
|
if (command === "mcp") {
|
|
20
24
|
await runPackageLspMcpCli();
|
|
21
25
|
return;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
stderr.write("Usage: omo-lsp [mcp | hook post-tool-use]\n");
|
|
28
|
+
stderr.write("Usage: omo-lsp [mcp | hook post-tool-use | hook post-compact]\n");
|
|
25
29
|
process.exitCode = 2;
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -2,9 +2,20 @@ import { stdin as processStdin } from "node:process";
|
|
|
2
2
|
|
|
3
3
|
import { disposeDefaultLspManager } from "@code-yeongyu/lsp-tools-mcp/dist/lsp/manager.js";
|
|
4
4
|
|
|
5
|
-
import { isRecord, runLspPostToolUseHook } from "./codex-hook.js";
|
|
5
|
+
import { isRecord, runLspPostCompactHook, runLspPostToolUseHook } from "./codex-hook.js";
|
|
6
6
|
|
|
7
7
|
export async function runPostToolUseHookCli(stdin: NodeJS.ReadStream = processStdin): Promise<void> {
|
|
8
|
+
await runHookCli((input) => runLspPostToolUseHook(input), stdin);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runPostCompactHookCli(stdin: NodeJS.ReadStream = processStdin): Promise<void> {
|
|
12
|
+
await runHookCli((input) => runLspPostCompactHook(input), stdin);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function runHookCli(
|
|
16
|
+
runHook: (input: Record<string, unknown>) => Promise<string>,
|
|
17
|
+
stdin: NodeJS.ReadStream,
|
|
18
|
+
): Promise<void> {
|
|
8
19
|
try {
|
|
9
20
|
const raw = await readStdin(stdin);
|
|
10
21
|
if (!raw.trim()) return;
|
|
@@ -16,7 +27,7 @@ export async function runPostToolUseHookCli(stdin: NodeJS.ReadStream = processSt
|
|
|
16
27
|
throw error;
|
|
17
28
|
}
|
|
18
29
|
const input = isRecord(parsed) ? parsed : {};
|
|
19
|
-
const output = await
|
|
30
|
+
const output = await runHook(input);
|
|
20
31
|
if (output) process.stdout.write(output);
|
|
21
32
|
} finally {
|
|
22
33
|
await disposeDefaultLspManager();
|
|
@@ -2,15 +2,31 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
|
|
3
3
|
import { executeLspDiagnostics } from "@code-yeongyu/lsp-tools-mcp/dist/tools.js";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
isUnavailableLspDiagnostics,
|
|
7
|
+
markLspSessionCompacted,
|
|
8
|
+
recordLspDiagnosticsObservations,
|
|
9
|
+
sessionIdFrom,
|
|
10
|
+
shouldSkipUnavailableLspDiagnostics,
|
|
11
|
+
} from "./lsp-session-state.js";
|
|
12
|
+
import { extractMutatedFilePaths } from "./mutated-file-paths.js";
|
|
13
|
+
|
|
14
|
+
export { extractMutatedFilePaths } from "./mutated-file-paths.js";
|
|
15
|
+
|
|
5
16
|
export type DiagnosticsRunner = (filePath: string) => Promise<string>;
|
|
6
17
|
|
|
7
18
|
export interface CodexPostToolUseInput {
|
|
19
|
+
session_id?: unknown;
|
|
8
20
|
tool_name?: unknown;
|
|
9
21
|
tool_input?: unknown;
|
|
10
22
|
tool_response?: unknown;
|
|
11
23
|
transcript_path?: unknown;
|
|
12
24
|
}
|
|
13
25
|
|
|
26
|
+
export interface CodexPostCompactInput {
|
|
27
|
+
session_id?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
interface DiagnosticBlock {
|
|
15
31
|
filePath: string;
|
|
16
32
|
diagnostics: string;
|
|
@@ -25,7 +41,6 @@ interface PostToolUseHookOutput {
|
|
|
25
41
|
};
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
const MUTATION_TOOL_NAMES = new Set(["apply_patch", "write", "edit", "multiedit", "multi_edit"]);
|
|
29
44
|
const CLEAN_DIAGNOSTICS_TEXT = "No diagnostics found";
|
|
30
45
|
const UNSUPPORTED_EXTENSION_TEXT = "No LSP server configured for extension:";
|
|
31
46
|
const DIAGNOSTIC_START_PATTERN = /(?:error|warning|information|hint)\[[^\]\r\n]+\] \(\d+\) at \d+:\d+:/g;
|
|
@@ -52,14 +67,22 @@ export async function runLspPostToolUseHook(
|
|
|
52
67
|
input: CodexPostToolUseInput,
|
|
53
68
|
runDiagnostics: DiagnosticsRunner = runLspDiagnosticsText,
|
|
54
69
|
): Promise<string> {
|
|
55
|
-
const
|
|
70
|
+
const sessionId = sessionIdFrom(input);
|
|
71
|
+
const filePaths = extractMutatedFilePaths(input).filter(
|
|
72
|
+
(filePath) => !shouldSkipUnavailableLspDiagnostics(filePath, sessionId),
|
|
73
|
+
);
|
|
56
74
|
if (filePaths.length === 0) return "";
|
|
57
75
|
|
|
58
76
|
const blocks: DiagnosticBlock[] = [];
|
|
77
|
+
const observations: Array<{ filePath: string; unavailable: boolean }> = [];
|
|
59
78
|
for (const { filePath, diagnostics } of await collectDiagnostics(filePaths, runDiagnostics)) {
|
|
79
|
+
const unavailable = isUnavailableLspDiagnostics(diagnostics);
|
|
80
|
+
observations.push({ filePath, unavailable });
|
|
60
81
|
if (isCleanDiagnostics(diagnostics)) continue;
|
|
82
|
+
if (unavailable) continue;
|
|
61
83
|
blocks.push({ filePath, diagnostics });
|
|
62
84
|
}
|
|
85
|
+
recordLspDiagnosticsObservations(sessionId, observations);
|
|
63
86
|
|
|
64
87
|
if (blocks.length === 0) return "";
|
|
65
88
|
|
|
@@ -76,6 +99,11 @@ export async function runLspPostToolUseHook(
|
|
|
76
99
|
return `${JSON.stringify(output)}\n`;
|
|
77
100
|
}
|
|
78
101
|
|
|
102
|
+
export async function runLspPostCompactHook(input: CodexPostCompactInput): Promise<string> {
|
|
103
|
+
markLspSessionCompacted(sessionIdFrom(input));
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
async function collectDiagnostics(
|
|
80
108
|
filePaths: readonly string[],
|
|
81
109
|
runDiagnostics: DiagnosticsRunner,
|
|
@@ -187,29 +215,6 @@ function limitHookText(text: string, maxChars: number): string {
|
|
|
187
215
|
return `${head}${marker}`;
|
|
188
216
|
}
|
|
189
217
|
|
|
190
|
-
export function extractMutatedFilePaths(input: CodexPostToolUseInput): string[] {
|
|
191
|
-
if (!isMutationTool(input.tool_name)) return [];
|
|
192
|
-
if (isFailedToolResponse(input.tool_response)) return [];
|
|
193
|
-
|
|
194
|
-
const toolInput = isRecord(input.tool_input) ? input.tool_input : {};
|
|
195
|
-
const paths = new Set<string>();
|
|
196
|
-
addStringValue(paths, toolInput["path"]);
|
|
197
|
-
addStringValue(paths, toolInput["filePath"]);
|
|
198
|
-
addStringValue(paths, toolInput["file_path"]);
|
|
199
|
-
addStringArray(paths, toolInput["paths"]);
|
|
200
|
-
addStringArray(paths, toolInput["filePaths"]);
|
|
201
|
-
addStringArray(paths, toolInput["file_paths"]);
|
|
202
|
-
addPatchPayloads(paths, toolInput);
|
|
203
|
-
addPatchFiles(paths, toolInput["files"]);
|
|
204
|
-
addPatchFiles(paths, toolInput["changes"]);
|
|
205
|
-
return [...paths];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function isMutationTool(value: unknown): boolean {
|
|
209
|
-
if (typeof value !== "string") return false;
|
|
210
|
-
return MUTATION_TOOL_NAMES.has(value.toLowerCase());
|
|
211
|
-
}
|
|
212
|
-
|
|
213
218
|
function isCleanDiagnostics(diagnostics: string): boolean {
|
|
214
219
|
return (
|
|
215
220
|
diagnostics.length === 0 ||
|
|
@@ -218,60 +223,6 @@ function isCleanDiagnostics(diagnostics: string): boolean {
|
|
|
218
223
|
);
|
|
219
224
|
}
|
|
220
225
|
|
|
221
|
-
function isFailedToolResponse(value: unknown): boolean {
|
|
222
|
-
if (!isRecord(value)) return false;
|
|
223
|
-
return (
|
|
224
|
-
value["isError"] === true || value["is_error"] === true || value["error"] === true || value["status"] === "error"
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function addStringValue(paths: Set<string>, value: unknown): void {
|
|
229
|
-
if (typeof value === "string" && value.length > 0) {
|
|
230
|
-
paths.add(value);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function addStringArray(paths: Set<string>, value: unknown): void {
|
|
235
|
-
if (!Array.isArray(value)) return;
|
|
236
|
-
for (const item of value) {
|
|
237
|
-
addStringValue(paths, item);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function addPatchPayloads(paths: Set<string>, input: Record<string, unknown>): void {
|
|
242
|
-
addPatchInput(paths, input["input"]);
|
|
243
|
-
addPatchInput(paths, input["patch"]);
|
|
244
|
-
addPatchInput(paths, input["command"]);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function addPatchInput(paths: Set<string>, value: unknown): void {
|
|
248
|
-
if (typeof value !== "string") return;
|
|
249
|
-
for (const line of value.split("\n")) {
|
|
250
|
-
const path = extractPatchHeaderPath(line);
|
|
251
|
-
if (path !== undefined) paths.add(path);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function extractPatchHeaderPath(line: string): string | undefined {
|
|
256
|
-
const prefixes = ["*** Add File: ", "*** Update File: ", "*** Move to: "] as const;
|
|
257
|
-
for (const prefix of prefixes) {
|
|
258
|
-
if (line.startsWith(prefix)) return line.slice(prefix.length).trim();
|
|
259
|
-
}
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function addPatchFiles(paths: Set<string>, value: unknown): void {
|
|
264
|
-
if (!Array.isArray(value)) return;
|
|
265
|
-
for (const item of value) {
|
|
266
|
-
if (!isRecord(item)) continue;
|
|
267
|
-
addStringValue(paths, item["path"]);
|
|
268
|
-
addStringValue(paths, item["filePath"]);
|
|
269
|
-
addStringValue(paths, item["file_path"]);
|
|
270
|
-
addStringValue(paths, item["movePath"]);
|
|
271
|
-
addStringValue(paths, item["move_path"]);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
226
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
276
227
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
277
228
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, extname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
interface LspSessionState {
|
|
6
|
+
readonly unavailableExtensions: readonly string[];
|
|
7
|
+
readonly postCompactProbePending?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DiagnosticsObservation {
|
|
11
|
+
readonly filePath: string;
|
|
12
|
+
readonly unavailable: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function sessionIdFrom(input: { readonly session_id?: unknown }): string | undefined {
|
|
16
|
+
return typeof input.session_id === "string" && input.session_id.length > 0 ? input.session_id : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function shouldSkipUnavailableLspDiagnostics(filePath: string, sessionId: string | undefined): boolean {
|
|
20
|
+
if (sessionId === undefined) return false;
|
|
21
|
+
const state = readSessionState(sessionStatePath(sessionId));
|
|
22
|
+
const extension = extensionKey(filePath);
|
|
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() });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function markLspSessionCompacted(sessionId: string | undefined): void {
|
|
52
|
+
if (sessionId === undefined) return;
|
|
53
|
+
const state = readSessionState(sessionStatePath(sessionId));
|
|
54
|
+
if (state.unavailableExtensions.length === 0) return;
|
|
55
|
+
writeSessionState(sessionStatePath(sessionId), {
|
|
56
|
+
unavailableExtensions: state.unavailableExtensions,
|
|
57
|
+
postCompactProbePending: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isUnavailableLspDiagnostics(diagnostics: string): boolean {
|
|
62
|
+
const normalized = diagnostics.trim();
|
|
63
|
+
return (
|
|
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
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sessionStatePath(sessionId: string): string {
|
|
72
|
+
const root = process.env["PLUGIN_DATA"] ?? join(homedir(), ".codex", "codex-lsp");
|
|
73
|
+
return join(root, "sessions", `${safePathSegment(sessionId)}.json`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readSessionState(path: string): LspSessionState {
|
|
77
|
+
try {
|
|
78
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
79
|
+
if (isLspSessionState(parsed)) return parsed;
|
|
80
|
+
return emptyState();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof SyntaxError || (isRecord(error) && error["code"] === "ENOENT")) return emptyState();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeSessionState(path: string, state: LspSessionState): void {
|
|
88
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
89
|
+
writeFileSync(path, `${JSON.stringify(state)}\n`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function emptyState(): LspSessionState {
|
|
93
|
+
return { unavailableExtensions: [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function extensionKey(filePath: string): string | undefined {
|
|
97
|
+
const extension = extname(filePath).toLowerCase();
|
|
98
|
+
return extension.length === 0 ? undefined : extension;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function safePathSegment(value: string): string {
|
|
102
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120) || "unknown-session";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isLspSessionState(value: unknown): value is LspSessionState {
|
|
106
|
+
if (!isRecord(value) || !Array.isArray(value["unavailableExtensions"])) return false;
|
|
107
|
+
const postCompactProbePending = value["postCompactProbePending"];
|
|
108
|
+
return (
|
|
109
|
+
value["unavailableExtensions"].every((item) => typeof item === "string") &&
|
|
110
|
+
(postCompactProbePending === undefined || typeof postCompactProbePending === "boolean")
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
116
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const MUTATION_TOOL_NAMES = new Set(["apply_patch", "write", "edit", "multiedit", "multi_edit"]);
|
|
2
|
+
|
|
3
|
+
export interface MutatedFileInput {
|
|
4
|
+
readonly tool_name?: unknown;
|
|
5
|
+
readonly tool_input?: unknown;
|
|
6
|
+
readonly tool_response?: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function extractMutatedFilePaths(input: MutatedFileInput): string[] {
|
|
10
|
+
if (!isMutationTool(input.tool_name)) return [];
|
|
11
|
+
if (isFailedToolResponse(input.tool_response)) return [];
|
|
12
|
+
|
|
13
|
+
const toolInput = isRecord(input.tool_input) ? input.tool_input : {};
|
|
14
|
+
const paths = new Set<string>();
|
|
15
|
+
addStringValue(paths, toolInput["path"]);
|
|
16
|
+
addStringValue(paths, toolInput["filePath"]);
|
|
17
|
+
addStringValue(paths, toolInput["file_path"]);
|
|
18
|
+
addStringArray(paths, toolInput["paths"]);
|
|
19
|
+
addStringArray(paths, toolInput["filePaths"]);
|
|
20
|
+
addStringArray(paths, toolInput["file_paths"]);
|
|
21
|
+
addPatchPayloads(paths, toolInput);
|
|
22
|
+
addPatchFiles(paths, toolInput["files"]);
|
|
23
|
+
addPatchFiles(paths, toolInput["changes"]);
|
|
24
|
+
return [...paths];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isMutationTool(value: unknown): boolean {
|
|
28
|
+
if (typeof value !== "string") return false;
|
|
29
|
+
return MUTATION_TOOL_NAMES.has(value.toLowerCase());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isFailedToolResponse(value: unknown): boolean {
|
|
33
|
+
if (!isRecord(value)) return false;
|
|
34
|
+
return (
|
|
35
|
+
value["isError"] === true || value["is_error"] === true || value["error"] === true || value["status"] === "error"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addStringValue(paths: Set<string>, value: unknown): void {
|
|
40
|
+
if (typeof value === "string" && value.length > 0) {
|
|
41
|
+
paths.add(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function addStringArray(paths: Set<string>, value: unknown): void {
|
|
46
|
+
if (!Array.isArray(value)) return;
|
|
47
|
+
for (const item of value) {
|
|
48
|
+
addStringValue(paths, item);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function addPatchPayloads(paths: Set<string>, input: Record<string, unknown>): void {
|
|
53
|
+
addPatchInput(paths, input["input"]);
|
|
54
|
+
addPatchInput(paths, input["patch"]);
|
|
55
|
+
addPatchInput(paths, input["command"]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addPatchInput(paths: Set<string>, value: unknown): void {
|
|
59
|
+
if (typeof value !== "string") return;
|
|
60
|
+
for (const line of value.split("\n")) {
|
|
61
|
+
const path = extractPatchHeaderPath(line);
|
|
62
|
+
if (path !== undefined) paths.add(path);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractPatchHeaderPath(line: string): string | undefined {
|
|
67
|
+
const prefixes = ["*** Add File: ", "*** Update File: ", "*** Move to: "] as const;
|
|
68
|
+
for (const prefix of prefixes) {
|
|
69
|
+
if (line.startsWith(prefix)) return line.slice(prefix.length).trim();
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function addPatchFiles(paths: Set<string>, value: unknown): void {
|
|
75
|
+
if (!Array.isArray(value)) return;
|
|
76
|
+
for (const item of value) {
|
|
77
|
+
if (!isRecord(item)) continue;
|
|
78
|
+
addStringValue(paths, item["path"]);
|
|
79
|
+
addStringValue(paths, item["filePath"]);
|
|
80
|
+
addStringValue(paths, item["file_path"]);
|
|
81
|
+
addStringValue(paths, item["movePath"]);
|
|
82
|
+
addStringValue(paths, item["move_path"]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
87
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|