oh-my-opencode 5.0.0-beta.25 → 5.0.0-beta.28
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/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
- package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
- package/dist/cli/index.js +309 -182
- package/dist/cli-node/index.js +332 -182
- package/dist/features/team-mode/tools/messaging-fallback-wake.d.ts +15 -0
- package/dist/hooks/todo-continuation-enforcer/types.d.ts +1 -0
- package/dist/index.js +1958 -1668
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/legacy-plugin-warning.d.ts +2 -1
- package/dist/shared/plugin-entry-migrator.d.ts +4 -3
- package/dist/shared/plugin-entry-shape.d.ts +6 -0
- package/dist/skills/data-scientist/SKILL.md +99 -239
- package/dist/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/dist/skills/data-scientist/references/placement.md +74 -0
- package/dist/skills/data-scientist/references/polars-lane.md +95 -0
- package/dist/skills/data-scientist/references/uv-setup.md +1 -1
- package/dist/skills/data-scientist/references/visualization.md +64 -0
- package/dist/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/dist/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/dist/skills/debugging/SKILL.md +2 -0
- package/dist/skills/debugging/references/methodology/00-setup.md +12 -0
- package/dist/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/dist/skills/debugging/references/runtimes/go.md +15 -1
- package/dist/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/dist/skills/debugging/references/runtimes/node.md +25 -0
- package/dist/skills/debugging/references/runtimes/python.md +13 -0
- package/dist/skills/debugging/references/runtimes/rust.md +41 -0
- package/dist/skills/debugging/references/scripts/dap.mjs +267 -0
- package/dist/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/dist/skills/debugging/references/tools/dap.md +103 -0
- package/dist/skills/debugging/references/tools/frida.md +193 -0
- package/dist/skills/ulw-research/SKILL.md +3 -1
- package/dist/tools/delegate-task/types.d.ts +3 -0
- package/dist/tui.d.ts +12 -0
- package/dist/tui.js +106 -15
- package/package.json +18 -15
- package/packages/lsp-core/src/index.ts +1 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +25 -5
- package/packages/lsp-core/src/lsp/client.ts +26 -0
- package/packages/lsp-core/src/lsp/connection.ts +16 -0
- package/packages/lsp-core/src/lsp/constants.ts +1 -0
- package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
- package/packages/lsp-core/src/lsp/format-document.ts +90 -0
- package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
- package/packages/lsp-core/src/lsp/manager.ts +34 -1
- package/packages/lsp-core/src/lsp/server-definitions.ts +29 -0
- package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
- package/packages/lsp-core/src/lsp/server-installation.ts +200 -27
- package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
- package/packages/lsp-core/src/lsp/server-resolution.ts +18 -5
- package/packages/lsp-core/src/lsp/transport.ts +9 -0
- package/packages/lsp-core/src/lsp/types.ts +8 -0
- package/packages/lsp-core/src/request-context.test.ts +29 -0
- package/packages/lsp-core/src/request-context.ts +1 -1
- package/packages/lsp-core/src/tool-surface.test.ts +13 -1
- package/packages/lsp-core/src/tools/definitions.ts +14 -0
- package/packages/lsp-core/src/tools/format.test.ts +56 -0
- package/packages/lsp-core/src/tools/format.ts +56 -0
- package/packages/lsp-core/src/tools/index.ts +1 -0
- package/packages/lsp-core/src/tools/types.ts +10 -0
- package/packages/lsp-daemon/dist/cli.js +433 -145
- package/packages/lsp-daemon/dist/client.d.ts +10 -0
- package/packages/lsp-daemon/dist/client.js +402 -108
- package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
- package/packages/lsp-daemon/dist/daemon-client.js +3 -0
- package/packages/lsp-daemon/dist/index.js +396 -108
- package/packages/lsp-tools-mcp/dist/cli.js +385 -100
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +53 -0
- package/packages/lsp-tools-mcp/dist/mcp.js +385 -100
- package/packages/lsp-tools-mcp/dist/request-context.js +1 -1
- package/packages/lsp-tools-mcp/dist/tools.js +386 -100
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +69 -35
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +21 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +404 -121
- 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 +3 -3
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +3 -3
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +4 -4
- 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/dist/cli.js +30 -15
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +30 -15
- 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/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- 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/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-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-ulw-execute-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/model-catalog.json +3 -3
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +3 -3
- package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -1
- package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +8 -8
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +38 -23
- package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
- package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
- package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/shared-skills/skills/debugging/SKILL.md +2 -0
- package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
- package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
- package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -1
- package/dist/skills/data-scientist/references/common-scenarios.md +0 -176
- package/dist/skills/data-scientist/references/execution-templates.md +0 -197
- package/dist/skills/data-scientist/references/integration-patterns.md +0 -153
- package/dist/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
package/dist/tui.js
CHANGED
|
@@ -17499,6 +17499,25 @@ var init_telemetry = __esm(() => {
|
|
|
17499
17499
|
}).strict();
|
|
17500
17500
|
});
|
|
17501
17501
|
|
|
17502
|
+
// packages/omo-config-core/src/schema/format-on-mutation.ts
|
|
17503
|
+
var mode, languages, OmoFormatOnMutationLayerSchema, OmoFormatOnMutationSchema;
|
|
17504
|
+
var init_format_on_mutation = __esm(() => {
|
|
17505
|
+
init_zod();
|
|
17506
|
+
mode = _enum2(["off", "best-effort", "required"]);
|
|
17507
|
+
languages = record(string2(), boolean2()).optional();
|
|
17508
|
+
OmoFormatOnMutationLayerSchema = object({
|
|
17509
|
+
mode: mode.optional(),
|
|
17510
|
+
languages,
|
|
17511
|
+
maxFileBytes: number2().int().positive().optional(),
|
|
17512
|
+
timeoutMs: number2().int().positive().optional()
|
|
17513
|
+
}).strict();
|
|
17514
|
+
OmoFormatOnMutationSchema = OmoFormatOnMutationLayerSchema.extend({
|
|
17515
|
+
mode: mode.default("best-effort"),
|
|
17516
|
+
maxFileBytes: number2().int().positive().default(1048576),
|
|
17517
|
+
timeoutMs: number2().int().positive().default(3000)
|
|
17518
|
+
}).strict();
|
|
17519
|
+
});
|
|
17520
|
+
|
|
17502
17521
|
// packages/omo-config-core/src/schema/config.ts
|
|
17503
17522
|
var OmoOpenCodeHarnessConfigSchema, OmoTypedHarnessConfigSchema, OmoConfigProfileSchema, OmoConfigSchema, OmoConfigLayerSchema;
|
|
17504
17523
|
var init_config = __esm(() => {
|
|
@@ -17512,8 +17531,10 @@ var init_config = __esm(() => {
|
|
|
17512
17531
|
init_task();
|
|
17513
17532
|
init_team();
|
|
17514
17533
|
init_telemetry();
|
|
17534
|
+
init_format_on_mutation();
|
|
17515
17535
|
OmoOpenCodeHarnessConfigSchema = record(string2(), unknown());
|
|
17516
17536
|
OmoTypedHarnessConfigSchema = object({
|
|
17537
|
+
formatOnMutation: OmoFormatOnMutationLayerSchema.optional(),
|
|
17517
17538
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
17518
17539
|
agents: OmoAgentsConfigSchema.optional(),
|
|
17519
17540
|
codegraph: OmoCodegraphSettingsLayerSchema.optional(),
|
|
@@ -17525,6 +17546,7 @@ var init_config = __esm(() => {
|
|
|
17525
17546
|
telemetry: OmoTelemetrySettingsLayerSchema.optional()
|
|
17526
17547
|
}).strict();
|
|
17527
17548
|
OmoConfigProfileSchema = object({
|
|
17549
|
+
formatOnMutation: OmoFormatOnMutationLayerSchema.optional(),
|
|
17528
17550
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
17529
17551
|
agents: OmoAgentsConfigSchema.optional(),
|
|
17530
17552
|
codegraph: OmoCodegraphSettingsLayerSchema.optional(),
|
|
@@ -17539,6 +17561,7 @@ var init_config = __esm(() => {
|
|
|
17539
17561
|
"[codex]": OmoTypedHarnessConfigSchema.optional()
|
|
17540
17562
|
}).strict();
|
|
17541
17563
|
OmoConfigSchema = object({
|
|
17564
|
+
formatOnMutation: OmoFormatOnMutationSchema.optional(),
|
|
17542
17565
|
$schema: string2().optional(),
|
|
17543
17566
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
17544
17567
|
agents: OmoAgentsConfigSchema.optional(),
|
|
@@ -17557,6 +17580,7 @@ var init_config = __esm(() => {
|
|
|
17557
17580
|
legacy_migrations: record(string2(), unknown()).optional()
|
|
17558
17581
|
}).strict();
|
|
17559
17582
|
OmoConfigLayerSchema = object({
|
|
17583
|
+
formatOnMutation: OmoFormatOnMutationLayerSchema.optional(),
|
|
17560
17584
|
$schema: string2().optional(),
|
|
17561
17585
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
17562
17586
|
agents: OmoAgentsConfigSchema.optional(),
|
|
@@ -17583,6 +17607,7 @@ var init_schema = __esm(() => {
|
|
|
17583
17607
|
init_codegraph();
|
|
17584
17608
|
init_config();
|
|
17585
17609
|
init_fallback_models();
|
|
17610
|
+
init_format_on_mutation();
|
|
17586
17611
|
init_git_master();
|
|
17587
17612
|
init_harness();
|
|
17588
17613
|
init_memory();
|
|
@@ -18651,6 +18676,9 @@ var init_message_inspection_error = () => {};
|
|
|
18651
18676
|
function getPromptGateMessagesFetchTimeoutMs() {
|
|
18652
18677
|
return promptGateMessagesFetchTimeoutMsForTesting ?? DEFAULT_PROMPT_GATE_MESSAGES_FETCH_TIMEOUT_MS;
|
|
18653
18678
|
}
|
|
18679
|
+
function getPromptGateRevalidationTimeoutMs() {
|
|
18680
|
+
return promptGateRevalidationTimeoutMsForTesting ?? DEFAULT_PROMPT_REVALIDATION_TIMEOUT_MS;
|
|
18681
|
+
}
|
|
18654
18682
|
async function withDispatchTimeout(operation, dispatchTimeoutMs, operationName) {
|
|
18655
18683
|
if (dispatchTimeoutMs <= 0) {
|
|
18656
18684
|
return operation;
|
|
@@ -18669,7 +18697,7 @@ async function withDispatchTimeout(operation, dispatchTimeoutMs, operationName)
|
|
|
18669
18697
|
}
|
|
18670
18698
|
}
|
|
18671
18699
|
}
|
|
18672
|
-
var DEFAULT_PROMPT_GATE_MESSAGES_FETCH_TIMEOUT_MS = 5000, promptGateMessagesFetchTimeoutMsForTesting;
|
|
18700
|
+
var DEFAULT_PROMPT_GATE_MESSAGES_FETCH_TIMEOUT_MS = 5000, DEFAULT_PROMPT_REVALIDATION_TIMEOUT_MS = 5000, promptGateMessagesFetchTimeoutMsForTesting, promptGateRevalidationTimeoutMsForTesting;
|
|
18673
18701
|
|
|
18674
18702
|
// packages/utils/src/prompt-async-gate/pending-tool-turn.ts
|
|
18675
18703
|
function getPromptQuery(input) {
|
|
@@ -18866,6 +18894,7 @@ async function dispatchAfterSessionIdle(args) {
|
|
|
18866
18894
|
checkStatus,
|
|
18867
18895
|
checkToolState,
|
|
18868
18896
|
shouldDispatch,
|
|
18897
|
+
retryDispatchFailure,
|
|
18869
18898
|
dispatch
|
|
18870
18899
|
} = args;
|
|
18871
18900
|
const existing = getActiveReservation(sessionID);
|
|
@@ -18920,7 +18949,9 @@ async function dispatchAfterSessionIdle(args) {
|
|
|
18920
18949
|
});
|
|
18921
18950
|
return { status: "active" };
|
|
18922
18951
|
}
|
|
18923
|
-
|
|
18952
|
+
const dispatchDecision = shouldDispatch?.();
|
|
18953
|
+
const resolvedDispatchDecision = dispatchDecision !== undefined && typeof dispatchDecision !== "boolean" ? await withDispatchTimeout(dispatchDecision, Math.min(dispatchTimeoutMs, getPromptGateRevalidationTimeoutMs()), `[prompt-async-gate] ${sessionName} shouldDispatch`) : dispatchDecision;
|
|
18954
|
+
if (resolvedDispatchDecision === false) {
|
|
18924
18955
|
log(`[prompt-async-gate] ${sessionName} cancelled before dispatch`, { sessionID, source });
|
|
18925
18956
|
return { status: "cancelled" };
|
|
18926
18957
|
}
|
|
@@ -18936,6 +18967,7 @@ async function dispatchAfterSessionIdle(args) {
|
|
|
18936
18967
|
log(`[prompt-async-gate] ${sessionName} dispatched`, { sessionID, source });
|
|
18937
18968
|
return { status: "dispatched", response };
|
|
18938
18969
|
} catch (error51) {
|
|
18970
|
+
const queueRetryable = dispatchAttempted && retryDispatchFailure?.(error51) === true;
|
|
18939
18971
|
if (dispatchAttempted) {
|
|
18940
18972
|
rememberRecentPromptDispatch({
|
|
18941
18973
|
sessionID,
|
|
@@ -18946,7 +18978,12 @@ async function dispatchAfterSessionIdle(args) {
|
|
|
18946
18978
|
}
|
|
18947
18979
|
const errorText = error51 instanceof Error ? `${error51.name}: ${error51.message}` : String(error51);
|
|
18948
18980
|
log(`[prompt-async-gate] ${sessionName} failed`, { sessionID, source, error: errorText });
|
|
18949
|
-
return {
|
|
18981
|
+
return {
|
|
18982
|
+
status: "failed",
|
|
18983
|
+
error: error51,
|
|
18984
|
+
dispatchAttempted,
|
|
18985
|
+
...queueRetryable ? { queueRetryable: true } : {}
|
|
18986
|
+
};
|
|
18950
18987
|
} finally {
|
|
18951
18988
|
finishPromptReservation(sessionID, reservation, dispatchAttempted, postDispatchHoldMs);
|
|
18952
18989
|
}
|
|
@@ -18999,6 +19036,7 @@ function schedulePromptQueueDrain(sessionID, delayMs) {
|
|
|
18999
19036
|
promptQueueTimers.set(sessionID, timer);
|
|
19000
19037
|
}
|
|
19001
19038
|
function removePromptQueueEntry(sessionID, entry) {
|
|
19039
|
+
promptQueueRetryStates.delete(entry.id);
|
|
19002
19040
|
const queue = promptQueues.get(sessionID);
|
|
19003
19041
|
if (!queue) {
|
|
19004
19042
|
return;
|
|
@@ -19013,6 +19051,7 @@ async function drainPromptQueue(sessionID, awaitedEntry) {
|
|
|
19013
19051
|
promptQueueDraining.add(sessionID);
|
|
19014
19052
|
clearPromptQueueTimer(sessionID);
|
|
19015
19053
|
let awaitedResult;
|
|
19054
|
+
const deferredRetryIds = new Set;
|
|
19016
19055
|
try {
|
|
19017
19056
|
while (true) {
|
|
19018
19057
|
const queue = promptQueues.get(sessionID);
|
|
@@ -19020,6 +19059,20 @@ async function drainPromptQueue(sessionID, awaitedEntry) {
|
|
|
19020
19059
|
if (!entry) {
|
|
19021
19060
|
break;
|
|
19022
19061
|
}
|
|
19062
|
+
const retryState = promptQueueRetryStates.get(entry.id);
|
|
19063
|
+
if (entry.durableRetry && retryState?.retryAfter !== undefined && retryState.retryAfter > Date.now()) {
|
|
19064
|
+
if (deferredRetryIds.has(entry.id)) {
|
|
19065
|
+
const nextRetryAt = Math.min(...queue.flatMap((queued) => {
|
|
19066
|
+
const retryAfter = promptQueueRetryStates.get(queued.id)?.retryAfter;
|
|
19067
|
+
return retryAfter === undefined ? [] : [retryAfter];
|
|
19068
|
+
}));
|
|
19069
|
+
schedulePromptQueueDrain(sessionID, nextRetryAt - Date.now());
|
|
19070
|
+
break;
|
|
19071
|
+
}
|
|
19072
|
+
deferredRetryIds.add(entry.id);
|
|
19073
|
+
setPromptQueue(sessionID, [...queue.slice(1), entry]);
|
|
19074
|
+
continue;
|
|
19075
|
+
}
|
|
19023
19076
|
promptQueueInFlight.set(sessionID, entry);
|
|
19024
19077
|
const result = await dispatchAfterSessionIdle({
|
|
19025
19078
|
sessionName: entry.sessionName,
|
|
@@ -19035,12 +19088,45 @@ async function drainPromptQueue(sessionID, awaitedEntry) {
|
|
|
19035
19088
|
checkStatus: entry.checkStatus,
|
|
19036
19089
|
checkToolState: entry.checkToolState,
|
|
19037
19090
|
shouldDispatch: entry.shouldDispatch,
|
|
19091
|
+
retryDispatchFailure: entry.retryDispatchFailure,
|
|
19038
19092
|
dispatch: entry.dispatch
|
|
19039
19093
|
});
|
|
19040
19094
|
if (promptQueueInFlight.get(sessionID)?.id === entry.id) {
|
|
19041
19095
|
promptQueueInFlight.delete(sessionID);
|
|
19042
19096
|
}
|
|
19043
|
-
|
|
19097
|
+
const retryableQueuedFailure = result.status === "failed" && (!result.dispatchAttempted || result.queueRetryable === true);
|
|
19098
|
+
if (retryableQueuedFailure) {
|
|
19099
|
+
const failureCount = (promptQueueRetryStates.get(entry.id)?.failureCount ?? 0) + 1;
|
|
19100
|
+
if (entry.durableRetry) {
|
|
19101
|
+
const exponentialBackoffMs = entry.queueRetryMs * 2 ** Math.min(failureCount, 7);
|
|
19102
|
+
const retryDelayMs = Math.max(entry.postDispatchHoldMs, Math.min(exponentialBackoffMs, MAX_DURABLE_RETRY_BACKOFF_MS));
|
|
19103
|
+
promptQueueRetryStates.set(entry.id, {
|
|
19104
|
+
failureCount,
|
|
19105
|
+
retryAfter: Date.now() + retryDelayMs
|
|
19106
|
+
});
|
|
19107
|
+
const currentQueue = promptQueues.get(sessionID);
|
|
19108
|
+
if (currentQueue && currentQueue.length > 1) {
|
|
19109
|
+
setPromptQueue(sessionID, [...currentQueue.slice(1), entry]);
|
|
19110
|
+
continue;
|
|
19111
|
+
}
|
|
19112
|
+
schedulePromptQueueDrain(sessionID, retryDelayMs);
|
|
19113
|
+
break;
|
|
19114
|
+
}
|
|
19115
|
+
if (failureCount >= MAX_QUEUED_RETRYABLE_FAILURES) {
|
|
19116
|
+
log("[prompt-async-gate] dropping queued prompt after repeated retryable failures", {
|
|
19117
|
+
sessionID,
|
|
19118
|
+
source: entry.source,
|
|
19119
|
+
failureCount
|
|
19120
|
+
});
|
|
19121
|
+
removePromptQueueEntry(sessionID, entry);
|
|
19122
|
+
if (awaitedEntry?.id === entry.id) {
|
|
19123
|
+
awaitedResult = result;
|
|
19124
|
+
}
|
|
19125
|
+
continue;
|
|
19126
|
+
}
|
|
19127
|
+
promptQueueRetryStates.set(entry.id, { failureCount });
|
|
19128
|
+
}
|
|
19129
|
+
if (result.status === "active" || result.status === "reserved" || retryableQueuedFailure) {
|
|
19044
19130
|
const queued = queuedResult(entry, 1, result.status === "reserved" ? result.reservedBy : entry.source);
|
|
19045
19131
|
if (awaitedEntry?.id === entry.id) {
|
|
19046
19132
|
awaitedResult = queued;
|
|
@@ -19064,7 +19150,7 @@ async function drainPromptQueue(sessionID, awaitedEntry) {
|
|
|
19064
19150
|
}
|
|
19065
19151
|
return awaitedResult;
|
|
19066
19152
|
}
|
|
19067
|
-
var promptQueues, promptQueueDraining, promptQueueInFlight, promptQueueTimers;
|
|
19153
|
+
var promptQueues, promptQueueDraining, promptQueueInFlight, promptQueueTimers, promptQueueRetryStates, MAX_QUEUED_RETRYABLE_FAILURES = 3, MAX_DURABLE_RETRY_BACKOFF_MS = 30000;
|
|
19068
19154
|
var init_queue = __esm(() => {
|
|
19069
19155
|
init_session_idle_dispatch();
|
|
19070
19156
|
init_reservations();
|
|
@@ -19072,6 +19158,7 @@ var init_queue = __esm(() => {
|
|
|
19072
19158
|
promptQueueDraining = new Set;
|
|
19073
19159
|
promptQueueInFlight = new Map;
|
|
19074
19160
|
promptQueueTimers = new Map;
|
|
19161
|
+
promptQueueRetryStates = new Map;
|
|
19075
19162
|
setExpiredReservationHandler((sessionID) => {
|
|
19076
19163
|
schedulePromptQueueDrain(sessionID, 0);
|
|
19077
19164
|
});
|
|
@@ -78070,7 +78157,17 @@ function isPreSendConnectionFailure(error51) {
|
|
|
78070
78157
|
if (error51.name === "AbortError") {
|
|
78071
78158
|
return false;
|
|
78072
78159
|
}
|
|
78073
|
-
const CONNECTION_CODES = new Set([
|
|
78160
|
+
const CONNECTION_CODES = new Set([
|
|
78161
|
+
"ECONNREFUSED",
|
|
78162
|
+
"ENOTFOUND",
|
|
78163
|
+
"EAI_AGAIN",
|
|
78164
|
+
"ENETUNREACH",
|
|
78165
|
+
"EHOSTUNREACH",
|
|
78166
|
+
"ENETDOWN",
|
|
78167
|
+
"EHOSTDOWN",
|
|
78168
|
+
"EADDRNOTAVAIL",
|
|
78169
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
78170
|
+
]);
|
|
78074
78171
|
const self = error51;
|
|
78075
78172
|
if (self.code && CONNECTION_CODES.has(self.code)) {
|
|
78076
78173
|
return true;
|
|
@@ -78082,12 +78179,6 @@ function isPreSendConnectionFailure(error51) {
|
|
|
78082
78179
|
return true;
|
|
78083
78180
|
}
|
|
78084
78181
|
}
|
|
78085
|
-
if (error51 instanceof TypeError) {
|
|
78086
|
-
const msg = error51.message;
|
|
78087
|
-
if (msg.includes("fetch failed") || msg.includes("Unable to connect")) {
|
|
78088
|
-
return true;
|
|
78089
|
-
}
|
|
78090
|
-
}
|
|
78091
78182
|
return false;
|
|
78092
78183
|
}
|
|
78093
78184
|
function markLiveRouteUnavailable(reason) {
|
|
@@ -78411,7 +78502,6 @@ var sessionCategoryMap;
|
|
|
78411
78502
|
var init_session_category_registry = __esm(() => {
|
|
78412
78503
|
sessionCategoryMap = new Map;
|
|
78413
78504
|
});
|
|
78414
|
-
|
|
78415
78505
|
// packages/omo-opencode/src/shared/legacy-plugin-warning.ts
|
|
78416
78506
|
var init_legacy_plugin_warning = __esm(() => {
|
|
78417
78507
|
init_jsonc_parser2();
|
|
@@ -81989,7 +82079,7 @@ function registerSidebarContentSlot({
|
|
|
81989
82079
|
registerSlot({
|
|
81990
82080
|
order: 900,
|
|
81991
82081
|
slots: {
|
|
81992
|
-
sidebar_content: renderSidebar
|
|
82082
|
+
sidebar_content: () => renderSidebar
|
|
81993
82083
|
}
|
|
81994
82084
|
});
|
|
81995
82085
|
requestRender();
|
|
@@ -82110,5 +82200,6 @@ var module = {
|
|
|
82110
82200
|
var tui_default = module;
|
|
82111
82201
|
export {
|
|
82112
82202
|
tui_default as default,
|
|
82113
|
-
handleTuiPollError
|
|
82203
|
+
handleTuiPollError,
|
|
82204
|
+
registerSidebarContentSlot
|
|
82114
82205
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-opencode",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.28",
|
|
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",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"zod": "^4.4.3"
|
|
182
182
|
},
|
|
183
183
|
"devDependencies": {
|
|
184
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
184
|
+
"@code-yeongyu/senpi": "2026.8.30",
|
|
185
185
|
"@oh-my-opencode/agents-md-core": "workspace:*",
|
|
186
186
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
|
187
187
|
"@oh-my-opencode/boulder-state": "workspace:*",
|
|
@@ -222,18 +222,18 @@
|
|
|
222
222
|
"typescript": "^7.0.2"
|
|
223
223
|
},
|
|
224
224
|
"optionalDependencies": {
|
|
225
|
-
"oh-my-opencode-darwin-arm64": "5.0.0-beta.
|
|
226
|
-
"oh-my-opencode-darwin-x64": "5.0.0-beta.
|
|
227
|
-
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.
|
|
228
|
-
"oh-my-opencode-linux-arm64": "5.0.0-beta.
|
|
229
|
-
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.
|
|
230
|
-
"oh-my-opencode-linux-x64": "5.0.0-beta.
|
|
231
|
-
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.
|
|
232
|
-
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.
|
|
233
|
-
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.
|
|
234
|
-
"oh-my-opencode-windows-arm64": "5.0.0-beta.
|
|
235
|
-
"oh-my-opencode-windows-x64": "5.0.0-beta.
|
|
236
|
-
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.
|
|
225
|
+
"oh-my-opencode-darwin-arm64": "5.0.0-beta.28",
|
|
226
|
+
"oh-my-opencode-darwin-x64": "5.0.0-beta.28",
|
|
227
|
+
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.28",
|
|
228
|
+
"oh-my-opencode-linux-arm64": "5.0.0-beta.28",
|
|
229
|
+
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.28",
|
|
230
|
+
"oh-my-opencode-linux-x64": "5.0.0-beta.28",
|
|
231
|
+
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.28",
|
|
232
|
+
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.28",
|
|
233
|
+
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.28",
|
|
234
|
+
"oh-my-opencode-windows-arm64": "5.0.0-beta.28",
|
|
235
|
+
"oh-my-opencode-windows-x64": "5.0.0-beta.28",
|
|
236
|
+
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.28"
|
|
237
237
|
},
|
|
238
238
|
"overrides": {
|
|
239
239
|
"@earendil-works/pi-agent-core": "0.84.2",
|
|
@@ -249,5 +249,8 @@
|
|
|
249
249
|
},
|
|
250
250
|
"trustedDependencies": [
|
|
251
251
|
"@code-yeongyu/comment-checker"
|
|
252
|
-
]
|
|
252
|
+
],
|
|
253
|
+
"patchedDependencies": {
|
|
254
|
+
"@code-yeongyu/senpi@2026.8.30": "patches/@code-yeongyu%2Fsenpi@2026.8.30.patch"
|
|
255
|
+
}
|
|
253
256
|
}
|
|
@@ -7,6 +7,7 @@ export * from "./lsp/constants.js";
|
|
|
7
7
|
export * from "./lsp/directory-diagnostics.js";
|
|
8
8
|
export * from "./lsp/effective-extension.js";
|
|
9
9
|
export * from "./lsp/errors.js";
|
|
10
|
+
export * from "./lsp/format-document.js";
|
|
10
11
|
export * from "./lsp/formatters.js";
|
|
11
12
|
export * from "./lsp/infer-extension.js";
|
|
12
13
|
export * from "./lsp/json-rpc-connection.js";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "./errors.js";
|
|
18
18
|
import { getLspManager, type LspManager } from "./manager.js";
|
|
19
19
|
import { findWorkspaceRootOutsideContext } from "./outside-context-workspace.js";
|
|
20
|
+
import { LSP_LOCAL_INSTALL_HINTS } from "./server-definitions.js";
|
|
20
21
|
import { loadInstallDecision } from "./server-install-state.js";
|
|
21
22
|
import { findServerForExtension } from "./server-resolution.js";
|
|
22
23
|
import type { ServerLookupResult } from "./types.js";
|
|
@@ -138,6 +139,26 @@ export function formatServerLookupError(result: Exclude<ServerLookupResult, { st
|
|
|
138
139
|
].join("\n");
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Builds the install guidance, offering the repo-local install first.
|
|
144
|
+
*
|
|
145
|
+
* A repo-local install is resolvable without touching the global environment, so it is the
|
|
146
|
+
* recommended route whenever the server ships as a project dependency.
|
|
147
|
+
*/
|
|
148
|
+
function formatInstallOptions(serverId: string, installHint: string): string[] {
|
|
149
|
+
const localHint = LSP_LOCAL_INSTALL_HINTS[serverId];
|
|
150
|
+
if (localHint === undefined) {
|
|
151
|
+
return ["To install, run:", ` ${installHint}`];
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
"To install in THIS repository (preferred — no global install needed):",
|
|
155
|
+
` ${localHint}`,
|
|
156
|
+
"",
|
|
157
|
+
"Or install it globally:",
|
|
158
|
+
` ${installHint}`,
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
|
|
141
162
|
function formatNotInstalled(result: Extract<ServerLookupResult, { status: "not_installed" }>): string {
|
|
142
163
|
const { server, installHint } = result;
|
|
143
164
|
const extensions = server.extensions.join(", ");
|
|
@@ -154,20 +175,20 @@ function formatNotInstalled(result: Extract<ServerLookupResult, { status: "not_i
|
|
|
154
175
|
`Command not found: ${server.command[0]}`,
|
|
155
176
|
"",
|
|
156
177
|
];
|
|
178
|
+
const installOptions = formatInstallOptions(server.id, installHint);
|
|
157
179
|
|
|
158
180
|
if (decision === "allowed") {
|
|
159
181
|
return [
|
|
160
182
|
...header,
|
|
161
183
|
"The user has pre-authorized LSP installation. Run the install command, then retry this tool:",
|
|
162
|
-
|
|
184
|
+
...installOptions.slice(1),
|
|
163
185
|
].join("\n");
|
|
164
186
|
}
|
|
165
187
|
|
|
166
188
|
if (!context.capabilities.installDecisionTool) {
|
|
167
189
|
return [
|
|
168
190
|
...header,
|
|
169
|
-
|
|
170
|
-
` ${installHint}`,
|
|
191
|
+
...installOptions,
|
|
171
192
|
"",
|
|
172
193
|
"ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
|
|
173
194
|
"Install-decision recording is unavailable in this harness; proceed without LSP if the user declines.",
|
|
@@ -176,8 +197,7 @@ function formatNotInstalled(result: Extract<ServerLookupResult, { status: "not_i
|
|
|
176
197
|
|
|
177
198
|
return [
|
|
178
199
|
...header,
|
|
179
|
-
|
|
180
|
-
` ${installHint}`,
|
|
200
|
+
...installOptions,
|
|
181
201
|
"",
|
|
182
202
|
"ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
|
|
183
203
|
"- If the user agrees: run the install command above, then retry this tool.",
|
|
@@ -6,6 +6,7 @@ import type { LspClientTimeoutOptions } from "./transport.js";
|
|
|
6
6
|
import type {
|
|
7
7
|
Diagnostic,
|
|
8
8
|
DocumentSymbol,
|
|
9
|
+
FormattingOptions,
|
|
9
10
|
Location,
|
|
10
11
|
LocationLink,
|
|
11
12
|
PrepareRenameDefaultBehavior,
|
|
@@ -13,6 +14,7 @@ import type {
|
|
|
13
14
|
Range,
|
|
14
15
|
ResolvedServer,
|
|
15
16
|
SymbolInfo,
|
|
17
|
+
TextEdit,
|
|
16
18
|
WorkspaceEdit,
|
|
17
19
|
} from "./types.js";
|
|
18
20
|
import { LspRequestTimeoutError } from "./errors.js";
|
|
@@ -133,6 +135,30 @@ export class LspClient extends LspClientConnection {
|
|
|
133
135
|
}, options);
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Requests whole-document formatting edits, returning null when the server never advertised the
|
|
140
|
+
* capability so callers can report unavailability instead of surfacing a method-not-found error.
|
|
141
|
+
*/
|
|
142
|
+
async formatDocument(
|
|
143
|
+
filePath: string,
|
|
144
|
+
options: FormattingOptions,
|
|
145
|
+
signal?: AbortSignal,
|
|
146
|
+
): Promise<TextEdit[] | null> {
|
|
147
|
+
if (!this.isDocumentFormattingSupported()) return null;
|
|
148
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
149
|
+
await this.openFile(absPath);
|
|
150
|
+
const requestOptions = signal === undefined ? {} : { signal };
|
|
151
|
+
const edits = await this.sendRequest<TextEdit[] | null>(
|
|
152
|
+
"textDocument/formatting",
|
|
153
|
+
{
|
|
154
|
+
textDocument: { uri: pathToFileURL(absPath).href },
|
|
155
|
+
options,
|
|
156
|
+
},
|
|
157
|
+
requestOptions,
|
|
158
|
+
);
|
|
159
|
+
return edits ?? [];
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
async workspaceSymbols(query: string, signal?: AbortSignal): Promise<SymbolInfo[]> {
|
|
137
163
|
const options = signal === undefined ? {} : { signal };
|
|
138
164
|
return this.sendRequest<SymbolInfo[]>("workspace/symbol", { query }, options);
|
|
@@ -4,6 +4,7 @@ import { LspClientTransport } from "./transport.js";
|
|
|
4
4
|
|
|
5
5
|
interface InitializeCapabilities {
|
|
6
6
|
readonly diagnosticProvider?: unknown;
|
|
7
|
+
readonly documentFormattingProvider?: unknown;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
function supportsDiagnosticPull(capabilities: InitializeCapabilities | undefined): boolean {
|
|
@@ -11,6 +12,19 @@ function supportsDiagnosticPull(capabilities: InitializeCapabilities | undefined
|
|
|
11
12
|
return Object.hasOwn(capabilities, "diagnosticProvider");
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Reads the whole-document formatting capability the server advertised at initialize time.
|
|
17
|
+
*
|
|
18
|
+
* The provider is either a boolean or an options object, and an explicit `false` means the
|
|
19
|
+
* server refuses the request, so both shapes are checked instead of mere key presence.
|
|
20
|
+
*/
|
|
21
|
+
function supportsDocumentFormatting(capabilities: InitializeCapabilities | undefined): boolean {
|
|
22
|
+
if (capabilities === undefined) return false;
|
|
23
|
+
const provider = capabilities.documentFormattingProvider;
|
|
24
|
+
if (provider === undefined || provider === null || provider === false) return false;
|
|
25
|
+
return provider === true || typeof provider === "object";
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
export class LspClientConnection extends LspClientTransport {
|
|
15
29
|
async initialize(): Promise<void> {
|
|
16
30
|
const rootUri = pathToFileURL(this.root).href;
|
|
@@ -28,6 +42,7 @@ export class LspClientConnection extends LspClientTransport {
|
|
|
28
42
|
references: {},
|
|
29
43
|
documentSymbol: { hierarchicalDocumentSymbolSupport: true },
|
|
30
44
|
publishDiagnostics: {},
|
|
45
|
+
formatting: { dynamicRegistration: false },
|
|
31
46
|
rename: {
|
|
32
47
|
prepareSupport: true,
|
|
33
48
|
prepareSupportDefaultBehavior: 1,
|
|
@@ -71,6 +86,7 @@ export class LspClientConnection extends LspClientTransport {
|
|
|
71
86
|
{ timeoutMs: this.initializeTimeoutMs },
|
|
72
87
|
);
|
|
73
88
|
this.setDiagnosticPullSupported(supportsDiagnosticPull(result?.capabilities));
|
|
89
|
+
this.setDocumentFormattingSupported(supportsDocumentFormatting(result?.capabilities));
|
|
74
90
|
await this.sendNotification("initialized", {});
|
|
75
91
|
await this.sendNotification("workspace/didChangeConfiguration", {
|
|
76
92
|
settings: { json: { validate: { enable: true } } },
|
|
@@ -6,6 +6,7 @@ export const DEFAULT_MAX_DIRECTORY_FILES = 50;
|
|
|
6
6
|
export const REQUEST_TIMEOUT_MS = 15_000;
|
|
7
7
|
export const INIT_TIMEOUT_MS = 60_000;
|
|
8
8
|
export const IDLE_TIMEOUT_MS = 5 * 60_000;
|
|
9
|
+
export const MAX_RESIDENT_CLIENTS = 6;
|
|
9
10
|
export const REAPER_INTERVAL_MS = 60_000;
|
|
10
11
|
export const STOP_HARD_KILL_TIMEOUT_MS = 5_000;
|
|
11
12
|
export const STOP_SIGKILL_GRACE_MS = 1_000;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import type { LspClient } from "./client.js";
|
|
8
|
+
import { formatDocumentWithClient } from "./format-document.js";
|
|
9
|
+
import type { TextEdit } from "./types.js";
|
|
10
|
+
|
|
11
|
+
const workspaces: string[] = [];
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const workspace of workspaces.splice(0)) rmSync(workspace, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function workspaceWith(fileName: string, content: string): { readonly root: string; readonly filePath: string } {
|
|
18
|
+
const root = mkdtempSync(join(tmpdir(), "lsp-format-"));
|
|
19
|
+
workspaces.push(root);
|
|
20
|
+
const filePath = join(root, fileName);
|
|
21
|
+
writeFileSync(filePath, content, "utf-8");
|
|
22
|
+
return { root, filePath };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function stubClient(edits: TextEdit[] | null): LspClient {
|
|
26
|
+
return {
|
|
27
|
+
async formatDocument() {
|
|
28
|
+
return edits;
|
|
29
|
+
},
|
|
30
|
+
async openFile() {},
|
|
31
|
+
} as Pick<LspClient, "formatDocument" | "openFile"> as LspClient;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("formatDocumentWithClient", () => {
|
|
35
|
+
it("#given a server returning text edits #when the document is formatted #then the file is rewritten and line deltas are reported", async () => {
|
|
36
|
+
// given
|
|
37
|
+
const { filePath } = workspaceWith("sample.ts", "const a=1\nconst b =2\nconst c=3\n");
|
|
38
|
+
const edits: TextEdit[] = [
|
|
39
|
+
{ range: { start: { line: 0, character: 0 }, end: { line: 1, character: 12 } }, newText: "const a = 1;\nconst b = 2;" },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// when
|
|
43
|
+
const result = await formatDocumentWithClient(stubClient(edits), filePath);
|
|
44
|
+
|
|
45
|
+
// then
|
|
46
|
+
expect(result.status).toBe("formatted");
|
|
47
|
+
expect(readFileSync(filePath, "utf-8")).toBe("const a = 1;\nconst b = 2;\nconst c=3\n");
|
|
48
|
+
expect(result).toMatchObject({ status: "formatted", linesAdded: 2, linesRemoved: 2 });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("#given a server returning an edit that adds lines #when the document is formatted #then the added and removed line counts differ", async () => {
|
|
52
|
+
// given
|
|
53
|
+
const { filePath } = workspaceWith("sample.ts", "const a=1;const b=2\n");
|
|
54
|
+
const edits: TextEdit[] = [
|
|
55
|
+
{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 19 } }, newText: "const a = 1;\nconst b = 2;" },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// when
|
|
59
|
+
const result = await formatDocumentWithClient(stubClient(edits), filePath);
|
|
60
|
+
|
|
61
|
+
// then
|
|
62
|
+
expect(result).toMatchObject({ status: "formatted", linesAdded: 2, linesRemoved: 1 });
|
|
63
|
+
expect(readFileSync(filePath, "utf-8")).toBe("const a = 1;\nconst b = 2;\n");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("#given a server that does not advertise document formatting #when the document is formatted #then the result is unavailable and the file bytes are untouched", async () => {
|
|
67
|
+
// given
|
|
68
|
+
const original = "const a=1\n";
|
|
69
|
+
const { filePath } = workspaceWith("sample.ts", original);
|
|
70
|
+
const before = statSync(filePath);
|
|
71
|
+
|
|
72
|
+
// when
|
|
73
|
+
const result = await formatDocumentWithClient(stubClient(null), filePath);
|
|
74
|
+
|
|
75
|
+
// then
|
|
76
|
+
expect(result).toEqual({ status: "unavailable", reason: "capability_not_advertised" });
|
|
77
|
+
expect(readFileSync(filePath)).toEqual(Buffer.from(original));
|
|
78
|
+
expect(statSync(filePath).mtimeMs).toBe(before.mtimeMs);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("#given a server returning no edits #when the document is formatted #then the result is unchanged and the file bytes are untouched", async () => {
|
|
82
|
+
// given
|
|
83
|
+
const original = "const a = 1;\n";
|
|
84
|
+
const { filePath } = workspaceWith("sample.ts", original);
|
|
85
|
+
|
|
86
|
+
// when
|
|
87
|
+
const result = await formatDocumentWithClient(stubClient([]), filePath);
|
|
88
|
+
|
|
89
|
+
// then
|
|
90
|
+
expect(result).toEqual({ status: "unchanged", linesAdded: 0, linesRemoved: 0 });
|
|
91
|
+
expect(readFileSync(filePath)).toEqual(Buffer.from(original));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("#given edits that reproduce the current bytes #when the document is formatted #then the result is unchanged", async () => {
|
|
95
|
+
// given
|
|
96
|
+
const original = "const a = 1;\n";
|
|
97
|
+
const { filePath } = workspaceWith("sample.ts", original);
|
|
98
|
+
const edits: TextEdit[] = [
|
|
99
|
+
{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 12 } }, newText: "const a = 1;" },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// when
|
|
103
|
+
const result = await formatDocumentWithClient(stubClient(edits), filePath);
|
|
104
|
+
|
|
105
|
+
// then
|
|
106
|
+
expect(result.status).toBe("unchanged");
|
|
107
|
+
expect(readFileSync(filePath, "utf-8")).toBe(original);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("#given the file shrinks after the server computed its edits #when the edits are applied #then the stale edit is rejected instead of corrupting the file", async () => {
|
|
111
|
+
// given
|
|
112
|
+
const { filePath } = workspaceWith("sample.ts", "const a=1\nconst b=2\n");
|
|
113
|
+
const staleEdits: TextEdit[] = [
|
|
114
|
+
{ range: { start: { line: 1, character: 0 }, end: { line: 1, character: 9 } }, newText: "const b = 2;" },
|
|
115
|
+
];
|
|
116
|
+
const racingClient = {
|
|
117
|
+
async formatDocument() {
|
|
118
|
+
writeFileSync(filePath, "short\n", "utf-8");
|
|
119
|
+
return staleEdits;
|
|
120
|
+
},
|
|
121
|
+
async openFile() {},
|
|
122
|
+
} as Pick<LspClient, "formatDocument" | "openFile"> as LspClient;
|
|
123
|
+
|
|
124
|
+
// when / then
|
|
125
|
+
await expect(formatDocumentWithClient(racingClient, filePath)).rejects.toThrow(/outside line/);
|
|
126
|
+
expect(readFileSync(filePath, "utf-8")).toBe("short\n");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("#given a formatted document #when the write completes #then no temporary file is left behind", async () => {
|
|
130
|
+
// given
|
|
131
|
+
const { root, filePath } = workspaceWith("sample.ts", "const a=1\n");
|
|
132
|
+
const edits: TextEdit[] = [
|
|
133
|
+
{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 9 } }, newText: "const a = 1;" },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
// when
|
|
137
|
+
await formatDocumentWithClient(stubClient(edits), filePath);
|
|
138
|
+
|
|
139
|
+
// then
|
|
140
|
+
expect(readFileSync(filePath, "utf-8")).toBe("const a = 1;\n");
|
|
141
|
+
expect(() => statSync(join(root, "sample.ts.tmp"))).toThrow();
|
|
142
|
+
});
|
|
143
|
+
});
|