oh-my-opencode 4.19.0 → 4.19.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/command/omomomo.md +1 -1
- package/.agents/skills/omomomo/SKILL.md +1 -1
- package/.opencode/command/omomomo.md +1 -1
- package/README.ja.md +6 -6
- package/README.ko.md +6 -6
- package/README.md +6 -6
- package/README.ru.md +6 -6
- package/README.zh-cn.md +6 -6
- package/dist/agents/atlas/agent.d.ts +5 -3
- package/dist/agents/explore.d.ts +4 -2
- package/dist/agents/gpt-prompt-identity.d.ts +3 -0
- package/dist/agents/hephaestus/agent.d.ts +4 -2
- package/dist/agents/librarian.d.ts +4 -2
- package/dist/agents/metis.d.ts +4 -2
- package/dist/agents/momus.d.ts +3 -2
- package/dist/agents/multimodal-looker.d.ts +4 -2
- package/dist/agents/oracle.d.ts +4 -2
- package/dist/agents/sisyphus/claude-opus-5.d.ts +33 -0
- package/dist/agents/sisyphus/gpt-5-5.d.ts +1 -1
- package/dist/agents/sisyphus/gpt-task-system-guide.d.ts +1 -0
- package/dist/agents/sisyphus/index.d.ts +2 -0
- package/dist/agents/sisyphus-agent-factory.d.ts +5 -2
- package/dist/agents/sisyphus-junior/agent.d.ts +4 -1
- package/dist/agents/sisyphus-junior/gpt-5-5.d.ts +2 -2
- package/dist/agents/types.d.ts +2 -2
- package/dist/cli/index.js +23577 -19269
- package/dist/cli-node/index.js +23577 -19269
- package/dist/config/schema/agent-names.d.ts +26 -26
- package/dist/config/schema/agent-overrides.d.ts +400 -400
- package/dist/config/schema/browser-automation.d.ts +2 -2
- package/dist/config/schema/categories.d.ts +36 -36
- package/dist/config/schema/codegraph.d.ts +2 -0
- package/dist/config/schema/commands.d.ts +2 -2
- package/dist/config/schema/dynamic-context-pruning.d.ts +1 -1
- package/dist/config/schema/experimental.d.ts +1 -1
- package/dist/config/schema/fallback-models.d.ts +25 -25
- package/dist/config/schema/hooks.d.ts +43 -43
- package/dist/config/schema/internal/permission.d.ts +9 -9
- package/dist/config/schema/keyword-detector.d.ts +6 -6
- package/dist/config/schema/oh-my-opencode-config.d.ts +404 -402
- package/dist/config/schema/tmux.d.ts +6 -6
- package/dist/create-hooks.d.ts +35 -35
- package/dist/features/background-agent/spawner/tmux-callback-invoker.d.ts +11 -0
- package/dist/features/builtin-commands/templates/start-work.d.ts +1 -1
- package/dist/features/claude-tasks/types.d.ts +4 -4
- package/dist/features/team-mode/team-runtime/shutdown-helpers.d.ts +1 -1
- package/dist/features/team-mode/team-runtime/shutdown-test-fixtures.d.ts +4 -4
- package/dist/features/team-mode/tools/lifecycle-test-fixture.d.ts +42 -42
- package/dist/features/tmux-subagent/environment-eligibility.d.ts +2 -0
- package/dist/features/tmux-subagent/index.d.ts +1 -0
- package/dist/features/tmux-subagent/tracked-session-state.d.ts +1 -0
- package/dist/features/tui-sidebar/snapshot-schema.d.ts +6 -6
- package/dist/help/schema/acp.d.ts +3 -3
- package/dist/help/schema/doctor.d.ts +4 -4
- package/dist/help/schema/status.d.ts +3 -3
- package/dist/hooks/codegraph-bootstrap/hook.d.ts +3 -2
- package/dist/hooks/goal/types.d.ts +5 -5
- package/dist/hooks/non-interactive-env/constants.d.ts +4 -1
- package/dist/hooks/sisyphus-junior-notepad/constants.d.ts +1 -1
- package/dist/hooks/start-work/notepad-scaffold.d.ts +10 -0
- package/dist/hooks/start-work/parse-user-request.d.ts +2 -0
- package/dist/hooks/start-work/worktree-block.d.ts +5 -0
- package/dist/index.js +27370 -21301
- package/dist/mcp/types.d.ts +3 -3
- package/dist/oh-my-opencode.schema.json +11 -0
- package/dist/plugin/chat-message/loop-commands.d.ts +1 -0
- package/dist/plugin/command-execute-before.d.ts +3 -3
- package/dist/plugin/hooks/create-core-hooks.d.ts +25 -25
- package/dist/shared/omo-process-sweep.d.ts +18 -0
- package/dist/shared/tmux/tmux-utils/environment.d.ts +1 -1
- package/dist/shared/tmux/tmux-utils.d.ts +1 -1
- package/dist/shared/typescript-native-source-parser.d.ts +7 -0
- package/dist/skills/coding-agent-sessions/SKILL.md +5 -2
- package/dist/skills/coding-agent-sessions/references/all-platforms.md +3 -1
- package/dist/skills/coding-agent-sessions/references/senpi.md +15 -5
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +46 -0
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +19 -10
- package/dist/skills/debugging/SKILL.md +2 -1
- package/dist/skills/debugging/references/methodology/03-flaky-triage.md +47 -0
- package/dist/skills/programming/SKILL.md +11 -1
- package/dist/skills/programming/references/logging.md +6 -1
- package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.test.ts +87 -0
- package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.ts +75 -11
- package/dist/skills/start-work/SKILL.md +12 -2
- package/dist/skills/ulw-plan/SKILL.md +21 -2
- package/dist/skills/ulw-plan/references/full-workflow.md +12 -1
- package/dist/skills/ulw-plan/references/intent-clear.md +1 -1
- package/dist/testing/create-plugin-module.d.ts +1 -0
- package/dist/tools/task/types.d.ts +10 -10
- package/dist/tui.js +23442 -19182
- package/package.json +14 -14
- package/packages/git-bash-mcp/dist/cli.js +54 -8
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +62 -2
- package/packages/lsp-core/src/lsp/client.ts +14 -1
- package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +2 -2
- package/packages/lsp-core/src/mcp.ts +9 -0
- package/packages/lsp-daemon/dist/cli.js +319 -51
- package/packages/lsp-daemon/dist/client.js +136 -48
- package/packages/lsp-daemon/dist/daemon-client.d.ts +0 -10
- package/packages/lsp-daemon/dist/daemon-client.js +4 -34
- package/packages/lsp-daemon/dist/daemon-failure-result.d.ts +3 -0
- package/packages/lsp-daemon/dist/daemon-failure-result.js +38 -0
- package/packages/lsp-daemon/dist/daemon-request-error.d.ts +14 -0
- package/packages/lsp-daemon/dist/daemon-request-error.js +26 -0
- package/packages/lsp-daemon/dist/daemon-server.js +5 -1
- package/packages/lsp-daemon/dist/index.js +137 -48
- package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
- package/packages/lsp-daemon/dist/proxy.js +1 -0
- package/packages/lsp-daemon/dist/version-reap.d.ts +26 -0
- package/packages/lsp-daemon/dist/version-reap.js +182 -0
- package/packages/lsp-tools-mcp/dist/cli.js +61 -10
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +6 -1
- package/packages/lsp-tools-mcp/dist/mcp.js +61 -10
- package/packages/lsp-tools-mcp/dist/tools.js +6 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +72 -12
- 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/bootstrap/src/setup.ts +22 -2
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +779 -385
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +302 -173
- package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +3 -1
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +23 -42
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +98 -32
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +5 -0
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +17 -9
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +18 -25
- package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +1 -0
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +5 -0
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +176 -0
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +6 -1
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +31 -2
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +6 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +65 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -7
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +2 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +57 -3
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +42 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +122 -10
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +2 -2
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +101 -64
- 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/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +80 -40
- 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/rules/dist/cli.js +11 -0
- 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/rules/src/post-compact-budget.ts +11 -0
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +60 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
- 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/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/agents/lazycodex-clone-fidelity-reviewer.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +12 -7
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +1 -1
- 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 +12 -7
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +21 -2
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +12 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/codex-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +42 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +6 -4
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +12 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.d.ts +21 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +64 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +15 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.js +85 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +0 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +13 -33
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +998 -593
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/goal-status.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/goal-status.js +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +10 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +84 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +81 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +7 -59
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +12 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/validation-batch.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/validation-batch.js +122 -0
- 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 +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +8 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +84 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +13 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts +49 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +13 -45
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +4 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +20 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +125 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +59 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +6 -58
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +12 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts +122 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +76 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +111 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +67 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +57 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +152 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +29 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/validation-batch-checkpoint.test.ts +147 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/validation-batch.test.ts +73 -0
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +41 -41
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +44 -0
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +5 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +3 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +15 -5
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +46 -0
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +19 -10
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -1
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/03-flaky-triage.md +47 -0
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +11 -1
- package/packages/omo-codex/plugin/skills/programming/references/logging.md +6 -1
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +75 -11
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +12 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +12 -7
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +8 -6
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +21 -2
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +12 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +1 -1
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +9 -9
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +26 -0
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +41 -0
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +22 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +49 -5
- package/packages/omo-codex/scripts/install-marketplace-cache.test.mjs +4 -1
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +5 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +3 -1
- package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +15 -5
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +46 -0
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +19 -10
- package/packages/shared-skills/skills/debugging/SKILL.md +2 -1
- package/packages/shared-skills/skills/debugging/references/methodology/03-flaky-triage.md +47 -0
- package/packages/shared-skills/skills/programming/SKILL.md +11 -1
- package/packages/shared-skills/skills/programming/references/logging.md +6 -1
- package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.test.ts +87 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +75 -11
- package/packages/shared-skills/skills/start-work/SKILL.md +12 -2
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +21 -2
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +12 -1
- package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +1 -1
|
@@ -2613,8 +2613,13 @@ class LspClient extends LspClientConnection {
|
|
|
2613
2613
|
if (!pushFallbackOnly)
|
|
2614
2614
|
continue;
|
|
2615
2615
|
const remainingMs = deadlineAt - Date.now();
|
|
2616
|
-
if (remainingMs <= 0)
|
|
2616
|
+
if (remainingMs <= 0) {
|
|
2617
|
+
if (!this.isDiagnosticPullSupported() && snapshot.publishGeneration === 0) {
|
|
2618
|
+
const cached = this.documents.getPullCache(snapshot);
|
|
2619
|
+
return { items: cached === null ? [] : [...cached.diagnostics] };
|
|
2620
|
+
}
|
|
2617
2621
|
return this.freshnessTimeout(absPath);
|
|
2622
|
+
}
|
|
2618
2623
|
const waitMs = push.status === "wait" ? Math.min(push.waitMs, remainingMs) : remainingMs;
|
|
2619
2624
|
await waitForDiagnosticsActivity(this.documents.waitForDiagnosticsActivity(snapshot, waitMs), signal);
|
|
2620
2625
|
}
|
|
@@ -3184,6 +3189,81 @@ function isPlainRecord(value) {
|
|
|
3184
3189
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3185
3190
|
}
|
|
3186
3191
|
|
|
3192
|
+
// src/daemon-request-error.ts
|
|
3193
|
+
class DaemonRequestError extends Error {
|
|
3194
|
+
requestWritten;
|
|
3195
|
+
constructor(message, requestWritten) {
|
|
3196
|
+
super(message);
|
|
3197
|
+
this.name = "DaemonRequestError";
|
|
3198
|
+
this.requestWritten = requestWritten;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
class DaemonAuthenticationRejectedError extends DaemonRequestError {
|
|
3203
|
+
constructor() {
|
|
3204
|
+
super("daemon authentication failed before dispatch", true);
|
|
3205
|
+
this.name = "DaemonAuthenticationRejectedError";
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
class DaemonRequestCancelledError extends DaemonRequestError {
|
|
3210
|
+
constructor(requestWritten) {
|
|
3211
|
+
super("daemon request cancelled", requestWritten);
|
|
3212
|
+
this.name = "DaemonRequestCancelledError";
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
class DaemonRequestTimedOutError extends DaemonRequestError {
|
|
3217
|
+
timeoutMs;
|
|
3218
|
+
constructor(requestWritten, timeoutMs) {
|
|
3219
|
+
super("daemon request timed out", requestWritten);
|
|
3220
|
+
this.name = "DaemonRequestTimedOutError";
|
|
3221
|
+
this.timeoutMs = timeoutMs;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
// src/daemon-failure-result.ts
|
|
3226
|
+
function daemonFailureResult(paths, error) {
|
|
3227
|
+
if (error instanceof DaemonRequestCancelledError)
|
|
3228
|
+
return daemonCancelledResult(paths);
|
|
3229
|
+
if (error instanceof DaemonRequestTimedOutError)
|
|
3230
|
+
return daemonTimedOutResult(paths, error.timeoutMs);
|
|
3231
|
+
return daemonUnreachableResult(paths, error);
|
|
3232
|
+
}
|
|
3233
|
+
function daemonCancelledResult(paths) {
|
|
3234
|
+
const text = [
|
|
3235
|
+
"LSP daemon request cancelled: the caller aborted this request (for example, the turn was interrupted).",
|
|
3236
|
+
"The daemon stays available; no LSP work was applied. Retry when you are ready.",
|
|
3237
|
+
`Socket: ${paths.socket}`
|
|
3238
|
+
].join(`
|
|
3239
|
+
`);
|
|
3240
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
3241
|
+
}
|
|
3242
|
+
function daemonTimedOutResult(paths, timeoutMs) {
|
|
3243
|
+
const text = [
|
|
3244
|
+
`LSP daemon request timed out after ${timeoutMs}ms: the daemon did not respond in time.`,
|
|
3245
|
+
"The daemon stays available but may be busy. Retry when you are ready.",
|
|
3246
|
+
`Socket: ${paths.socket}`,
|
|
3247
|
+
`Logs: ${paths.log}`
|
|
3248
|
+
].join(`
|
|
3249
|
+
`);
|
|
3250
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
3251
|
+
}
|
|
3252
|
+
function daemonUnreachableResult(paths, error) {
|
|
3253
|
+
const text = [
|
|
3254
|
+
`LSP daemon unreachable: ${errorText(error)}.`,
|
|
3255
|
+
"The MCP server is a thin proxy and never runs language servers in-process.",
|
|
3256
|
+
`Socket: ${paths.socket}`,
|
|
3257
|
+
`Logs: ${paths.log}`,
|
|
3258
|
+
"The daemon is auto-started on demand and will be retried on the next request."
|
|
3259
|
+
].join(`
|
|
3260
|
+
`);
|
|
3261
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
3262
|
+
}
|
|
3263
|
+
function errorText(error) {
|
|
3264
|
+
return error instanceof Error ? error.message : String(error);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3187
3267
|
// src/ensure-daemon.ts
|
|
3188
3268
|
import { spawn as spawn2 } from "node:child_process";
|
|
3189
3269
|
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2 } from "node:fs";
|
|
@@ -3911,16 +3991,35 @@ function bufferFromChunk(chunk) {
|
|
|
3911
3991
|
|
|
3912
3992
|
// ../mcp-stdio-core/src/server.ts
|
|
3913
3993
|
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60000;
|
|
3994
|
+
var DEFAULT_PARENT_POLL_INTERVAL_MS = 30000;
|
|
3914
3995
|
var noopLog = () => {};
|
|
3996
|
+
function isProcessAlive(pid) {
|
|
3997
|
+
try {
|
|
3998
|
+
process.kill(pid, 0);
|
|
3999
|
+
return true;
|
|
4000
|
+
} catch (error) {
|
|
4001
|
+
return !hasErrorCode(error, "ESRCH");
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
3915
4004
|
async function runJsonRpcStdioServer(config) {
|
|
3916
4005
|
const log = config.log ?? noopLog;
|
|
3917
4006
|
const idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
3918
|
-
|
|
4007
|
+
let isClosed = false;
|
|
4008
|
+
const idleTimer = createIdleTimer(idleTimeoutMs, log, () => {
|
|
4009
|
+
isClosed = true;
|
|
4010
|
+
config.onIdleTimeout?.();
|
|
4011
|
+
});
|
|
4012
|
+
const watchdog = createParentWatchdog(config.parentWatchdog, (parentPid, pollIntervalMs) => {
|
|
4013
|
+
isClosed = true;
|
|
4014
|
+
log("parent_exit", { parent_pid: parentPid, poll_interval_ms: pollIntervalMs });
|
|
4015
|
+
config.onParentExit?.();
|
|
4016
|
+
config.input.destroy();
|
|
4017
|
+
});
|
|
3919
4018
|
log("stdio_started", { cwd: process.cwd(), idle_timeout_ms: idleTimeoutMs });
|
|
3920
4019
|
idleTimer.arm();
|
|
3921
4020
|
try {
|
|
3922
4021
|
for await (const message of readStdioJsonRpcMessages(config.input)) {
|
|
3923
|
-
if (
|
|
4022
|
+
if (isClosed)
|
|
3924
4023
|
break;
|
|
3925
4024
|
idleTimer.arm();
|
|
3926
4025
|
if (message.kind === "parse_error") {
|
|
@@ -3931,8 +4030,12 @@ async function runJsonRpcStdioServer(config) {
|
|
|
3931
4030
|
if (!await handleRequest(message, config, log))
|
|
3932
4031
|
break;
|
|
3933
4032
|
}
|
|
4033
|
+
} catch (error) {
|
|
4034
|
+
if (!(isClosed && hasErrorCode(error, "ERR_STREAM_PREMATURE_CLOSE")))
|
|
4035
|
+
throw error;
|
|
3934
4036
|
} finally {
|
|
3935
4037
|
idleTimer.clear();
|
|
4038
|
+
watchdog.clear();
|
|
3936
4039
|
log("stdio_stopped");
|
|
3937
4040
|
}
|
|
3938
4041
|
}
|
|
@@ -3988,9 +4091,33 @@ function isTerminalOutputError(error) {
|
|
|
3988
4091
|
return false;
|
|
3989
4092
|
return error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED" || error.code === "ERR_STREAM_WRITE_AFTER_END";
|
|
3990
4093
|
}
|
|
3991
|
-
function
|
|
4094
|
+
function hasErrorCode(error, code) {
|
|
4095
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
4096
|
+
}
|
|
4097
|
+
function createParentWatchdog(config, onDeadParent) {
|
|
4098
|
+
if (config === undefined)
|
|
4099
|
+
return { clear: () => {} };
|
|
4100
|
+
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_PARENT_POLL_INTERVAL_MS;
|
|
4101
|
+
if (pollIntervalMs <= 0)
|
|
4102
|
+
return { clear: () => {} };
|
|
4103
|
+
const parentPid = config.parentPid ?? process.ppid;
|
|
4104
|
+
const probeAlive = config.probeAlive ?? isProcessAlive;
|
|
4105
|
+
let fired = false;
|
|
4106
|
+
const timer = setInterval(() => {
|
|
4107
|
+
if (fired || probeAlive(parentPid))
|
|
4108
|
+
return;
|
|
4109
|
+
fired = true;
|
|
4110
|
+
onDeadParent(parentPid, pollIntervalMs);
|
|
4111
|
+
}, pollIntervalMs);
|
|
4112
|
+
timer.unref();
|
|
4113
|
+
return {
|
|
4114
|
+
clear: () => {
|
|
4115
|
+
clearInterval(timer);
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
function createIdleTimer(idleTimeoutMs, log, onTimeout) {
|
|
3992
4120
|
let timer = null;
|
|
3993
|
-
let isClosed = false;
|
|
3994
4121
|
return {
|
|
3995
4122
|
arm: () => {
|
|
3996
4123
|
if (timer !== null)
|
|
@@ -3998,9 +4125,8 @@ function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
|
|
|
3998
4125
|
if (idleTimeoutMs <= 0)
|
|
3999
4126
|
return;
|
|
4000
4127
|
timer = setTimeout(() => {
|
|
4001
|
-
isClosed = true;
|
|
4002
4128
|
log("idle_timeout", { idle_timeout_ms: idleTimeoutMs });
|
|
4003
|
-
|
|
4129
|
+
onTimeout();
|
|
4004
4130
|
}, idleTimeoutMs);
|
|
4005
4131
|
timer.unref();
|
|
4006
4132
|
},
|
|
@@ -4009,8 +4135,7 @@ function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
|
|
|
4009
4135
|
return;
|
|
4010
4136
|
clearTimeout(timer);
|
|
4011
4137
|
timer = null;
|
|
4012
|
-
}
|
|
4013
|
-
closed: () => isClosed
|
|
4138
|
+
}
|
|
4014
4139
|
};
|
|
4015
4140
|
}
|
|
4016
4141
|
// ../lsp-core/src/lsp/client-wrapper.ts
|
|
@@ -5714,29 +5839,6 @@ function parseContext(value) {
|
|
|
5714
5839
|
// src/daemon-client.ts
|
|
5715
5840
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
5716
5841
|
var nextProxyRequestId = 1;
|
|
5717
|
-
|
|
5718
|
-
class DaemonRequestError extends Error {
|
|
5719
|
-
requestWritten;
|
|
5720
|
-
constructor(message, requestWritten) {
|
|
5721
|
-
super(message);
|
|
5722
|
-
this.name = "DaemonRequestError";
|
|
5723
|
-
this.requestWritten = requestWritten;
|
|
5724
|
-
}
|
|
5725
|
-
}
|
|
5726
|
-
|
|
5727
|
-
class DaemonAuthenticationRejectedError extends DaemonRequestError {
|
|
5728
|
-
constructor() {
|
|
5729
|
-
super("daemon authentication failed before dispatch", true);
|
|
5730
|
-
this.name = "DaemonAuthenticationRejectedError";
|
|
5731
|
-
}
|
|
5732
|
-
}
|
|
5733
|
-
|
|
5734
|
-
class DaemonRequestCancelledError extends DaemonRequestError {
|
|
5735
|
-
constructor(requestWritten) {
|
|
5736
|
-
super("daemon request cancelled", requestWritten);
|
|
5737
|
-
this.name = "DaemonRequestCancelledError";
|
|
5738
|
-
}
|
|
5739
|
-
}
|
|
5740
5842
|
async function callToolViaDaemon(name, args, options) {
|
|
5741
5843
|
const context = requireContext(options.context);
|
|
5742
5844
|
const paths = options.paths ?? daemonPaths();
|
|
@@ -5765,7 +5867,7 @@ async function callToolViaDaemon(name, args, options) {
|
|
|
5765
5867
|
break;
|
|
5766
5868
|
}
|
|
5767
5869
|
}
|
|
5768
|
-
return
|
|
5870
|
+
return daemonFailureResult(paths, lastError);
|
|
5769
5871
|
}
|
|
5770
5872
|
function callDiagnosticsViaDaemon(filePath, options) {
|
|
5771
5873
|
return callToolViaDaemon("diagnostics", { filePath, severity: "error" }, options);
|
|
@@ -5810,17 +5912,6 @@ function ensureDaemonAvailable(paths, ensure, signal) {
|
|
|
5810
5912
|
Promise.resolve().then(() => ensure(paths, signal)).then(() => finish(() => resolve9()), (error) => finish(() => reject(signal.aborted ? new DaemonRequestCancelledError(false) : error)));
|
|
5811
5913
|
});
|
|
5812
5914
|
}
|
|
5813
|
-
function daemonUnreachableResult(paths, error) {
|
|
5814
|
-
const text2 = [
|
|
5815
|
-
`LSP daemon unreachable: ${errorText(error)}.`,
|
|
5816
|
-
"The MCP server is a thin proxy and never runs language servers in-process.",
|
|
5817
|
-
`Socket: ${paths.socket}`,
|
|
5818
|
-
`Logs: ${paths.log}`,
|
|
5819
|
-
"The daemon is auto-started on demand and will be retried on the next request."
|
|
5820
|
-
].join(`
|
|
5821
|
-
`);
|
|
5822
|
-
return { content: [{ type: "text", text: text2 }], isError: true };
|
|
5823
|
-
}
|
|
5824
5915
|
function sendToolCall(paths, token, name, args, options) {
|
|
5825
5916
|
return new Promise((resolve9, reject) => {
|
|
5826
5917
|
const socket = connect(paths.socket);
|
|
@@ -5860,7 +5951,7 @@ function sendToolCall(paths, token, name, args, options) {
|
|
|
5860
5951
|
};
|
|
5861
5952
|
const timer = setTimeout(() => {
|
|
5862
5953
|
sendCancel();
|
|
5863
|
-
finish(() => reject(new
|
|
5954
|
+
finish(() => reject(new DaemonRequestTimedOutError(requestWritten, options.timeoutMs)));
|
|
5864
5955
|
}, options.timeoutMs);
|
|
5865
5956
|
timer.unref();
|
|
5866
5957
|
if (options.signal?.aborted) {
|
|
@@ -5919,9 +6010,6 @@ function allocateProxyRequestId() {
|
|
|
5919
6010
|
function isRetryableTool(name) {
|
|
5920
6011
|
return name !== "rename" && name !== "lsp_rename";
|
|
5921
6012
|
}
|
|
5922
|
-
function errorText(error) {
|
|
5923
|
-
return error instanceof Error ? error.message : String(error);
|
|
5924
|
-
}
|
|
5925
6013
|
// src/proxy.ts
|
|
5926
6014
|
import { existsSync as existsSync11, realpathSync as realpathSync4 } from "node:fs";
|
|
5927
6015
|
import { basename as basename3, delimiter as delimiter4, dirname as dirname9, isAbsolute as isAbsolute4 } from "node:path";
|
|
@@ -5979,6 +6067,7 @@ async function runMcpStdioProxy(options = {}) {
|
|
|
5979
6067
|
input,
|
|
5980
6068
|
output,
|
|
5981
6069
|
idleTimeoutMs: 0,
|
|
6070
|
+
parentWatchdog: options.parentWatchdog ?? {},
|
|
5982
6071
|
handler: (request, requestOptions) => {
|
|
5983
6072
|
clearStartupWatchdog();
|
|
5984
6073
|
return runWithRequestContext(context, () => handleProxyRequest(request, requestOptions));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import { type ParentWatchdogConfig } from "@oh-my-opencode/mcp-stdio-core";
|
|
2
3
|
import { type CallToolOptions, type DaemonToolContext } from "./daemon-client.js";
|
|
3
4
|
import { type DaemonPaths } from "./paths.js";
|
|
4
5
|
export interface ProxyOptions {
|
|
@@ -12,5 +13,6 @@ export interface ProxyOptions {
|
|
|
12
13
|
homeDir?: string;
|
|
13
14
|
ensure?: CallToolOptions["ensure"];
|
|
14
15
|
startupTimeoutMs?: number;
|
|
16
|
+
parentWatchdog?: ParentWatchdogConfig;
|
|
15
17
|
}
|
|
16
18
|
export declare function runMcpStdioProxy(options?: ProxyOptions): Promise<void>;
|
|
@@ -59,6 +59,7 @@ export async function runMcpStdioProxy(options = {}) {
|
|
|
59
59
|
input,
|
|
60
60
|
output,
|
|
61
61
|
idleTimeoutMs: 0,
|
|
62
|
+
parentWatchdog: options.parentWatchdog ?? {},
|
|
62
63
|
handler: (request, requestOptions) => {
|
|
63
64
|
clearStartupWatchdog();
|
|
64
65
|
return runWithRequestContext(context, () => handleProxyRequest(request, requestOptions));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type DaemonOwner } from "./ownership.js";
|
|
2
|
+
import { type DaemonPaths } from "./paths.js";
|
|
3
|
+
export interface DaemonCliAttestationDeps {
|
|
4
|
+
readonly readProcFile?: (path: string) => Promise<Buffer>;
|
|
5
|
+
readonly executeForStdout?: (file: string, args: readonly string[]) => Promise<string | null>;
|
|
6
|
+
}
|
|
7
|
+
export interface ReapStaleDaemonVersionsDeps {
|
|
8
|
+
readonly platform?: NodeJS.Platform;
|
|
9
|
+
readonly isAlive?: (pid: number) => boolean;
|
|
10
|
+
readonly attest?: (pid: number, platform: NodeJS.Platform) => Promise<boolean>;
|
|
11
|
+
readonly sendSignal?: (pid: number, signal: NodeJS.Signals) => boolean;
|
|
12
|
+
readonly waitForExit?: (pid: number, timeoutMs: number) => Promise<boolean>;
|
|
13
|
+
readonly removeDir?: (path: string) => Promise<void>;
|
|
14
|
+
readonly readOwner?: (paths: DaemonPaths) => DaemonOwner | null;
|
|
15
|
+
readonly log?: (message: string) => void;
|
|
16
|
+
readonly termGraceMs?: number;
|
|
17
|
+
readonly killGraceMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export type VersionReapStatus = "terminated" | "removed" | "deferred" | "spared";
|
|
20
|
+
export interface VersionReapResult {
|
|
21
|
+
readonly version: string;
|
|
22
|
+
readonly status: VersionReapStatus;
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function attestDaemonCliProcess(pid: number, platform: NodeJS.Platform, deps?: DaemonCliAttestationDeps): Promise<boolean>;
|
|
26
|
+
export declare function reapStaleDaemonVersions(ownPaths: DaemonPaths, deps?: ReapStaleDaemonVersionsDeps): Promise<readonly VersionReapResult[]>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readdir, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { isProcessAlive, readLockPid } from "./lock.js";
|
|
5
|
+
import { readDaemonOwner } from "./ownership.js";
|
|
6
|
+
import { daemonPaths, OMO_LSP_DAEMON_DIR } from "./paths.js";
|
|
7
|
+
const TERM_GRACE_MS = 5_000;
|
|
8
|
+
const KILL_GRACE_MS = 1_000;
|
|
9
|
+
const VERSION_ENTRY_PATTERN = /^v([A-Za-z0-9][A-Za-z0-9._+-]{0,127})$/;
|
|
10
|
+
export async function attestDaemonCliProcess(pid, platform, deps = {}) {
|
|
11
|
+
if (platform === "win32")
|
|
12
|
+
return false;
|
|
13
|
+
if (platform === "linux") {
|
|
14
|
+
const readProcFile = deps.readProcFile ?? defaultReadProcFile;
|
|
15
|
+
const cmdline = await readProcFile(`/proc/${pid}/cmdline`).catch(() => null);
|
|
16
|
+
if (cmdline === null)
|
|
17
|
+
return false;
|
|
18
|
+
return isNodeCliDaemonArgv(splitCmdline(cmdline));
|
|
19
|
+
}
|
|
20
|
+
const executeForStdout = deps.executeForStdout ?? defaultExecuteForStdout;
|
|
21
|
+
const command = await executeForStdout("/bin/ps", ["-p", String(pid), "-o", "command="]);
|
|
22
|
+
if (command === null)
|
|
23
|
+
return false;
|
|
24
|
+
return isNodeCliDaemonCommand(command.trim());
|
|
25
|
+
}
|
|
26
|
+
export async function reapStaleDaemonVersions(ownPaths, deps = {}) {
|
|
27
|
+
const baseDir = dirname(ownPaths.dir);
|
|
28
|
+
const platform = deps.platform ?? process.platform;
|
|
29
|
+
const isAlive = deps.isAlive ?? isProcessAlive;
|
|
30
|
+
const attest = deps.attest ?? ((pid) => attestDaemonCliProcess(pid, platform));
|
|
31
|
+
const sendSignal = deps.sendSignal ?? defaultSendSignal;
|
|
32
|
+
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
33
|
+
const removeDir = deps.removeDir ?? ((path) => rm(path, { recursive: true, force: true }));
|
|
34
|
+
const readOwner = deps.readOwner ?? readDaemonOwner;
|
|
35
|
+
const log = deps.log ?? defaultLog;
|
|
36
|
+
const termGraceMs = deps.termGraceMs ?? TERM_GRACE_MS;
|
|
37
|
+
const killGraceMs = deps.killGraceMs ?? KILL_GRACE_MS;
|
|
38
|
+
const entries = await readdir(baseDir, { withFileTypes: true }).catch(() => []);
|
|
39
|
+
const results = [];
|
|
40
|
+
for (const entry of [...entries].sort((left, right) => left.name.localeCompare(right.name))) {
|
|
41
|
+
if (entry.name === `v${ownPaths.version}`)
|
|
42
|
+
continue;
|
|
43
|
+
const version = parseVersionEntry(entry.name);
|
|
44
|
+
if (version === null || !entry.isDirectory())
|
|
45
|
+
continue;
|
|
46
|
+
const versionDir = join(baseDir, entry.name);
|
|
47
|
+
const siblingPaths = daemonPaths({ [OMO_LSP_DAEMON_DIR]: baseDir }, { cliPath: ownPaths.cliPath, version });
|
|
48
|
+
results.push(await reapOneVersion({
|
|
49
|
+
version,
|
|
50
|
+
versionDir,
|
|
51
|
+
siblingPaths,
|
|
52
|
+
platform,
|
|
53
|
+
isAlive,
|
|
54
|
+
attest,
|
|
55
|
+
sendSignal,
|
|
56
|
+
waitForExit,
|
|
57
|
+
removeDir,
|
|
58
|
+
readOwner,
|
|
59
|
+
log,
|
|
60
|
+
termGraceMs,
|
|
61
|
+
killGraceMs,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
async function reapOneVersion(context) {
|
|
67
|
+
const { version, versionDir } = context;
|
|
68
|
+
const owner = context.readOwner(context.siblingPaths);
|
|
69
|
+
if (!owner) {
|
|
70
|
+
const lockPid = readLockPid(join(versionDir, "daemon.lock"));
|
|
71
|
+
if (lockPid !== null && context.isAlive(lockPid)) {
|
|
72
|
+
context.log(`reap: sparing v${version}: owner metadata missing but daemon.lock is held by live pid ${lockPid}`);
|
|
73
|
+
return { version, status: "spared", reason: `owner metadata missing but lock held by live pid ${lockPid}` };
|
|
74
|
+
}
|
|
75
|
+
await context.removeDir(versionDir);
|
|
76
|
+
return { version, status: "removed", reason: "removed stale version dir without readable owner metadata" };
|
|
77
|
+
}
|
|
78
|
+
if (!context.isAlive(owner.pid)) {
|
|
79
|
+
await context.removeDir(versionDir);
|
|
80
|
+
return { version, status: "removed", reason: `removed stale version dir for dead owner pid ${owner.pid}` };
|
|
81
|
+
}
|
|
82
|
+
if (context.platform === "win32") {
|
|
83
|
+
context.log(`reap: deferring v${version}: Windows cannot prove pid ownership safely (named-pipe policy)`);
|
|
84
|
+
return {
|
|
85
|
+
version,
|
|
86
|
+
status: "deferred",
|
|
87
|
+
reason: "Windows cannot prove pid ownership safely; named-pipe reap deferred",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (!(await context.attest(owner.pid, context.platform))) {
|
|
91
|
+
context.log(`reap: sparing v${version}: pid ${owner.pid} is alive but cmdline attestation failed (possible recycled pid)`);
|
|
92
|
+
return {
|
|
93
|
+
version,
|
|
94
|
+
status: "spared",
|
|
95
|
+
reason: `pid ${owner.pid} attestation failed; possible recycled pid`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return await terminateAttestedOwner(context, owner.pid);
|
|
99
|
+
}
|
|
100
|
+
async function terminateAttestedOwner(context, pid) {
|
|
101
|
+
const { version, versionDir } = context;
|
|
102
|
+
if (!context.sendSignal(pid, "SIGTERM")) {
|
|
103
|
+
await context.removeDir(versionDir);
|
|
104
|
+
return {
|
|
105
|
+
version,
|
|
106
|
+
status: "removed",
|
|
107
|
+
reason: `owner pid ${pid} exited before SIGTERM; removed stale version dir`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (await context.waitForExit(pid, context.termGraceMs)) {
|
|
111
|
+
await context.removeDir(versionDir);
|
|
112
|
+
return { version, status: "terminated", reason: `terminated attested older daemon pid ${pid} with SIGTERM` };
|
|
113
|
+
}
|
|
114
|
+
context.log(`reap: v${version} pid ${pid} survived SIGTERM; escalating to SIGKILL`);
|
|
115
|
+
if (!context.sendSignal(pid, "SIGKILL") || !(await context.waitForExit(pid, context.killGraceMs))) {
|
|
116
|
+
context.log(`reap: deferring v${version}: pid ${pid} survived SIGKILL`);
|
|
117
|
+
return { version, status: "deferred", reason: `attested daemon pid ${pid} survived SIGKILL; dir kept` };
|
|
118
|
+
}
|
|
119
|
+
await context.removeDir(versionDir);
|
|
120
|
+
return {
|
|
121
|
+
version,
|
|
122
|
+
status: "terminated",
|
|
123
|
+
reason: `terminated attested older daemon pid ${pid} after SIGKILL escalation`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function parseVersionEntry(entryName) {
|
|
127
|
+
const match = VERSION_ENTRY_PATTERN.exec(entryName);
|
|
128
|
+
return match?.[1] ?? null;
|
|
129
|
+
}
|
|
130
|
+
function splitCmdline(buffer) {
|
|
131
|
+
return buffer
|
|
132
|
+
.toString("utf8")
|
|
133
|
+
.split("\u0000")
|
|
134
|
+
.filter((value) => value.length > 0);
|
|
135
|
+
}
|
|
136
|
+
function isNodeCliDaemonArgv(argv) {
|
|
137
|
+
if (argv.length < 2 || !argv.includes("daemon"))
|
|
138
|
+
return false;
|
|
139
|
+
const executable = basename(argv[0] ?? "");
|
|
140
|
+
if (!/^node(?:\.exe)?$/i.test(executable))
|
|
141
|
+
return false;
|
|
142
|
+
return argv.some((value) => value === "cli.js" || value.endsWith("/cli.js") || value.endsWith("\\cli.js"));
|
|
143
|
+
}
|
|
144
|
+
function isNodeCliDaemonCommand(command) {
|
|
145
|
+
return /\bnode(?:\.exe)?\b/i.test(command) && /\bcli\.js\b/.test(command) && /\bdaemon\b/.test(command);
|
|
146
|
+
}
|
|
147
|
+
function defaultReadProcFile(path) {
|
|
148
|
+
return readFile(path);
|
|
149
|
+
}
|
|
150
|
+
function defaultExecuteForStdout(file, args) {
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
execFile(file, [...args], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: 1_000 }, (error, stdout) => {
|
|
153
|
+
if (error !== null) {
|
|
154
|
+
resolve(null);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
resolve(stdout);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function defaultSendSignal(pid, signal) {
|
|
162
|
+
try {
|
|
163
|
+
process.kill(pid, signal);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function defaultWaitForExit(pid, timeoutMs) {
|
|
171
|
+
const deadline = Date.now() + timeoutMs;
|
|
172
|
+
for (;;) {
|
|
173
|
+
if (!isProcessAlive(pid))
|
|
174
|
+
return true;
|
|
175
|
+
if (Date.now() >= deadline)
|
|
176
|
+
return false;
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function defaultLog(message) {
|
|
181
|
+
process.stderr.write(`[lsp-daemon] ${message}\n`);
|
|
182
|
+
}
|
|
@@ -2615,8 +2615,13 @@ class LspClient extends LspClientConnection {
|
|
|
2615
2615
|
if (!pushFallbackOnly)
|
|
2616
2616
|
continue;
|
|
2617
2617
|
const remainingMs = deadlineAt - Date.now();
|
|
2618
|
-
if (remainingMs <= 0)
|
|
2618
|
+
if (remainingMs <= 0) {
|
|
2619
|
+
if (!this.isDiagnosticPullSupported() && snapshot.publishGeneration === 0) {
|
|
2620
|
+
const cached = this.documents.getPullCache(snapshot);
|
|
2621
|
+
return { items: cached === null ? [] : [...cached.diagnostics] };
|
|
2622
|
+
}
|
|
2619
2623
|
return this.freshnessTimeout(absPath);
|
|
2624
|
+
}
|
|
2620
2625
|
const waitMs = push.status === "wait" ? Math.min(push.waitMs, remainingMs) : remainingMs;
|
|
2621
2626
|
await waitForDiagnosticsActivity(this.documents.waitForDiagnosticsActivity(snapshot, waitMs), signal);
|
|
2622
2627
|
}
|
|
@@ -3167,16 +3172,35 @@ function bufferFromChunk(chunk) {
|
|
|
3167
3172
|
|
|
3168
3173
|
// ../mcp-stdio-core/src/server.ts
|
|
3169
3174
|
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60000;
|
|
3175
|
+
var DEFAULT_PARENT_POLL_INTERVAL_MS = 30000;
|
|
3170
3176
|
var noopLog = () => {};
|
|
3177
|
+
function isProcessAlive(pid) {
|
|
3178
|
+
try {
|
|
3179
|
+
process.kill(pid, 0);
|
|
3180
|
+
return true;
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
return !hasErrorCode(error, "ESRCH");
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3171
3185
|
async function runJsonRpcStdioServer(config) {
|
|
3172
3186
|
const log = config.log ?? noopLog;
|
|
3173
3187
|
const idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
3174
|
-
|
|
3188
|
+
let isClosed = false;
|
|
3189
|
+
const idleTimer = createIdleTimer(idleTimeoutMs, log, () => {
|
|
3190
|
+
isClosed = true;
|
|
3191
|
+
config.onIdleTimeout?.();
|
|
3192
|
+
});
|
|
3193
|
+
const watchdog = createParentWatchdog(config.parentWatchdog, (parentPid, pollIntervalMs) => {
|
|
3194
|
+
isClosed = true;
|
|
3195
|
+
log("parent_exit", { parent_pid: parentPid, poll_interval_ms: pollIntervalMs });
|
|
3196
|
+
config.onParentExit?.();
|
|
3197
|
+
config.input.destroy();
|
|
3198
|
+
});
|
|
3175
3199
|
log("stdio_started", { cwd: process.cwd(), idle_timeout_ms: idleTimeoutMs });
|
|
3176
3200
|
idleTimer.arm();
|
|
3177
3201
|
try {
|
|
3178
3202
|
for await (const message of readStdioJsonRpcMessages(config.input)) {
|
|
3179
|
-
if (
|
|
3203
|
+
if (isClosed)
|
|
3180
3204
|
break;
|
|
3181
3205
|
idleTimer.arm();
|
|
3182
3206
|
if (message.kind === "parse_error") {
|
|
@@ -3187,8 +3211,12 @@ async function runJsonRpcStdioServer(config) {
|
|
|
3187
3211
|
if (!await handleRequest(message, config, log))
|
|
3188
3212
|
break;
|
|
3189
3213
|
}
|
|
3214
|
+
} catch (error) {
|
|
3215
|
+
if (!(isClosed && hasErrorCode(error, "ERR_STREAM_PREMATURE_CLOSE")))
|
|
3216
|
+
throw error;
|
|
3190
3217
|
} finally {
|
|
3191
3218
|
idleTimer.clear();
|
|
3219
|
+
watchdog.clear();
|
|
3192
3220
|
log("stdio_stopped");
|
|
3193
3221
|
}
|
|
3194
3222
|
}
|
|
@@ -3244,9 +3272,33 @@ function isTerminalOutputError(error) {
|
|
|
3244
3272
|
return false;
|
|
3245
3273
|
return error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED" || error.code === "ERR_STREAM_WRITE_AFTER_END";
|
|
3246
3274
|
}
|
|
3247
|
-
function
|
|
3275
|
+
function hasErrorCode(error, code) {
|
|
3276
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
3277
|
+
}
|
|
3278
|
+
function createParentWatchdog(config, onDeadParent) {
|
|
3279
|
+
if (config === undefined)
|
|
3280
|
+
return { clear: () => {} };
|
|
3281
|
+
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_PARENT_POLL_INTERVAL_MS;
|
|
3282
|
+
if (pollIntervalMs <= 0)
|
|
3283
|
+
return { clear: () => {} };
|
|
3284
|
+
const parentPid = config.parentPid ?? process.ppid;
|
|
3285
|
+
const probeAlive = config.probeAlive ?? isProcessAlive;
|
|
3286
|
+
let fired = false;
|
|
3287
|
+
const timer = setInterval(() => {
|
|
3288
|
+
if (fired || probeAlive(parentPid))
|
|
3289
|
+
return;
|
|
3290
|
+
fired = true;
|
|
3291
|
+
onDeadParent(parentPid, pollIntervalMs);
|
|
3292
|
+
}, pollIntervalMs);
|
|
3293
|
+
timer.unref();
|
|
3294
|
+
return {
|
|
3295
|
+
clear: () => {
|
|
3296
|
+
clearInterval(timer);
|
|
3297
|
+
}
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
function createIdleTimer(idleTimeoutMs, log, onTimeout) {
|
|
3248
3301
|
let timer = null;
|
|
3249
|
-
let isClosed = false;
|
|
3250
3302
|
return {
|
|
3251
3303
|
arm: () => {
|
|
3252
3304
|
if (timer !== null)
|
|
@@ -3254,9 +3306,8 @@ function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
|
|
|
3254
3306
|
if (idleTimeoutMs <= 0)
|
|
3255
3307
|
return;
|
|
3256
3308
|
timer = setTimeout(() => {
|
|
3257
|
-
isClosed = true;
|
|
3258
3309
|
log("idle_timeout", { idle_timeout_ms: idleTimeoutMs });
|
|
3259
|
-
|
|
3310
|
+
onTimeout();
|
|
3260
3311
|
}, idleTimeoutMs);
|
|
3261
3312
|
timer.unref();
|
|
3262
3313
|
},
|
|
@@ -3265,8 +3316,7 @@ function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
|
|
|
3265
3316
|
return;
|
|
3266
3317
|
clearTimeout(timer);
|
|
3267
3318
|
timer = null;
|
|
3268
|
-
}
|
|
3269
|
-
closed: () => isClosed
|
|
3319
|
+
}
|
|
3270
3320
|
};
|
|
3271
3321
|
}
|
|
3272
3322
|
// ../lsp-core/src/lsp/client-wrapper.ts
|
|
@@ -5027,12 +5077,13 @@ async function handleLspMcpRequest(input, options = {}) {
|
|
|
5027
5077
|
}
|
|
5028
5078
|
return errorResponse(id, -32601, `Method not found: ${String(method)}`);
|
|
5029
5079
|
}
|
|
5030
|
-
async function runMcpStdioServer(input = process.stdin, output = process.stdout) {
|
|
5080
|
+
async function runMcpStdioServer(input = process.stdin, output = process.stdout, options = {}) {
|
|
5031
5081
|
const requestContext = createStandaloneMcpRequestContext();
|
|
5032
5082
|
await runJsonRpcStdioServer({
|
|
5033
5083
|
input,
|
|
5034
5084
|
output,
|
|
5035
5085
|
idleTimeoutMs: 0,
|
|
5086
|
+
parentWatchdog: options.parentWatchdog ?? {},
|
|
5036
5087
|
handler: (request) => runWithRequestContext(requestContext, () => handleLspMcpRequest(request)),
|
|
5037
5088
|
handlerOptions: undefined
|
|
5038
5089
|
});
|
|
@@ -2613,8 +2613,13 @@ class LspClient extends LspClientConnection {
|
|
|
2613
2613
|
if (!pushFallbackOnly)
|
|
2614
2614
|
continue;
|
|
2615
2615
|
const remainingMs = deadlineAt - Date.now();
|
|
2616
|
-
if (remainingMs <= 0)
|
|
2616
|
+
if (remainingMs <= 0) {
|
|
2617
|
+
if (!this.isDiagnosticPullSupported() && snapshot.publishGeneration === 0) {
|
|
2618
|
+
const cached = this.documents.getPullCache(snapshot);
|
|
2619
|
+
return { items: cached === null ? [] : [...cached.diagnostics] };
|
|
2620
|
+
}
|
|
2617
2621
|
return this.freshnessTimeout(absPath);
|
|
2622
|
+
}
|
|
2618
2623
|
const waitMs = push.status === "wait" ? Math.min(push.waitMs, remainingMs) : remainingMs;
|
|
2619
2624
|
await waitForDiagnosticsActivity(this.documents.waitForDiagnosticsActivity(snapshot, waitMs), signal);
|
|
2620
2625
|
}
|