better-dsh 0.2.3-e → 0.2.3-g
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/docs/50_test-reports/2026-09-13-preact-ui-shell/345/256/236/346/265/213/346/212/245/345/221/212.md +1 -1
- package/docs/50_test-reports/2026-09-14-4999-skill/346/270/205/345/215/225/344/270/216lsp-gate/345/256/236/346/265/213/346/212/245/345/221/212.md +54 -0
- package/docs/specs/agent/spec.md +54 -0
- package/docs/specs/ast/spec.md +34 -0
- package/docs/specs/compaction-recall/spec.md +46 -0
- package/docs/specs/ctx/spec.md +107 -0
- package/docs/specs/dsh/spec.md +47 -0
- package/docs/specs/dvc/spec.md +87 -0
- package/docs/specs/escalation-guidance/spec.md +44 -0
- package/docs/specs/fs-scheme-resolution/spec.md +37 -0
- package/docs/specs/hash-edit/spec.md +41 -0
- package/docs/specs/http-read/spec.md +73 -0
- package/docs/specs/kernel-provisioning/spec.md +53 -0
- package/docs/specs/lsp/spec.md +121 -0
- package/docs/specs/mobile-layout/spec.md +108 -0
- package/docs/specs/model-failover/spec.md +20 -0
- package/docs/specs/preact-ui-shell/spec.md +22 -0
- package/docs/specs/repl-dispatch-resilience/spec.md +21 -0
- package/docs/specs/skill/spec.md +58 -0
- package/docs/specs/tool-surface/spec.md +222 -0
- package/docs/specs/url-schema/spec.md +148 -0
- package/docs/specs/web-trust-fence/spec.md +43 -0
- package/dsh-docs/AGENTS.md +75 -0
- package/dsh-docs/agent-lifecycle.md +84 -0
- package/dsh-docs/agent-lifecycle.zh.md +86 -0
- package/dsh-docs/api-gateway.md +164 -0
- package/dsh-docs/api-gateway.zh.md +164 -0
- package/dsh-docs/architecture.md +150 -0
- package/dsh-docs/architecture.zh.md +154 -0
- package/dsh-docs/capability-seams.md +543 -0
- package/dsh-docs/capability-seams.zh.md +545 -0
- package/dsh-docs/config-catalog.md +3473 -0
- package/dsh-docs/config-catalog.zh.md +3474 -0
- package/dsh-docs/cookbook/adding-a-package.md +117 -0
- package/dsh-docs/cookbook/adding-a-package.zh.md +119 -0
- package/dsh-docs/cookbook/adding-a-remote-api.md +197 -0
- package/dsh-docs/cookbook/adding-a-remote-api.zh.md +197 -0
- package/dsh-docs/cookbook/adding-a-settings-card.md +102 -0
- package/dsh-docs/cookbook/adding-a-settings-card.zh.md +102 -0
- package/dsh-docs/cookbook/adding-a-tool.md +101 -0
- package/dsh-docs/cookbook/adding-a-tool.zh.md +103 -0
- package/dsh-docs/cookbook/adding-a-vendored-package.md +59 -0
- package/dsh-docs/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/dsh-docs/cookbook/adding-an-llm-adapter.md +43 -0
- package/dsh-docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
- package/dsh-docs/cookbook/extension-cookbook.md +132 -0
- package/dsh-docs/cookbook/extension-cookbook.zh.md +136 -0
- package/dsh-docs/cookbook/maintaining-dsh-code-review.md +64 -0
- package/dsh-docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/dsh-docs/cordis-api/context.md +364 -0
- package/dsh-docs/cordis-api/context.zh.md +366 -0
- package/dsh-docs/cordis-api/events.md +207 -0
- package/dsh-docs/cordis-api/events.zh.md +209 -0
- package/dsh-docs/cordis-api/fiber.md +375 -0
- package/dsh-docs/cordis-api/fiber.zh.md +377 -0
- package/dsh-docs/cordis-api/inherited.md +39 -0
- package/dsh-docs/cordis-api/registry.md +152 -0
- package/dsh-docs/cordis-api/registry.zh.md +154 -0
- package/dsh-docs/cordis-api/service.md +102 -0
- package/dsh-docs/cordis-api/service.zh.md +104 -0
- package/dsh-docs/cordis-primer.md +45 -0
- package/dsh-docs/cordis-primer.zh.md +51 -0
- package/dsh-docs/cordis-tutorial/01-first-plugin.md +95 -0
- package/dsh-docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
- package/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
- package/dsh-docs/cordis-tutorial/03-services.md +98 -0
- package/dsh-docs/cordis-tutorial/03-services.zh.md +98 -0
- package/dsh-docs/cordis-tutorial/04-events.md +144 -0
- package/dsh-docs/cordis-tutorial/04-events.zh.md +144 -0
- package/dsh-docs/cordis-tutorial/05-config.md +84 -0
- package/dsh-docs/cordis-tutorial/05-config.zh.md +84 -0
- package/dsh-docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/dsh-docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
- package/dsh-docs/cordis-tutorial/07-into-the-harness.md +108 -0
- package/dsh-docs/cordis-tutorial/07-into-the-harness.zh.md +108 -0
- package/dsh-docs/cordis-tutorial/index.md +60 -0
- package/dsh-docs/cordis-tutorial/index.zh.md +62 -0
- package/dsh-docs/deepseek-llm-api-wire-extensions.md +163 -0
- package/dsh-docs/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/dsh-docs/defensive-patterns.md +33 -0
- package/dsh-docs/defensive-patterns.zh.md +35 -0
- package/dsh-docs/development.md +167 -0
- package/dsh-docs/development.zh.md +173 -0
- package/dsh-docs/event-producer-consumer.md +86 -0
- package/dsh-docs/event-producer-consumer.zh.md +88 -0
- package/dsh-docs/glossary.md +45 -0
- package/dsh-docs/glossary.zh.md +45 -0
- package/dsh-docs/graph-atlas.md +22 -0
- package/dsh-docs/graph-atlas.zh.md +24 -0
- package/dsh-docs/i18n/README.md +60 -0
- package/dsh-docs/i18n/README.zh.md +62 -0
- package/dsh-docs/i18n/style-samples.md +87 -0
- package/dsh-docs/i18n/terminology.md +214 -0
- package/dsh-docs/i18n/translation-prompt.md +263 -0
- package/dsh-docs/i18n/translation-rules.md +69 -0
- package/dsh-docs/i18n/translation-rules.zh.md +69 -0
- package/dsh-docs/module-graph.md +1411 -0
- package/dsh-docs/module-graph.zh.md +1413 -0
- package/dsh-docs/persistence-catalog.md +1075 -0
- package/dsh-docs/persistence-catalog.zh.md +1077 -0
- package/dsh-docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/dsh-docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/dsh-docs/postmortem/README.md +18 -0
- package/dsh-docs/postmortem/README.zh.md +18 -0
- package/dsh-docs/rescope.md +53 -0
- package/dsh-docs/rescope.zh.md +53 -0
- package/dsh-docs/subsystems/README.md +61 -0
- package/dsh-docs/subsystems/README.zh.md +61 -0
- package/dsh-docs/subsystems/agent-team.md +207 -0
- package/dsh-docs/subsystems/agent-team.zh.md +207 -0
- package/dsh-docs/subsystems/approval.md +170 -0
- package/dsh-docs/subsystems/approval.zh.md +170 -0
- package/dsh-docs/subsystems/attachment.md +351 -0
- package/dsh-docs/subsystems/attachment.zh.md +351 -0
- package/dsh-docs/subsystems/client-modules.md +168 -0
- package/dsh-docs/subsystems/client-modules.zh.md +168 -0
- package/dsh-docs/subsystems/code-runtime.md +195 -0
- package/dsh-docs/subsystems/code-runtime.zh.md +195 -0
- package/dsh-docs/subsystems/commands.md +219 -0
- package/dsh-docs/subsystems/commands.zh.md +219 -0
- package/dsh-docs/subsystems/compaction.md +238 -0
- package/dsh-docs/subsystems/compaction.zh.md +238 -0
- package/dsh-docs/subsystems/conversation.md +258 -0
- package/dsh-docs/subsystems/conversation.zh.md +258 -0
- package/dsh-docs/subsystems/core.md +1209 -0
- package/dsh-docs/subsystems/core.zh.md +1219 -0
- package/dsh-docs/subsystems/credentials.md +329 -0
- package/dsh-docs/subsystems/credentials.zh.md +329 -0
- package/dsh-docs/subsystems/extensions.md +382 -0
- package/dsh-docs/subsystems/extensions.zh.md +382 -0
- package/dsh-docs/subsystems/feedback.md +266 -0
- package/dsh-docs/subsystems/feedback.zh.md +266 -0
- package/dsh-docs/subsystems/filesystem.md +505 -0
- package/dsh-docs/subsystems/filesystem.zh.md +505 -0
- package/dsh-docs/subsystems/goal.md +277 -0
- package/dsh-docs/subsystems/goal.zh.md +277 -0
- package/dsh-docs/subsystems/invariants.md +88 -0
- package/dsh-docs/subsystems/invariants.zh.md +88 -0
- package/dsh-docs/subsystems/jobs.md +290 -0
- package/dsh-docs/subsystems/jobs.zh.md +290 -0
- package/dsh-docs/subsystems/llm-streaming.md +1080 -0
- package/dsh-docs/subsystems/llm-streaming.zh.md +1086 -0
- package/dsh-docs/subsystems/lsp.md +202 -0
- package/dsh-docs/subsystems/lsp.zh.md +202 -0
- package/dsh-docs/subsystems/permission-presets.md +131 -0
- package/dsh-docs/subsystems/permission-presets.zh.md +131 -0
- package/dsh-docs/subsystems/persistence.md +395 -0
- package/dsh-docs/subsystems/persistence.zh.md +395 -0
- package/dsh-docs/subsystems/plan.md +87 -0
- package/dsh-docs/subsystems/plan.zh.md +87 -0
- package/dsh-docs/subsystems/sandbox.md +220 -0
- package/dsh-docs/subsystems/sandbox.zh.md +220 -0
- package/dsh-docs/subsystems/schedule.md +192 -0
- package/dsh-docs/subsystems/schedule.zh.md +192 -0
- package/dsh-docs/subsystems/scope.md +59 -0
- package/dsh-docs/subsystems/scope.zh.md +59 -0
- package/dsh-docs/subsystems/session-projection.md +354 -0
- package/dsh-docs/subsystems/session-projection.zh.md +354 -0
- package/dsh-docs/subsystems/session-query.md +509 -0
- package/dsh-docs/subsystems/session-query.zh.md +509 -0
- package/dsh-docs/subsystems/session-reference.md +219 -0
- package/dsh-docs/subsystems/session-reference.zh.md +219 -0
- package/dsh-docs/subsystems/session-telemetry.md +194 -0
- package/dsh-docs/subsystems/session-telemetry.zh.md +194 -0
- package/dsh-docs/subsystems/session-title.md +204 -0
- package/dsh-docs/subsystems/session-title.zh.md +204 -0
- package/dsh-docs/subsystems/session.md +1155 -0
- package/dsh-docs/subsystems/session.zh.md +1159 -0
- package/dsh-docs/subsystems/settings.md +405 -0
- package/dsh-docs/subsystems/settings.zh.md +405 -0
- package/dsh-docs/subsystems/shell.md +303 -0
- package/dsh-docs/subsystems/shell.zh.md +303 -0
- package/dsh-docs/subsystems/skills.md +354 -0
- package/dsh-docs/subsystems/skills.zh.md +354 -0
- package/dsh-docs/subsystems/slots.md +175 -0
- package/dsh-docs/subsystems/slots.zh.md +175 -0
- package/dsh-docs/subsystems/spill.md +117 -0
- package/dsh-docs/subsystems/spill.zh.md +117 -0
- package/dsh-docs/subsystems/storage.md +260 -0
- package/dsh-docs/subsystems/storage.zh.md +260 -0
- package/dsh-docs/subsystems/subagent.md +766 -0
- package/dsh-docs/subsystems/subagent.zh.md +770 -0
- package/dsh-docs/subsystems/subprocess.md +324 -0
- package/dsh-docs/subsystems/subprocess.zh.md +324 -0
- package/dsh-docs/subsystems/system-prompt.md +220 -0
- package/dsh-docs/subsystems/system-prompt.zh.md +220 -0
- package/dsh-docs/subsystems/terminal.md +184 -0
- package/dsh-docs/subsystems/terminal.zh.md +184 -0
- package/dsh-docs/subsystems/todo.md +32 -0
- package/dsh-docs/subsystems/todo.zh.md +32 -0
- package/dsh-docs/subsystems/token-meter.md +105 -0
- package/dsh-docs/subsystems/token-meter.zh.md +105 -0
- package/dsh-docs/subsystems/tools.md +720 -0
- package/dsh-docs/subsystems/tools.zh.md +720 -0
- package/dsh-docs/subsystems/typert.md +343 -0
- package/dsh-docs/subsystems/typert.zh.md +343 -0
- package/dsh-docs/subsystems/user-questions.md +178 -0
- package/dsh-docs/subsystems/user-questions.zh.md +178 -0
- package/dsh-docs/subsystems/web-client.md +95 -0
- package/dsh-docs/subsystems/web-client.zh.md +95 -0
- package/dsh-docs/subsystems/web-server.md +154 -0
- package/dsh-docs/subsystems/web-server.zh.md +154 -0
- package/dsh-docs/subsystems/web.md +206 -0
- package/dsh-docs/subsystems/web.zh.md +206 -0
- package/dsh-docs/subsystems/webhook.md +70 -0
- package/dsh-docs/subsystems/webhook.zh.md +70 -0
- package/dsh-docs/subsystems/workflow.md +278 -0
- package/dsh-docs/subsystems/workflow.zh.md +278 -0
- package/dsh-docs/subsystems/workspace.md +321 -0
- package/dsh-docs/subsystems/workspace.zh.md +321 -0
- package/dsh-docs/testing.md +54 -0
- package/dsh-docs/testing.zh.md +54 -0
- package/dsh-docs/tool-catalog.md +2225 -0
- package/dsh-docs/tool-catalog.zh.md +2233 -0
- package/dsh-docs/tool-execution-pipeline.md +62 -0
- package/dsh-docs/tool-execution-pipeline.zh.md +64 -0
- package/dsh-docs/user/develop/basic/config.md +106 -0
- package/dsh-docs/user/develop/basic/config.zh.md +106 -0
- package/dsh-docs/user/develop/basic/index.md +144 -0
- package/dsh-docs/user/develop/basic/index.zh.md +144 -0
- package/dsh-docs/user/develop/basic/publish.md +183 -0
- package/dsh-docs/user/develop/basic/publish.zh.md +183 -0
- package/dsh-docs/user/develop/basic/tool.md +52 -0
- package/dsh-docs/user/develop/basic/tool.zh.md +52 -0
- package/dsh-docs/user/develop/framework/events.md +143 -0
- package/dsh-docs/user/develop/framework/events.zh.md +143 -0
- package/dsh-docs/user/develop/framework/index.md +137 -0
- package/dsh-docs/user/develop/framework/index.zh.md +137 -0
- package/dsh-docs/user/develop/framework/service.md +148 -0
- package/dsh-docs/user/develop/framework/service.zh.md +150 -0
- package/dsh-docs/user/develop/practice/dynamic-cordis.md +15 -0
- package/dsh-docs/user/develop/practice/dynamic-cordis.zh.md +15 -0
- package/dsh-docs/user/develop/practice/index.md +155 -0
- package/dsh-docs/user/develop/practice/index.zh.md +155 -0
- package/dsh-docs/user/develop/practice/llm-adapter.md +189 -0
- package/dsh-docs/user/develop/practice/llm-adapter.zh.md +189 -0
- package/dsh-docs/user/guide/github-review.md +102 -0
- package/dsh-docs/user/guide/github-review.zh.md +102 -0
- package/dsh-docs/user/guide/index.md +30 -0
- package/dsh-docs/user/guide/index.zh.md +30 -0
- package/dsh-docs/user/guide/mcp-memory.md +101 -0
- package/dsh-docs/user/guide/mcp-memory.zh.md +101 -0
- package/dsh-docs/user/guide/network-proxy.md +85 -0
- package/dsh-docs/user/guide/network-proxy.zh.md +85 -0
- package/dsh-docs/user/guide/providers.md +190 -0
- package/dsh-docs/user/guide/providers.zh.md +190 -0
- package/dsh-docs/user/guide/python-sdk.md +150 -0
- package/dsh-docs/user/guide/python-sdk.zh.md +150 -0
- package/dsh-docs/user/guide/schedule.md +21 -0
- package/dsh-docs/user/guide/schedule.zh.md +21 -0
- package/dsh-docs/user/index.md +11 -0
- package/dsh-docs/user/index.zh.md +11 -0
- package/dsh-docs/web-styling.md +29 -0
- package/dsh-docs/web-styling.zh.md +29 -0
- package/lib/client/index.js +268 -38
- package/lib/fs-aware/sandbox-plugin.js +1 -1
- package/lib/index.js +1112 -1261
- package/lib/lsp-server-registry-B8DNonhS.js +3 -0
- package/lib/lsp-server-registry-BexQagaK.js +943 -0
- package/lib/{wrap-DC8O3SYz.js → wrap-JFjcWwZf.js} +42 -16
- package/package.json +2 -1
package/lib/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { a as RESERVED_ERROR_MEMBERS, i as RESERVED_BINDING_GLOBALS, n as DUNDER_MEMBER, o as ReplRuntime, r as PORTABLE_RESERVED_WORDS, t as isFlatBindableName } from "./py-sdk-Chvy92MB.js";
|
|
2
2
|
import { s as resolveKernelEnv } from "./kernel-env-hxaihi9C.js";
|
|
3
|
-
import { a as createDshHandler, c as createDvcHandler, d as registerDvcDevice, f as UrlSchemesError, i as createHttpHandler, l as dispatchDvcWrite, m as SCHEME_NAMES, n as wrapFsWithSchemes, o as UrlResolver, p as parseUrl, r as HTTP_SCHEMES, s as resolveDocsDir, u as listDvcDevices } from "./wrap-
|
|
3
|
+
import { a as createDshHandler, c as createDvcHandler, d as registerDvcDevice, f as UrlSchemesError, i as createHttpHandler, l as dispatchDvcWrite, m as SCHEME_NAMES, n as wrapFsWithSchemes, o as UrlResolver, p as parseUrl, r as HTTP_SCHEMES, s as resolveDocsDir, u as listDvcDevices } from "./wrap-JFjcWwZf.js";
|
|
4
|
+
import { a as resolveCommandPath, i as primaryServerForFile, n as installHintFor, o as serverByName, s as serversForFile, t as findWorkspaceRoot } from "./lsp-server-registry-BexQagaK.js";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { Context } from "@deepseek-ai/cordis";
|
|
6
7
|
import { createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
7
|
-
import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync,
|
|
8
|
+
import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
8
9
|
import * as path from "node:path";
|
|
9
10
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
11
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -2266,7 +2267,7 @@ var DashrRuntime = class extends ReplRuntime {
|
|
|
2266
2267
|
};
|
|
2267
2268
|
|
|
2268
2269
|
//#endregion
|
|
2269
|
-
//#region src/
|
|
2270
|
+
//#region src/devices/ast/natives-loader.ts
|
|
2270
2271
|
/** Platform tags with a published leaf package (mirrors the upstream loader). */
|
|
2271
2272
|
const SUPPORTED_PLATFORM_TAGS = [
|
|
2272
2273
|
"linux-x64",
|
|
@@ -2379,7 +2380,7 @@ function loadPiNatives(options = {}) {
|
|
|
2379
2380
|
}
|
|
2380
2381
|
|
|
2381
2382
|
//#endregion
|
|
2382
|
-
//#region src/
|
|
2383
|
+
//#region src/devices/ast/ast-device.ts
|
|
2383
2384
|
/** Per-run cap on distinct rewritten files (upstream `PI_MAX_AST_FILES` default). */
|
|
2384
2385
|
const MAX_FILES = 1e3;
|
|
2385
2386
|
/** Glob metacharacters that mark a path segment as a pattern (upstream `GLOB_PATH_CHARS`). */
|
|
@@ -2572,7 +2573,7 @@ const summaries = {
|
|
|
2572
2573
|
};
|
|
2573
2574
|
|
|
2574
2575
|
//#endregion
|
|
2575
|
-
//#region src/
|
|
2576
|
+
//#region src/devices/browser/browser-device.ts
|
|
2576
2577
|
let puppeteerModule;
|
|
2577
2578
|
/** Load puppeteer-core once; missing package → structured BROWSER_NO_PUPPETEER. */
|
|
2578
2579
|
async function loadPuppeteer() {
|
|
@@ -2802,12 +2803,12 @@ function registerBrowserDevice(registry = registerDvcDevice) {
|
|
|
2802
2803
|
}
|
|
2803
2804
|
|
|
2804
2805
|
//#endregion
|
|
2805
|
-
//#region src/
|
|
2806
|
+
//#region src/devices/lsp/lsp-jsonrpc.ts
|
|
2806
2807
|
/**
|
|
2807
2808
|
* Content-Length JSON-RPC framing for the vendored `dvc://lsp` device.
|
|
2808
2809
|
*
|
|
2809
2810
|
* Vendored from `upstream/oh-my-pi` (packages/coding-agent/src/jsonrpc/
|
|
2810
|
-
* message-framing.ts, MIT — see ../
|
|
2811
|
+
* message-framing.ts, MIT — see ../NOTICE-OMP.md): near-verbatim port to
|
|
2811
2812
|
* plain Node Buffer APIs; upstream's private `#` fields kept. This is the
|
|
2812
2813
|
* whole vendored surface of that module — no trims were needed.
|
|
2813
2814
|
*/
|
|
@@ -2930,7 +2931,7 @@ var MessageFramer = class {
|
|
|
2930
2931
|
};
|
|
2931
2932
|
|
|
2932
2933
|
//#endregion
|
|
2933
|
-
//#region src/
|
|
2934
|
+
//#region src/devices/lsp/lsp-language.ts
|
|
2934
2935
|
/** Extension (without dot) → LSP language id (defaults.json coverage). */
|
|
2935
2936
|
const EXTENSION_LANGUAGE_ID = {
|
|
2936
2937
|
rs: "rust",
|
|
@@ -3033,7 +3034,7 @@ function detectLanguageId(filePath) {
|
|
|
3033
3034
|
}
|
|
3034
3035
|
|
|
3035
3036
|
//#endregion
|
|
3036
|
-
//#region src/
|
|
3037
|
+
//#region src/devices/lsp/lsp-client.ts
|
|
3037
3038
|
/** Default per-request timeout when no explicit budget is given (upstream 30s). */
|
|
3038
3039
|
const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
3039
3040
|
/** Max time to wait for the `initialize` response (upstream WARMUP/init budget). */
|
|
@@ -3553,6 +3554,10 @@ async function shutdownClientInstance(client) {
|
|
|
3553
3554
|
killClient(client);
|
|
3554
3555
|
return waitForExit(client, EXIT_TIMEOUT_MS);
|
|
3555
3556
|
}
|
|
3557
|
+
/** Snapshot of every live client (stage 2/3: workspace aggregation + fan-out). */
|
|
3558
|
+
function listLiveClients() {
|
|
3559
|
+
return [...clients.values()];
|
|
3560
|
+
}
|
|
3556
3561
|
/** Shut down every live client (upstream shutdownAll). */
|
|
3557
3562
|
async function shutdownAllLspClients() {
|
|
3558
3563
|
const toShutdown = [...clients.values()];
|
|
@@ -3564,886 +3569,127 @@ if (typeof process !== "undefined") process.on("beforeExit", () => {
|
|
|
3564
3569
|
});
|
|
3565
3570
|
|
|
3566
3571
|
//#endregion
|
|
3567
|
-
//#region src/
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
"
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
".hxx",
|
|
3605
|
-
".m",
|
|
3606
|
-
".mm"
|
|
3607
|
-
],
|
|
3608
|
-
"rootMarkers": [
|
|
3609
|
-
"compile_commands.json",
|
|
3610
|
-
"CMakeLists.txt",
|
|
3611
|
-
".clangd",
|
|
3612
|
-
".clang-format",
|
|
3613
|
-
"Makefile"
|
|
3614
|
-
]
|
|
3615
|
-
},
|
|
3616
|
-
zls: {
|
|
3617
|
-
"command": "zls",
|
|
3618
|
-
"args": [],
|
|
3619
|
-
"fileTypes": [".zig"],
|
|
3620
|
-
"rootMarkers": [
|
|
3621
|
-
"build.zig",
|
|
3622
|
-
"build.zig.zon",
|
|
3623
|
-
"zls.json"
|
|
3624
|
-
]
|
|
3625
|
-
},
|
|
3626
|
-
gopls: {
|
|
3627
|
-
"command": "gopls",
|
|
3628
|
-
"args": ["serve"],
|
|
3629
|
-
"fileTypes": [
|
|
3630
|
-
".go",
|
|
3631
|
-
".mod",
|
|
3632
|
-
".sum"
|
|
3633
|
-
],
|
|
3634
|
-
"rootMarkers": [
|
|
3635
|
-
"go.mod",
|
|
3636
|
-
"go.work",
|
|
3637
|
-
"go.sum"
|
|
3638
|
-
],
|
|
3639
|
-
"settings": { "gopls": {
|
|
3640
|
-
"analyses": {
|
|
3641
|
-
"unusedparams": true,
|
|
3642
|
-
"shadow": true
|
|
3643
|
-
},
|
|
3644
|
-
"staticcheck": true,
|
|
3645
|
-
"gofumpt": true
|
|
3646
|
-
} }
|
|
3647
|
-
},
|
|
3648
|
-
"typescript-language-server": {
|
|
3649
|
-
"command": "typescript-language-server",
|
|
3650
|
-
"args": ["--stdio"],
|
|
3651
|
-
"fileTypes": [
|
|
3652
|
-
".ts",
|
|
3653
|
-
".tsx",
|
|
3654
|
-
".js",
|
|
3655
|
-
".jsx",
|
|
3656
|
-
".mjs",
|
|
3657
|
-
".cjs"
|
|
3658
|
-
],
|
|
3659
|
-
"rootMarkers": [
|
|
3660
|
-
"package.json",
|
|
3661
|
-
"tsconfig.json",
|
|
3662
|
-
"jsconfig.json"
|
|
3663
|
-
],
|
|
3664
|
-
"initOptions": {
|
|
3665
|
-
"hostInfo": "omp-coding-agent",
|
|
3666
|
-
"preferences": {
|
|
3667
|
-
"includeInlayParameterNameHints": "all",
|
|
3668
|
-
"includeInlayVariableTypeHints": true,
|
|
3669
|
-
"includeInlayFunctionParameterTypeHints": true
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
},
|
|
3673
|
-
biome: {
|
|
3674
|
-
"command": "biome",
|
|
3675
|
-
"args": ["lsp-proxy"],
|
|
3676
|
-
"fileTypes": [
|
|
3677
|
-
".ts",
|
|
3678
|
-
".tsx",
|
|
3679
|
-
".js",
|
|
3680
|
-
".jsx",
|
|
3681
|
-
".mjs",
|
|
3682
|
-
".cjs",
|
|
3683
|
-
".json",
|
|
3684
|
-
".jsonc",
|
|
3685
|
-
".css"
|
|
3686
|
-
],
|
|
3687
|
-
"rootMarkers": ["biome.json", "biome.jsonc"],
|
|
3688
|
-
"isLinter": true
|
|
3689
|
-
},
|
|
3690
|
-
eslint: {
|
|
3691
|
-
"command": "vscode-eslint-language-server",
|
|
3692
|
-
"args": ["--stdio"],
|
|
3693
|
-
"fileTypes": [
|
|
3694
|
-
".ts",
|
|
3695
|
-
".tsx",
|
|
3696
|
-
".js",
|
|
3697
|
-
".jsx",
|
|
3698
|
-
".mjs",
|
|
3699
|
-
".cjs",
|
|
3700
|
-
".vue",
|
|
3701
|
-
".svelte"
|
|
3702
|
-
],
|
|
3703
|
-
"rootMarkers": [
|
|
3704
|
-
".eslintrc",
|
|
3705
|
-
".eslintrc.js",
|
|
3706
|
-
".eslintrc.json",
|
|
3707
|
-
".eslintrc.yml",
|
|
3708
|
-
"eslint.config.js",
|
|
3709
|
-
"eslint.config.mjs"
|
|
3710
|
-
],
|
|
3711
|
-
"isLinter": true,
|
|
3712
|
-
"settings": {
|
|
3713
|
-
"validate": "on",
|
|
3714
|
-
"run": "onType"
|
|
3715
|
-
}
|
|
3716
|
-
},
|
|
3717
|
-
denols: {
|
|
3718
|
-
"command": "deno",
|
|
3719
|
-
"args": ["lsp"],
|
|
3720
|
-
"fileTypes": [
|
|
3721
|
-
".ts",
|
|
3722
|
-
".tsx",
|
|
3723
|
-
".js",
|
|
3724
|
-
".jsx"
|
|
3725
|
-
],
|
|
3726
|
-
"rootMarkers": [
|
|
3727
|
-
"deno.json",
|
|
3728
|
-
"deno.jsonc",
|
|
3729
|
-
"deno.lock"
|
|
3730
|
-
],
|
|
3731
|
-
"initOptions": {
|
|
3732
|
-
"enable": true,
|
|
3733
|
-
"lint": true,
|
|
3734
|
-
"unstable": true
|
|
3735
|
-
}
|
|
3736
|
-
},
|
|
3737
|
-
"vscode-html-language-server": {
|
|
3738
|
-
"command": "vscode-html-language-server",
|
|
3739
|
-
"args": ["--stdio"],
|
|
3740
|
-
"fileTypes": [".html", ".htm"],
|
|
3741
|
-
"rootMarkers": ["package.json", ".git"],
|
|
3742
|
-
"initOptions": { "provideFormatter": true }
|
|
3743
|
-
},
|
|
3744
|
-
"vscode-css-language-server": {
|
|
3745
|
-
"command": "vscode-css-language-server",
|
|
3746
|
-
"args": ["--stdio"],
|
|
3747
|
-
"fileTypes": [
|
|
3748
|
-
".css",
|
|
3749
|
-
".scss",
|
|
3750
|
-
".sass",
|
|
3751
|
-
".less"
|
|
3752
|
-
],
|
|
3753
|
-
"rootMarkers": ["package.json", ".git"],
|
|
3754
|
-
"initOptions": { "provideFormatter": true }
|
|
3755
|
-
},
|
|
3756
|
-
"vscode-json-language-server": {
|
|
3757
|
-
"command": "vscode-json-language-server",
|
|
3758
|
-
"args": ["--stdio"],
|
|
3759
|
-
"fileTypes": [".json", ".jsonc"],
|
|
3760
|
-
"rootMarkers": ["package.json", ".git"],
|
|
3761
|
-
"initOptions": { "provideFormatter": true }
|
|
3762
|
-
},
|
|
3763
|
-
tailwindcss: {
|
|
3764
|
-
"command": "tailwindcss-language-server",
|
|
3765
|
-
"args": ["--stdio"],
|
|
3766
|
-
"fileTypes": [
|
|
3767
|
-
".html",
|
|
3768
|
-
".css",
|
|
3769
|
-
".scss",
|
|
3770
|
-
".js",
|
|
3771
|
-
".jsx",
|
|
3772
|
-
".ts",
|
|
3773
|
-
".tsx",
|
|
3774
|
-
".vue",
|
|
3775
|
-
".svelte"
|
|
3776
|
-
],
|
|
3777
|
-
"rootMarkers": [
|
|
3778
|
-
"tailwind.config.js",
|
|
3779
|
-
"tailwind.config.ts",
|
|
3780
|
-
"tailwind.config.mjs",
|
|
3781
|
-
"tailwind.config.cjs"
|
|
3782
|
-
]
|
|
3783
|
-
},
|
|
3784
|
-
svelte: {
|
|
3785
|
-
"command": "svelteserver",
|
|
3786
|
-
"args": ["--stdio"],
|
|
3787
|
-
"fileTypes": [".svelte"],
|
|
3788
|
-
"rootMarkers": [
|
|
3789
|
-
"svelte.config.js",
|
|
3790
|
-
"svelte.config.mjs",
|
|
3791
|
-
"package.json"
|
|
3792
|
-
]
|
|
3793
|
-
},
|
|
3794
|
-
"vue-language-server": {
|
|
3795
|
-
"command": "vue-language-server",
|
|
3796
|
-
"args": ["--stdio"],
|
|
3797
|
-
"fileTypes": [".vue"],
|
|
3798
|
-
"rootMarkers": [
|
|
3799
|
-
"vue.config.js",
|
|
3800
|
-
"nuxt.config.js",
|
|
3801
|
-
"nuxt.config.ts",
|
|
3802
|
-
"package.json"
|
|
3803
|
-
]
|
|
3804
|
-
},
|
|
3805
|
-
astro: {
|
|
3806
|
-
"command": "astro-ls",
|
|
3807
|
-
"args": ["--stdio"],
|
|
3808
|
-
"fileTypes": [".astro"],
|
|
3809
|
-
"rootMarkers": [
|
|
3810
|
-
"astro.config.mjs",
|
|
3811
|
-
"astro.config.js",
|
|
3812
|
-
"astro.config.ts"
|
|
3813
|
-
]
|
|
3814
|
-
},
|
|
3815
|
-
pyright: {
|
|
3816
|
-
"command": "pyright-langserver",
|
|
3817
|
-
"args": ["--stdio"],
|
|
3818
|
-
"fileTypes": [".py", ".pyi"],
|
|
3819
|
-
"rootMarkers": [
|
|
3820
|
-
"pyproject.toml",
|
|
3821
|
-
"pyrightconfig.json",
|
|
3822
|
-
"setup.py",
|
|
3823
|
-
"setup.cfg",
|
|
3824
|
-
"requirements.txt",
|
|
3825
|
-
"Pipfile"
|
|
3826
|
-
],
|
|
3827
|
-
"settings": { "python": { "analysis": {
|
|
3828
|
-
"autoSearchPaths": true,
|
|
3829
|
-
"diagnosticMode": "openFilesOnly",
|
|
3830
|
-
"useLibraryCodeForTypes": true
|
|
3831
|
-
} } }
|
|
3832
|
-
},
|
|
3833
|
-
basedpyright: {
|
|
3834
|
-
"command": "basedpyright-langserver",
|
|
3835
|
-
"args": ["--stdio"],
|
|
3836
|
-
"fileTypes": [".py", ".pyi"],
|
|
3837
|
-
"rootMarkers": [
|
|
3838
|
-
"pyproject.toml",
|
|
3839
|
-
"pyrightconfig.json",
|
|
3840
|
-
"setup.py",
|
|
3841
|
-
"requirements.txt"
|
|
3842
|
-
],
|
|
3843
|
-
"settings": { "basedpyright": { "analysis": {
|
|
3844
|
-
"autoSearchPaths": true,
|
|
3845
|
-
"diagnosticMode": "openFilesOnly",
|
|
3846
|
-
"useLibraryCodeForTypes": true
|
|
3847
|
-
} } }
|
|
3848
|
-
},
|
|
3849
|
-
pylsp: {
|
|
3850
|
-
"command": "pylsp",
|
|
3851
|
-
"args": [],
|
|
3852
|
-
"fileTypes": [".py"],
|
|
3853
|
-
"rootMarkers": [
|
|
3854
|
-
"pyproject.toml",
|
|
3855
|
-
"setup.py",
|
|
3856
|
-
"setup.cfg",
|
|
3857
|
-
"requirements.txt",
|
|
3858
|
-
"Pipfile"
|
|
3859
|
-
]
|
|
3860
|
-
},
|
|
3861
|
-
ty: {
|
|
3862
|
-
"command": "ty",
|
|
3863
|
-
"args": ["server"],
|
|
3864
|
-
"fileTypes": [".py", ".pyi"],
|
|
3865
|
-
"rootMarkers": [
|
|
3866
|
-
"pyproject.toml",
|
|
3867
|
-
"ty.toml",
|
|
3868
|
-
"setup.py",
|
|
3869
|
-
"setup.cfg",
|
|
3870
|
-
"requirements.txt",
|
|
3871
|
-
"Pipfile"
|
|
3872
|
-
]
|
|
3873
|
-
},
|
|
3874
|
-
ruff: {
|
|
3875
|
-
"command": "ruff",
|
|
3876
|
-
"args": ["server"],
|
|
3877
|
-
"fileTypes": [".py", ".pyi"],
|
|
3878
|
-
"rootMarkers": [
|
|
3879
|
-
"pyproject.toml",
|
|
3880
|
-
"ruff.toml",
|
|
3881
|
-
".ruff.toml"
|
|
3882
|
-
],
|
|
3883
|
-
"isLinter": true
|
|
3884
|
-
},
|
|
3885
|
-
jdtls: {
|
|
3886
|
-
"command": "jdtls",
|
|
3887
|
-
"args": [],
|
|
3888
|
-
"fileTypes": [".java"],
|
|
3889
|
-
"rootMarkers": [
|
|
3890
|
-
"pom.xml",
|
|
3891
|
-
"build.gradle",
|
|
3892
|
-
"build.gradle.kts",
|
|
3893
|
-
"settings.gradle",
|
|
3894
|
-
".project"
|
|
3895
|
-
]
|
|
3896
|
-
},
|
|
3897
|
-
"kotlin-lsp": {
|
|
3898
|
-
"command": "kotlin-lsp",
|
|
3899
|
-
"args": ["--stdio"],
|
|
3900
|
-
"fileTypes": [".kt", ".kts"],
|
|
3901
|
-
"rootMarkers": [
|
|
3902
|
-
"build.gradle",
|
|
3903
|
-
"build.gradle.kts",
|
|
3904
|
-
"pom.xml",
|
|
3905
|
-
"settings.gradle",
|
|
3906
|
-
"settings.gradle.kts"
|
|
3907
|
-
]
|
|
3908
|
-
},
|
|
3909
|
-
metals: {
|
|
3910
|
-
"command": "metals",
|
|
3911
|
-
"args": [],
|
|
3912
|
-
"fileTypes": [
|
|
3913
|
-
".scala",
|
|
3914
|
-
".sbt",
|
|
3915
|
-
".sc"
|
|
3916
|
-
],
|
|
3917
|
-
"rootMarkers": [
|
|
3918
|
-
"build.sbt",
|
|
3919
|
-
"build.sc",
|
|
3920
|
-
"build.gradle",
|
|
3921
|
-
"pom.xml"
|
|
3922
|
-
],
|
|
3923
|
-
"initOptions": {
|
|
3924
|
-
"statusBarProvider": "show-message",
|
|
3925
|
-
"isHttpEnabled": true
|
|
3926
|
-
}
|
|
3927
|
-
},
|
|
3928
|
-
hls: {
|
|
3929
|
-
"command": "haskell-language-server-wrapper",
|
|
3930
|
-
"args": ["--lsp"],
|
|
3931
|
-
"fileTypes": [".hs", ".lhs"],
|
|
3932
|
-
"rootMarkers": [
|
|
3933
|
-
"stack.yaml",
|
|
3934
|
-
"cabal.project",
|
|
3935
|
-
"hie.yaml",
|
|
3936
|
-
"package.yaml",
|
|
3937
|
-
"*.cabal"
|
|
3938
|
-
],
|
|
3939
|
-
"settings": { "haskell": {
|
|
3940
|
-
"formattingProvider": "ormolu",
|
|
3941
|
-
"checkProject": true
|
|
3942
|
-
} }
|
|
3943
|
-
},
|
|
3944
|
-
ocamllsp: {
|
|
3945
|
-
"command": "ocamllsp",
|
|
3946
|
-
"args": [],
|
|
3947
|
-
"fileTypes": [
|
|
3948
|
-
".ml",
|
|
3949
|
-
".mli",
|
|
3950
|
-
".mll",
|
|
3951
|
-
".mly"
|
|
3952
|
-
],
|
|
3953
|
-
"rootMarkers": [
|
|
3954
|
-
"dune-project",
|
|
3955
|
-
"dune-workspace",
|
|
3956
|
-
"*.opam",
|
|
3957
|
-
".ocamlformat"
|
|
3958
|
-
]
|
|
3959
|
-
},
|
|
3960
|
-
elixirls: {
|
|
3961
|
-
"command": "elixir-ls",
|
|
3962
|
-
"args": [],
|
|
3963
|
-
"fileTypes": [
|
|
3964
|
-
".ex",
|
|
3965
|
-
".exs",
|
|
3966
|
-
".heex",
|
|
3967
|
-
".eex"
|
|
3968
|
-
],
|
|
3969
|
-
"rootMarkers": ["mix.exs", "mix.lock"],
|
|
3970
|
-
"settings": { "elixirLS": {
|
|
3971
|
-
"dialyzerEnabled": true,
|
|
3972
|
-
"fetchDeps": false
|
|
3973
|
-
} }
|
|
3974
|
-
},
|
|
3975
|
-
expert: {
|
|
3976
|
-
"command": "expert",
|
|
3977
|
-
"args": ["--stdio"],
|
|
3978
|
-
"fileTypes": [
|
|
3979
|
-
".ex",
|
|
3980
|
-
".exs",
|
|
3981
|
-
".heex",
|
|
3982
|
-
".eex"
|
|
3983
|
-
],
|
|
3984
|
-
"rootMarkers": ["mix.exs", "mix.lock"]
|
|
3985
|
-
},
|
|
3986
|
-
erlangls: {
|
|
3987
|
-
"command": "erlang_ls",
|
|
3988
|
-
"args": [],
|
|
3989
|
-
"fileTypes": [".erl", ".hrl"],
|
|
3990
|
-
"rootMarkers": [
|
|
3991
|
-
"rebar.config",
|
|
3992
|
-
"erlang.mk",
|
|
3993
|
-
"rebar.lock"
|
|
3994
|
-
]
|
|
3995
|
-
},
|
|
3996
|
-
gleam: {
|
|
3997
|
-
"command": "gleam",
|
|
3998
|
-
"args": ["lsp"],
|
|
3999
|
-
"fileTypes": [".gleam"],
|
|
4000
|
-
"rootMarkers": ["gleam.toml"]
|
|
4001
|
-
},
|
|
4002
|
-
solargraph: {
|
|
4003
|
-
"command": "solargraph",
|
|
4004
|
-
"args": ["stdio"],
|
|
4005
|
-
"fileTypes": [
|
|
4006
|
-
".rb",
|
|
4007
|
-
".rake",
|
|
4008
|
-
".gemspec"
|
|
4009
|
-
],
|
|
4010
|
-
"rootMarkers": [
|
|
4011
|
-
"Gemfile",
|
|
4012
|
-
".solargraph.yml",
|
|
4013
|
-
"Rakefile"
|
|
4014
|
-
],
|
|
4015
|
-
"initOptions": { "formatting": true },
|
|
4016
|
-
"settings": { "solargraph": {
|
|
4017
|
-
"diagnostics": true,
|
|
4018
|
-
"completion": true,
|
|
4019
|
-
"hover": true,
|
|
4020
|
-
"formatting": true,
|
|
4021
|
-
"references": true,
|
|
4022
|
-
"rename": true,
|
|
4023
|
-
"symbols": true
|
|
4024
|
-
} }
|
|
4025
|
-
},
|
|
4026
|
-
"ruby-lsp": {
|
|
4027
|
-
"command": "ruby-lsp",
|
|
4028
|
-
"args": [],
|
|
4029
|
-
"fileTypes": [
|
|
4030
|
-
".rb",
|
|
4031
|
-
".rake",
|
|
4032
|
-
".gemspec",
|
|
4033
|
-
".erb"
|
|
4034
|
-
],
|
|
4035
|
-
"rootMarkers": [
|
|
4036
|
-
"Gemfile",
|
|
4037
|
-
".ruby-version",
|
|
4038
|
-
".ruby-gemset"
|
|
4039
|
-
],
|
|
4040
|
-
"initOptions": { "formatter": "auto" }
|
|
4041
|
-
},
|
|
4042
|
-
rubocop: {
|
|
4043
|
-
"command": "rubocop",
|
|
4044
|
-
"args": ["--lsp"],
|
|
4045
|
-
"fileTypes": [".rb", ".rake"],
|
|
4046
|
-
"rootMarkers": [".rubocop.yml", "Gemfile"],
|
|
4047
|
-
"isLinter": true
|
|
4048
|
-
},
|
|
4049
|
-
bashls: {
|
|
4050
|
-
"command": "bash-language-server",
|
|
4051
|
-
"args": ["start"],
|
|
4052
|
-
"fileTypes": [
|
|
4053
|
-
".sh",
|
|
4054
|
-
".bash",
|
|
4055
|
-
".zsh"
|
|
4056
|
-
],
|
|
4057
|
-
"rootMarkers": [".git"],
|
|
4058
|
-
"settings": { "bashIde": { "globPattern": "*@(.sh|.inc|.bash|.command)" } }
|
|
4059
|
-
},
|
|
4060
|
-
"lua-language-server": {
|
|
4061
|
-
"command": "lua-language-server",
|
|
4062
|
-
"args": [],
|
|
4063
|
-
"fileTypes": [".lua"],
|
|
4064
|
-
"rootMarkers": [
|
|
4065
|
-
".luarc.json",
|
|
4066
|
-
".luarc.jsonc",
|
|
4067
|
-
".luacheckrc",
|
|
4068
|
-
".stylua.toml",
|
|
4069
|
-
"stylua.toml"
|
|
4070
|
-
],
|
|
4071
|
-
"settings": { "Lua": {
|
|
4072
|
-
"runtime": { "version": "LuaJIT" },
|
|
4073
|
-
"diagnostics": { "globals": ["vim"] },
|
|
4074
|
-
"workspace": { "checkThirdParty": false },
|
|
4075
|
-
"telemetry": { "enable": false }
|
|
4076
|
-
} }
|
|
4077
|
-
},
|
|
4078
|
-
intelephense: {
|
|
4079
|
-
"command": "intelephense",
|
|
4080
|
-
"args": ["--stdio"],
|
|
4081
|
-
"fileTypes": [".php", ".phtml"],
|
|
4082
|
-
"rootMarkers": [
|
|
4083
|
-
"composer.json",
|
|
4084
|
-
"composer.lock",
|
|
4085
|
-
".git"
|
|
4086
|
-
]
|
|
4087
|
-
},
|
|
4088
|
-
phpactor: {
|
|
4089
|
-
"command": "phpactor",
|
|
4090
|
-
"args": ["language-server"],
|
|
4091
|
-
"fileTypes": [".php"],
|
|
4092
|
-
"rootMarkers": [
|
|
4093
|
-
"composer.json",
|
|
4094
|
-
".phpactor.json",
|
|
4095
|
-
".phpactor.yml"
|
|
4096
|
-
]
|
|
4097
|
-
},
|
|
4098
|
-
omnisharp: {
|
|
4099
|
-
"command": "omnisharp",
|
|
4100
|
-
"args": [
|
|
4101
|
-
"-z",
|
|
4102
|
-
"--hostPID",
|
|
4103
|
-
"$PID",
|
|
4104
|
-
"--encoding",
|
|
4105
|
-
"utf-8",
|
|
4106
|
-
"--languageserver"
|
|
4107
|
-
],
|
|
4108
|
-
"fileTypes": [".cs", ".csx"],
|
|
4109
|
-
"rootMarkers": [
|
|
4110
|
-
"*.sln",
|
|
4111
|
-
"*.csproj",
|
|
4112
|
-
"omnisharp.json",
|
|
4113
|
-
".git"
|
|
4114
|
-
],
|
|
4115
|
-
"settings": {
|
|
4116
|
-
"FormattingOptions": { "EnableEditorConfigSupport": true },
|
|
4117
|
-
"RoslynExtensionsOptions": { "EnableAnalyzersSupport": true }
|
|
4118
|
-
}
|
|
4119
|
-
},
|
|
4120
|
-
yamlls: {
|
|
4121
|
-
"command": "yaml-language-server",
|
|
4122
|
-
"args": ["--stdio"],
|
|
4123
|
-
"fileTypes": [".yaml", ".yml"],
|
|
4124
|
-
"rootMarkers": [".git"],
|
|
4125
|
-
"settings": {
|
|
4126
|
-
"yaml": {
|
|
4127
|
-
"validate": true,
|
|
4128
|
-
"format": { "enable": true },
|
|
4129
|
-
"hover": true,
|
|
4130
|
-
"completion": true
|
|
4131
|
-
},
|
|
4132
|
-
"redhat": { "telemetry": { "enabled": false } }
|
|
4133
|
-
}
|
|
4134
|
-
},
|
|
4135
|
-
terraformls: {
|
|
4136
|
-
"command": "terraform-ls",
|
|
4137
|
-
"args": ["serve"],
|
|
4138
|
-
"fileTypes": [".tf", ".tfvars"],
|
|
4139
|
-
"rootMarkers": [
|
|
4140
|
-
".terraform",
|
|
4141
|
-
"terraform.tfstate",
|
|
4142
|
-
"*.tf"
|
|
4143
|
-
]
|
|
4144
|
-
},
|
|
4145
|
-
dockerls: {
|
|
4146
|
-
"command": "docker-langserver",
|
|
4147
|
-
"args": ["--stdio"],
|
|
4148
|
-
"fileTypes": [".dockerfile", "Dockerfile"],
|
|
4149
|
-
"rootMarkers": [
|
|
4150
|
-
"Dockerfile",
|
|
4151
|
-
"docker-compose.yml",
|
|
4152
|
-
"docker-compose.yaml",
|
|
4153
|
-
".dockerignore"
|
|
4154
|
-
]
|
|
4155
|
-
},
|
|
4156
|
-
"helm-ls": {
|
|
4157
|
-
"command": "helm_ls",
|
|
4158
|
-
"args": ["serve"],
|
|
4159
|
-
"fileTypes": [
|
|
4160
|
-
".yaml",
|
|
4161
|
-
".yml",
|
|
4162
|
-
".tpl"
|
|
4163
|
-
],
|
|
4164
|
-
"rootMarkers": ["Chart.yaml", "Chart.yml"]
|
|
4165
|
-
},
|
|
4166
|
-
nixd: {
|
|
4167
|
-
"command": "nixd",
|
|
4168
|
-
"args": [],
|
|
4169
|
-
"fileTypes": [".nix"],
|
|
4170
|
-
"rootMarkers": [
|
|
4171
|
-
"flake.nix",
|
|
4172
|
-
"default.nix",
|
|
4173
|
-
"shell.nix"
|
|
4174
|
-
]
|
|
4175
|
-
},
|
|
4176
|
-
nil: {
|
|
4177
|
-
"command": "nil",
|
|
4178
|
-
"args": [],
|
|
4179
|
-
"fileTypes": [".nix"],
|
|
4180
|
-
"rootMarkers": [
|
|
4181
|
-
"flake.nix",
|
|
4182
|
-
"default.nix",
|
|
4183
|
-
"shell.nix"
|
|
4184
|
-
]
|
|
4185
|
-
},
|
|
4186
|
-
ols: {
|
|
4187
|
-
"command": "ols",
|
|
4188
|
-
"args": [],
|
|
4189
|
-
"fileTypes": [".odin"],
|
|
4190
|
-
"rootMarkers": ["ols.json", ".git"]
|
|
4191
|
-
},
|
|
4192
|
-
dartls: {
|
|
4193
|
-
"command": "dart",
|
|
4194
|
-
"args": ["language-server", "--protocol=lsp"],
|
|
4195
|
-
"fileTypes": [".dart"],
|
|
4196
|
-
"rootMarkers": ["pubspec.yaml", "pubspec.lock"],
|
|
4197
|
-
"initOptions": {
|
|
4198
|
-
"closingLabels": true,
|
|
4199
|
-
"flutterOutline": true,
|
|
4200
|
-
"outline": true
|
|
4201
|
-
}
|
|
4202
|
-
},
|
|
4203
|
-
marksman: {
|
|
4204
|
-
"command": "marksman",
|
|
4205
|
-
"args": ["server"],
|
|
4206
|
-
"fileTypes": [".md", ".markdown"],
|
|
4207
|
-
"rootMarkers": [".marksman.toml", ".git"],
|
|
4208
|
-
"warmupTimeoutMs": 2e3
|
|
4209
|
-
},
|
|
4210
|
-
texlab: {
|
|
4211
|
-
"command": "texlab",
|
|
4212
|
-
"args": [],
|
|
4213
|
-
"fileTypes": [
|
|
4214
|
-
".tex",
|
|
4215
|
-
".bib",
|
|
4216
|
-
".sty",
|
|
4217
|
-
".cls"
|
|
4218
|
-
],
|
|
4219
|
-
"rootMarkers": [
|
|
4220
|
-
".latexmkrc",
|
|
4221
|
-
"latexmkrc",
|
|
4222
|
-
".texlabroot",
|
|
4223
|
-
"texlabroot",
|
|
4224
|
-
"Tectonic.toml"
|
|
4225
|
-
],
|
|
4226
|
-
"settings": { "texlab": {
|
|
4227
|
-
"build": {
|
|
4228
|
-
"executable": "latexmk",
|
|
4229
|
-
"args": [
|
|
4230
|
-
"-pdf",
|
|
4231
|
-
"-interaction=nonstopmode",
|
|
4232
|
-
"-synctex=1",
|
|
4233
|
-
"%f"
|
|
4234
|
-
]
|
|
4235
|
-
},
|
|
4236
|
-
"chktex": { "onOpenAndSave": true }
|
|
4237
|
-
} }
|
|
4238
|
-
},
|
|
4239
|
-
graphql: {
|
|
4240
|
-
"command": "graphql-lsp",
|
|
4241
|
-
"args": [
|
|
4242
|
-
"server",
|
|
4243
|
-
"-m",
|
|
4244
|
-
"stream"
|
|
4245
|
-
],
|
|
4246
|
-
"fileTypes": [".graphql", ".gql"],
|
|
4247
|
-
"rootMarkers": [
|
|
4248
|
-
".graphqlrc",
|
|
4249
|
-
".graphqlrc.json",
|
|
4250
|
-
".graphqlrc.yml",
|
|
4251
|
-
".graphqlrc.yaml",
|
|
4252
|
-
"graphql.config.js"
|
|
4253
|
-
]
|
|
4254
|
-
},
|
|
4255
|
-
prismals: {
|
|
4256
|
-
"command": "prisma-language-server",
|
|
4257
|
-
"args": ["--stdio"],
|
|
4258
|
-
"fileTypes": [".prisma"],
|
|
4259
|
-
"rootMarkers": ["schema.prisma", "prisma/schema.prisma"]
|
|
4260
|
-
},
|
|
4261
|
-
vimls: {
|
|
4262
|
-
"command": "vim-language-server",
|
|
4263
|
-
"args": ["--stdio"],
|
|
4264
|
-
"fileTypes": [".vim", ".vimrc"],
|
|
4265
|
-
"rootMarkers": [".git"],
|
|
4266
|
-
"initOptions": {
|
|
4267
|
-
"isNeovim": true,
|
|
4268
|
-
"diagnostic": { "enable": true }
|
|
4269
|
-
}
|
|
4270
|
-
},
|
|
4271
|
-
"emmet-language-server": {
|
|
4272
|
-
"command": "emmet-language-server",
|
|
4273
|
-
"args": ["--stdio"],
|
|
4274
|
-
"fileTypes": [
|
|
4275
|
-
".html",
|
|
4276
|
-
".css",
|
|
4277
|
-
".scss",
|
|
4278
|
-
".less",
|
|
4279
|
-
".jsx",
|
|
4280
|
-
".tsx",
|
|
4281
|
-
".vue",
|
|
4282
|
-
".svelte"
|
|
4283
|
-
],
|
|
4284
|
-
"rootMarkers": [".git"]
|
|
4285
|
-
},
|
|
4286
|
-
"sourcekit-lsp": {
|
|
4287
|
-
"command": "sourcekit-lsp",
|
|
4288
|
-
"args": [],
|
|
4289
|
-
"fileTypes": [".swift"],
|
|
4290
|
-
"rootMarkers": [
|
|
4291
|
-
"Package.swift",
|
|
4292
|
-
"*.xcodeproj",
|
|
4293
|
-
"*.xcworkspace",
|
|
4294
|
-
"project.yml",
|
|
4295
|
-
".swiftpm"
|
|
4296
|
-
]
|
|
4297
|
-
},
|
|
4298
|
-
swiftlint: {
|
|
4299
|
-
"command": "swiftlint",
|
|
4300
|
-
"args": [
|
|
4301
|
-
"lint",
|
|
4302
|
-
"--quiet",
|
|
4303
|
-
"--reporter",
|
|
4304
|
-
"json"
|
|
4305
|
-
],
|
|
4306
|
-
"fileTypes": [".swift"],
|
|
4307
|
-
"rootMarkers": [
|
|
4308
|
-
".swiftlint.yml",
|
|
4309
|
-
".swiftlint.yaml",
|
|
4310
|
-
"Package.swift",
|
|
4311
|
-
"*.xcodeproj"
|
|
4312
|
-
],
|
|
4313
|
-
"isLinter": true
|
|
3572
|
+
//#region src/devices/lsp/lsp-gate.ts
|
|
3573
|
+
/** Nag ceiling per session (spec: at most 10 availability notices). */
|
|
3574
|
+
const NAG_CAP = 10;
|
|
3575
|
+
/** Languages eligible for the availability notice. */
|
|
3576
|
+
const NAG_LANGUAGES = new Set([
|
|
3577
|
+
"python",
|
|
3578
|
+
"typescript",
|
|
3579
|
+
"javascript",
|
|
3580
|
+
"rust",
|
|
3581
|
+
"go"
|
|
3582
|
+
]);
|
|
3583
|
+
/** Must-have languages installed proactively at first availability check. */
|
|
3584
|
+
const MUST_HAVE = new Set([
|
|
3585
|
+
"python",
|
|
3586
|
+
"typescript",
|
|
3587
|
+
"javascript"
|
|
3588
|
+
]);
|
|
3589
|
+
/** Language id from a file extension (table-driven; unknown → undefined). */
|
|
3590
|
+
function languageOf(filePath) {
|
|
3591
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
3592
|
+
if (ext === ".py") return "python";
|
|
3593
|
+
if (ext === ".ts" || ext === ".tsx" || ext === ".mts" || ext === ".cts") return "typescript";
|
|
3594
|
+
if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "javascript";
|
|
3595
|
+
if (ext === ".rs") return "rust";
|
|
3596
|
+
if (ext === ".go") return "go";
|
|
3597
|
+
}
|
|
3598
|
+
const gates = /* @__PURE__ */ new Map();
|
|
3599
|
+
function gateOf(sessionId) {
|
|
3600
|
+
let gate = gates.get(sessionId);
|
|
3601
|
+
if (gate === void 0) {
|
|
3602
|
+
gate = {
|
|
3603
|
+
state: "unasked",
|
|
3604
|
+
nags: 0,
|
|
3605
|
+
installTried: /* @__PURE__ */ new Set(),
|
|
3606
|
+
warmed: /* @__PURE__ */ new Set()
|
|
3607
|
+
};
|
|
3608
|
+
gates.set(sessionId, gate);
|
|
4314
3609
|
}
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
//#endregion
|
|
4318
|
-
//#region src/url-schemes/vendored/devices/lsp/lsp-server-registry.ts
|
|
4319
|
-
/** The verbatim upstream registry (54 servers), keyed by server name. */
|
|
4320
|
-
const REGISTRY = defaults_default;
|
|
4321
|
-
/** Server names in registry (insertion) order — deterministic selection. */
|
|
4322
|
-
const REGISTRY_NAMES = Object.keys(REGISTRY);
|
|
4323
|
-
/** Cache resolved-command probes: command → absolute path or null (missing). */
|
|
4324
|
-
const commandProbeCache = /* @__PURE__ */ new Map();
|
|
4325
|
-
/**
|
|
4326
|
-
* Find servers whose `fileTypes` cover `filePath` (upstream getServersForFile,
|
|
4327
|
-
* extension match plus the basename entries defaults.json uses for Dockerfiles).
|
|
4328
|
-
*/
|
|
4329
|
-
function serversForFile(filePath) {
|
|
4330
|
-
const lower = filePath.toLowerCase();
|
|
4331
|
-
const baseName = path.posix.basename(lower.replaceAll("\\", "/"));
|
|
4332
|
-
const extension = path.extname(lower);
|
|
4333
|
-
const matches = [];
|
|
4334
|
-
for (const name$2 of REGISTRY_NAMES) {
|
|
4335
|
-
const config = REGISTRY[name$2];
|
|
4336
|
-
if (config === void 0) continue;
|
|
4337
|
-
if (config.fileTypes.some((fileType) => fileType === extension || fileType !== "" && !fileType.startsWith(".") && fileType === baseName)) matches.push([name$2, config]);
|
|
4338
|
-
}
|
|
4339
|
-
return matches;
|
|
3610
|
+
return gate;
|
|
4340
3611
|
}
|
|
4341
|
-
/**
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
*/
|
|
4346
|
-
function
|
|
4347
|
-
|
|
4348
|
-
return matches.find(([, config]) => config.isLinter !== true) ?? matches[0] ?? null;
|
|
4349
|
-
}
|
|
4350
|
-
/** Look up a named registry entry (device `server` arg). */
|
|
4351
|
-
function serverByName(name$2) {
|
|
4352
|
-
return REGISTRY[name$2];
|
|
4353
|
-
}
|
|
4354
|
-
/** One-level wildcard marker match: `*` crosses everything but `/` (upstream glob markers). */
|
|
4355
|
-
function markerMatchesEntry(marker, entry) {
|
|
4356
|
-
const star = marker.indexOf("*");
|
|
4357
|
-
if (star === -1) return marker === entry;
|
|
4358
|
-
const prefix = marker.slice(0, star);
|
|
4359
|
-
const suffix = marker.slice(star + 1);
|
|
4360
|
-
return entry.length >= prefix.length + suffix.length && entry.startsWith(prefix) && entry.endsWith(suffix) && !entry.slice(prefix.length, entry.length - suffix.length).includes("/");
|
|
4361
|
-
}
|
|
4362
|
-
/** Whether any root marker exists directly in `dir` (upstream hasRootMarkers). */
|
|
4363
|
-
function hasRootMarkers(dir, markers) {
|
|
4364
|
-
let entries = null;
|
|
4365
|
-
for (const marker of markers) {
|
|
4366
|
-
if (marker.includes("*")) {
|
|
4367
|
-
if (entries === null) try {
|
|
4368
|
-
entries = readdirSync(dir);
|
|
4369
|
-
} catch {
|
|
4370
|
-
return false;
|
|
4371
|
-
}
|
|
4372
|
-
if (entries.some((entry) => markerMatchesEntry(marker, entry))) return true;
|
|
4373
|
-
continue;
|
|
4374
|
-
}
|
|
4375
|
-
if (existsSync(path.join(dir, marker))) return true;
|
|
4376
|
-
}
|
|
4377
|
-
return false;
|
|
3612
|
+
/** Drop a session's gate state (agent dispose). */
|
|
3613
|
+
function disposeLspGate(sessionId) {
|
|
3614
|
+
gates.delete(sessionId);
|
|
3615
|
+
}
|
|
3616
|
+
/** Current gate state for a session (read side: `dvc://lsp` status). */
|
|
3617
|
+
function lspGateState(sessionId) {
|
|
3618
|
+
return gateOf(sessionId).state;
|
|
4378
3619
|
}
|
|
4379
3620
|
/**
|
|
4380
|
-
*
|
|
4381
|
-
*
|
|
4382
|
-
*
|
|
3621
|
+
* Must-have install (stage 5): for python/typescript/javascript, probe the
|
|
3622
|
+
* registry's primary server binary once per session; when missing, run its
|
|
3623
|
+
* package-manager install in the background (fail-soft — a failed install
|
|
3624
|
+
* degrades to the device's structured LSP_SERVER_MISSING at spawn time, and
|
|
3625
|
+
* is never retried within the session). Niche languages install only after
|
|
3626
|
+
* an explicit gate-on (spec: on-demand).
|
|
4383
3627
|
*/
|
|
4384
|
-
function
|
|
4385
|
-
if (
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
const
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
accessSync(candidate, constants.X_OK);
|
|
4397
|
-
return true;
|
|
4398
|
-
} catch {
|
|
4399
|
-
return false;
|
|
4400
|
-
}
|
|
3628
|
+
function ensureMustHave(gate, language) {
|
|
3629
|
+
if (!MUST_HAVE.has(language) || gate.installTried.has(language)) return;
|
|
3630
|
+
gate.installTried.add(language);
|
|
3631
|
+
(async () => {
|
|
3632
|
+
const registry = await import("./lsp-server-registry-B8DNonhS.js");
|
|
3633
|
+
const probePath = `/tmp/x.${language === "python" ? "py" : "ts"}`;
|
|
3634
|
+
const primary = registry.primaryServerForFile(probePath);
|
|
3635
|
+
if (primary === null) return;
|
|
3636
|
+
const [name$2, config] = primary;
|
|
3637
|
+
if (registry.resolveCommandPath(config.command, "/tmp") !== null) return;
|
|
3638
|
+
if (!await registry.installServer(config.command)) {}
|
|
3639
|
+
})().catch(() => {});
|
|
4401
3640
|
}
|
|
4402
3641
|
/**
|
|
4403
|
-
*
|
|
4404
|
-
*
|
|
4405
|
-
*
|
|
4406
|
-
* `command` args, fake-server fixtures) are probed directly. Result cached
|
|
4407
|
-
* per command; returns `null` when the binary is missing.
|
|
3642
|
+
* Maybe produce the availability notice for a just-landed mutation.
|
|
3643
|
+
* Returns undefined when silent (gate decided, cap reached, non-table
|
|
3644
|
+
* language). Pure — no side effects beyond the nag counter.
|
|
4408
3645
|
*/
|
|
4409
|
-
function
|
|
4410
|
-
const
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
return
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
"
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
3646
|
+
function lspGateNotice(sessionId, filePath) {
|
|
3647
|
+
const gate = gateOf(sessionId);
|
|
3648
|
+
if (gate.state !== "unasked") return void 0;
|
|
3649
|
+
const language = languageOf(filePath);
|
|
3650
|
+
if (language === void 0 || !NAG_LANGUAGES.has(language)) return void 0;
|
|
3651
|
+
ensureMustHave(gate, language);
|
|
3652
|
+
if (gate.nags >= NAG_CAP) return void 0;
|
|
3653
|
+
gate.nags += 1;
|
|
3654
|
+
const remaining = NAG_CAP - gate.nags;
|
|
3655
|
+
return `lsp diagnostics available for this language — write dvc://lsp {"action":"on"} to enable, {"action":"off"} to mute${remaining > 0 ? ` (${remaining} reminders left)` : " (final reminder)"}`;
|
|
3656
|
+
}
|
|
3657
|
+
/** Apply a gate decision. Returns a human-readable ack. */
|
|
3658
|
+
function lspGateDecide(sessionId, on) {
|
|
3659
|
+
const gate = gateOf(sessionId);
|
|
3660
|
+
gate.state = on ? "on" : "off";
|
|
3661
|
+
return on ? "lsp on for this session: servers warm-start per language on first file contact; no repo artifacts are written." : "lsp off for this session: no servers will spawn and notices are muted.";
|
|
3662
|
+
}
|
|
3663
|
+
let transport;
|
|
3664
|
+
/** The device registers its sync/spawn entry point at mount time. */
|
|
3665
|
+
function registerLspGateTransport(fn) {
|
|
3666
|
+
transport = fn;
|
|
3667
|
+
}
|
|
3668
|
+
function lspGateSyncOnLand(sessionId, kind, filePath, content) {
|
|
3669
|
+
if (gateOf(sessionId).state !== "on" || transport === void 0) return;
|
|
3670
|
+
if (languageOf(filePath) === void 0) return;
|
|
3671
|
+
transport({
|
|
3672
|
+
sessionId,
|
|
3673
|
+
kind,
|
|
3674
|
+
filePath,
|
|
3675
|
+
content
|
|
3676
|
+
}).catch(() => {});
|
|
4437
3677
|
}
|
|
4438
3678
|
|
|
4439
3679
|
//#endregion
|
|
4440
|
-
//#region src/
|
|
3680
|
+
//#region src/devices/lsp/lsp-device.ts
|
|
4441
3681
|
const ACTIONS = new Set([
|
|
4442
3682
|
"diagnostics",
|
|
4443
3683
|
"definition",
|
|
4444
3684
|
"references",
|
|
4445
3685
|
"hover",
|
|
4446
|
-
"format"
|
|
3686
|
+
"format",
|
|
3687
|
+
"on",
|
|
3688
|
+
"off",
|
|
3689
|
+
"status",
|
|
3690
|
+
"rename",
|
|
3691
|
+
"code_actions",
|
|
3692
|
+
"reload"
|
|
4447
3693
|
]);
|
|
4448
3694
|
const SEVERITY_NAMES = {
|
|
4449
3695
|
1: "error",
|
|
@@ -4655,12 +3901,107 @@ function isEmptyPositionResult(result) {
|
|
|
4655
3901
|
}
|
|
4656
3902
|
return false;
|
|
4657
3903
|
}
|
|
3904
|
+
/** Apply a WorkspaceEdit file entry through the in-memory applier; returns the changed path. */
|
|
3905
|
+
async function applyFileTextEdits(uri, edits) {
|
|
3906
|
+
const filePath = uriToFile(uri);
|
|
3907
|
+
const updated = applyTextEdits(await fsPromises.readFile(filePath, "utf-8"), edits);
|
|
3908
|
+
await fsPromises.writeFile(filePath, updated, "utf-8");
|
|
3909
|
+
return filePath;
|
|
3910
|
+
}
|
|
3911
|
+
/** Apply or preview a WorkspaceEdit; returns per-file summaries. */
|
|
3912
|
+
async function applyOrPreviewWorkspaceEdit(edit, apply$2) {
|
|
3913
|
+
const perFile = [];
|
|
3914
|
+
for (const [uri, edits] of Object.entries(edit.changes ?? {})) perFile.push({
|
|
3915
|
+
uri,
|
|
3916
|
+
edits
|
|
3917
|
+
});
|
|
3918
|
+
for (const doc of edit.documentChanges ?? []) perFile.push({
|
|
3919
|
+
uri: doc.textDocument.uri,
|
|
3920
|
+
edits: doc.edits
|
|
3921
|
+
});
|
|
3922
|
+
if (perFile.length === 0) return [];
|
|
3923
|
+
if (!apply$2) return perFile.map(({ uri, edits }) => ` ${uriToFile(uri)}: ${edits.length} edit(s) (preview — pass "apply":true to apply)`);
|
|
3924
|
+
const out = [];
|
|
3925
|
+
for (const { uri, edits } of perFile) out.push(` ${await applyFileTextEdits(uri, edits)} (${edits.length} edit(s))`);
|
|
3926
|
+
return out;
|
|
3927
|
+
}
|
|
3928
|
+
const BARE_IDENTIFIER_RE = /^[$A-Za-z_][\w$]*$/;
|
|
3929
|
+
const IDENTIFIER_CHAR_RE = /[A-Za-z0-9_$]/;
|
|
3930
|
+
/** `symbol` / `symbol#N` — N-th 1-based occurrence of the identifier on the line. */
|
|
3931
|
+
function parseSymbolSpec(spec) {
|
|
3932
|
+
const match = spec.match(/^(.+)#(\d+)$/);
|
|
3933
|
+
if (match === null) return {
|
|
3934
|
+
symbol: spec,
|
|
3935
|
+
occurrence: 1
|
|
3936
|
+
};
|
|
3937
|
+
return {
|
|
3938
|
+
symbol: match[1] ?? spec,
|
|
3939
|
+
occurrence: Math.max(1, Number.parseInt(match[2] ?? "1", 10))
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
/** Match indexes for the symbol on the line: exact (word-bounded for bare identifiers), then case-insensitive. */
|
|
3943
|
+
function findSymbolMatchIndexes(lineText, symbol, caseInsensitive = false) {
|
|
3944
|
+
if (symbol.length === 0) return [];
|
|
3945
|
+
const haystack = caseInsensitive ? lineText.toLowerCase() : lineText;
|
|
3946
|
+
const needle = caseInsensitive ? symbol.toLowerCase() : symbol;
|
|
3947
|
+
const requireWordBoundary = BARE_IDENTIFIER_RE.test(symbol);
|
|
3948
|
+
const indexes = [];
|
|
3949
|
+
let fromIndex = 0;
|
|
3950
|
+
for (;;) {
|
|
3951
|
+
const matchIndex = haystack.indexOf(needle, fromIndex);
|
|
3952
|
+
if (matchIndex === -1) break;
|
|
3953
|
+
if (requireWordBoundary) {
|
|
3954
|
+
const before = matchIndex > 0 ? haystack[matchIndex - 1] ?? "" : "";
|
|
3955
|
+
const afterIdx = matchIndex + needle.length;
|
|
3956
|
+
const after = afterIdx < haystack.length ? haystack[afterIdx] ?? "" : "";
|
|
3957
|
+
if (IDENTIFIER_CHAR_RE.test(before) || IDENTIFIER_CHAR_RE.test(after)) {
|
|
3958
|
+
fromIndex = matchIndex + 1;
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
indexes.push(matchIndex);
|
|
3963
|
+
fromIndex = matchIndex + needle.length;
|
|
3964
|
+
}
|
|
3965
|
+
return indexes;
|
|
3966
|
+
}
|
|
3967
|
+
/**
|
|
3968
|
+
* Resolve the query column on `line` (1-based): no `symbol` → first
|
|
3969
|
+
* non-whitespace column; `symbol` → the occurrence's 0-based column,
|
|
3970
|
+
* exact case-sensitive first, case-insensitive fallback. Mirrors upstream
|
|
3971
|
+
* resolveSymbolColumn (OMP utils.ts).
|
|
3972
|
+
*/
|
|
3973
|
+
function resolveSymbolColumn(lines, line, symbolSpec) {
|
|
3974
|
+
const targetLine = lines[line - 1] ?? "";
|
|
3975
|
+
if (symbolSpec === void 0 || symbolSpec === "") {
|
|
3976
|
+
const match = targetLine.match(/\S/);
|
|
3977
|
+
return match && match.index !== void 0 ? match.index : 0;
|
|
3978
|
+
}
|
|
3979
|
+
const { symbol, occurrence } = parseSymbolSpec(symbolSpec);
|
|
3980
|
+
const exact = findSymbolMatchIndexes(targetLine, symbol);
|
|
3981
|
+
const fallback = exact.length > 0 ? exact : findSymbolMatchIndexes(targetLine, symbol, true);
|
|
3982
|
+
if (fallback.length === 0) throw new UrlSchemesError("LSP_SYMBOL_NOT_FOUND", `lsp device: symbol "${symbol}" not found on line ${line}`);
|
|
3983
|
+
if (occurrence > fallback.length) throw new UrlSchemesError("LSP_SYMBOL_NOT_FOUND", `lsp device: symbol "${symbol}" occurrence ${occurrence} out of bounds on line ${line} (found ${fallback.length})`);
|
|
3984
|
+
return fallback[occurrence - 1] ?? 0;
|
|
3985
|
+
}
|
|
4658
3986
|
/** The `dvc://lsp` device: dispatch on `action`, structured errors on every bad path. */
|
|
4659
3987
|
const lspDevice = {
|
|
4660
|
-
async execute(args) {
|
|
3988
|
+
async execute(args, ctx) {
|
|
4661
3989
|
const record = requireObjectArgs(args);
|
|
4662
3990
|
const action = record.action;
|
|
4663
|
-
|
|
3991
|
+
const sessionId = ctx?.session;
|
|
3992
|
+
if (typeof action !== "string" || !ACTIONS.has(action)) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: unknown action ${JSON.stringify(action ?? null)} — expected "diagnostics", "definition", "references", "hover", "format", "rename", "code_actions", "reload", "on", "off", or "status"`);
|
|
3993
|
+
if (action === "on" || action === "off" || action === "status") {
|
|
3994
|
+
if (sessionId === void 0) throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: gate actions require a session context (the write dispatcher injects it automatically)");
|
|
3995
|
+
if (action === "status") return {
|
|
3996
|
+
ok: true,
|
|
3997
|
+
gate: lspGateState(sessionId)
|
|
3998
|
+
};
|
|
3999
|
+
return {
|
|
4000
|
+
ok: true,
|
|
4001
|
+
gate: action,
|
|
4002
|
+
message: lspGateDecide(sessionId, action === "on")
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4664
4005
|
const file = record.file;
|
|
4665
4006
|
if (typeof file !== "string" || file === "") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: this action requires a non-empty \"file\" string");
|
|
4666
4007
|
const content = record.content;
|
|
@@ -4671,6 +4012,8 @@ const lspDevice = {
|
|
|
4671
4012
|
const filePath = path.resolve(file);
|
|
4672
4013
|
if (content === void 0 && !existsSync(filePath)) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: file not found: ${filePath}`);
|
|
4673
4014
|
const position = requirePosition(record);
|
|
4015
|
+
let symbolCharacter;
|
|
4016
|
+
if (typeof record.symbol === "string" && record.symbol !== "" && position.character === 1 && existsSync(filePath)) symbolCharacter = resolveSymbolColumn((await fsPromises.readFile(filePath, "utf-8")).split("\n"), position.line, record.symbol);
|
|
4674
4017
|
const { name: name$2, client } = await obtainClient(record, filePath);
|
|
4675
4018
|
const base = {
|
|
4676
4019
|
ok: true,
|
|
@@ -4681,13 +4024,134 @@ const lspDevice = {
|
|
|
4681
4024
|
const uri = fileToUri(filePath);
|
|
4682
4025
|
const wirePosition = {
|
|
4683
4026
|
line: position.line - 1,
|
|
4684
|
-
character: position.character - 1
|
|
4027
|
+
character: symbolCharacter ?? position.character - 1
|
|
4685
4028
|
};
|
|
4686
4029
|
const diagnosticsBaseline = client.diagnosticsVersion;
|
|
4687
4030
|
if (content === void 0) await ensureFileOpen(client, filePath);
|
|
4688
4031
|
else await syncFileContent(client, filePath, content);
|
|
4689
4032
|
if (action !== "diagnostics" && client.config.isLinter !== true) await waitForProjectLoaded(client);
|
|
4690
4033
|
const retryCold = action !== "diagnostics" && client.config.isLinter !== true;
|
|
4034
|
+
if (action === "reload") {
|
|
4035
|
+
const { name: name$3, client: client$1 } = await obtainClient(record, file);
|
|
4036
|
+
await shutdownClientInstance(client$1);
|
|
4037
|
+
return `${name$3}: reloaded — the next lsp call respawns the server fresh (config re-read from disk).`;
|
|
4038
|
+
}
|
|
4039
|
+
if (action === "diagnostics" && record.all === true) {
|
|
4040
|
+
const candidates = serversForFile(file);
|
|
4041
|
+
if (candidates.length <= 1) {} else {
|
|
4042
|
+
const results = [];
|
|
4043
|
+
for (const [srvName] of candidates) try {
|
|
4044
|
+
const outcome = await thisDispatch({
|
|
4045
|
+
...record,
|
|
4046
|
+
all: void 0,
|
|
4047
|
+
server: srvName
|
|
4048
|
+
});
|
|
4049
|
+
results.push({
|
|
4050
|
+
server: srvName,
|
|
4051
|
+
diagnostics: outcome.diagnostics ?? [],
|
|
4052
|
+
summary: outcome.summary ?? ""
|
|
4053
|
+
});
|
|
4054
|
+
} catch (error) {
|
|
4055
|
+
results.push({
|
|
4056
|
+
server: srvName,
|
|
4057
|
+
diagnostics: [],
|
|
4058
|
+
summary: error instanceof Error ? error.message : String(error)
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
const total = results.reduce((n, r) => n + r.diagnostics.length, 0);
|
|
4062
|
+
return {
|
|
4063
|
+
ok: true,
|
|
4064
|
+
fanout: true,
|
|
4065
|
+
servers: results.map((r) => r.server),
|
|
4066
|
+
diagnostics: results.flatMap((r) => r.diagnostics),
|
|
4067
|
+
summary: `fan-out ${file}: ${total} diagnostic(s) across ${results.length} server(s) — ${results.map((r) => `${r.server}: ${r.diagnostics.length}`).join(", ")}`
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
if (action === "diagnostics" && file === "*") {
|
|
4072
|
+
const servers = listLiveClients().filter((c) => c.status === "ready");
|
|
4073
|
+
const files = [];
|
|
4074
|
+
for (const c of servers) for (const [uri$1, published] of c.diagnostics) {
|
|
4075
|
+
const diags = published.diagnostics ?? [];
|
|
4076
|
+
files.push({
|
|
4077
|
+
file: uriToFile(uri$1),
|
|
4078
|
+
server: c.config.command,
|
|
4079
|
+
count: diags.length,
|
|
4080
|
+
errors: diags.filter((d) => (d.severity ?? 1) === 1).length,
|
|
4081
|
+
warnings: diags.filter((d) => (d.severity ?? 1) === 2).length
|
|
4082
|
+
});
|
|
4083
|
+
}
|
|
4084
|
+
files.sort((a, b) => b.count - a.count);
|
|
4085
|
+
const totalErrors = files.reduce((n, f) => n + f.errors, 0);
|
|
4086
|
+
const totalWarnings = files.reduce((n, f) => n + f.warnings, 0);
|
|
4087
|
+
const lines = files.slice(0, 50).map((f) => ` ${f.file} [${f.server}]: ${f.count} (${f.errors}E/${f.warnings}W)`);
|
|
4088
|
+
return {
|
|
4089
|
+
ok: true,
|
|
4090
|
+
scope: "workspace",
|
|
4091
|
+
servers: servers.length,
|
|
4092
|
+
files: files.length,
|
|
4093
|
+
errors: totalErrors,
|
|
4094
|
+
warnings: totalWarnings,
|
|
4095
|
+
summary: `workspace diagnostics: ${files.length} file(s) with findings across ${servers.length} live server(s) — ${totalErrors} error(s), ${totalWarnings} warning(s)`,
|
|
4096
|
+
detail: lines.length > 0 ? lines.join("\n") : "no cached diagnostics"
|
|
4097
|
+
};
|
|
4098
|
+
}
|
|
4099
|
+
if (action === "rename") {
|
|
4100
|
+
const newName = record.new_name;
|
|
4101
|
+
if (typeof newName !== "string" || newName === "") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"rename\" requires a non-empty \"new_name\" string");
|
|
4102
|
+
const { name: name$3, client: client$1 } = await obtainClient(record, file);
|
|
4103
|
+
const { line } = requirePosition(record);
|
|
4104
|
+
const character = resolveSymbolColumn((await fsPromises.readFile(file, "utf-8")).split("\n"), line, typeof record.symbol === "string" ? record.symbol : void 0);
|
|
4105
|
+
await ensureFileOpen(client$1, file);
|
|
4106
|
+
const result = await lspPositionRequest(client$1, "textDocument/rename", {
|
|
4107
|
+
textDocument: { uri: fileToUri(file) },
|
|
4108
|
+
position: {
|
|
4109
|
+
line: line - 1,
|
|
4110
|
+
character
|
|
4111
|
+
},
|
|
4112
|
+
newName
|
|
4113
|
+
}, false);
|
|
4114
|
+
if (result === null || result.changes === void 0 && result.documentChanges === void 0) return `${name$3}: rename returned no edits`;
|
|
4115
|
+
const apply$2 = record.apply !== false;
|
|
4116
|
+
const lines = await applyOrPreviewWorkspaceEdit(result, apply$2);
|
|
4117
|
+
return `${name$3}: rename ${apply$2 ? "applied" : "preview"}:\n${lines.join("\n")}`;
|
|
4118
|
+
}
|
|
4119
|
+
if (action === "code_actions") {
|
|
4120
|
+
const { name: name$3, client: client$1 } = await obtainClient(record, file);
|
|
4121
|
+
const { line, character } = requirePosition(record);
|
|
4122
|
+
await ensureFileOpen(client$1, file);
|
|
4123
|
+
const actions = await lspPositionRequest(client$1, "textDocument/codeAction", {
|
|
4124
|
+
textDocument: { uri: fileToUri(file) },
|
|
4125
|
+
range: {
|
|
4126
|
+
start: {
|
|
4127
|
+
line: line - 1,
|
|
4128
|
+
character: character - 1
|
|
4129
|
+
},
|
|
4130
|
+
end: {
|
|
4131
|
+
line: line - 1,
|
|
4132
|
+
character: character - 1
|
|
4133
|
+
}
|
|
4134
|
+
},
|
|
4135
|
+
context: {
|
|
4136
|
+
diagnostics: client$1.diagnostics.get(fileToUri(file))?.diagnostics ?? [],
|
|
4137
|
+
triggerKind: 1
|
|
4138
|
+
}
|
|
4139
|
+
}, false) ?? [];
|
|
4140
|
+
if (actions.length === 0) return `${name$3}: no code actions available`;
|
|
4141
|
+
const applyIndex = typeof record.apply === "number" ? record.apply : void 0;
|
|
4142
|
+
if (applyIndex === void 0) return `${name$3}: code actions (pass "apply":<index> to apply):\n` + actions.map((a, i) => ` [${i}] ${a.title ?? "(untitled)"}${a.kind ? ` (${a.kind})` : ""}`).join("\n");
|
|
4143
|
+
const chosen = actions[applyIndex];
|
|
4144
|
+
if (chosen === void 0) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: "apply" index ${applyIndex} out of range (0..${actions.length - 1})`);
|
|
4145
|
+
if (chosen.edit !== void 0) {
|
|
4146
|
+
const lines = await applyOrPreviewWorkspaceEdit(chosen.edit, true);
|
|
4147
|
+
return `${name$3}: applied code action "${chosen.title ?? applyIndex}":\n${lines.join("\n")}`;
|
|
4148
|
+
}
|
|
4149
|
+
if (chosen.command?.command !== void 0) {
|
|
4150
|
+
await sendRequest(client$1, "workspace/executeCommand", chosen.command);
|
|
4151
|
+
return `${name$3}: executed command "${chosen.command.command}" for "${chosen.title ?? applyIndex}"`;
|
|
4152
|
+
}
|
|
4153
|
+
return `${name$3}: code action "${chosen.title ?? applyIndex}" carries neither edit nor command`;
|
|
4154
|
+
}
|
|
4691
4155
|
switch (action) {
|
|
4692
4156
|
case "diagnostics": {
|
|
4693
4157
|
if (saved !== true) {
|
|
@@ -4708,12 +4172,25 @@ const lspDevice = {
|
|
|
4708
4172
|
timeoutMs: DIAGNOSTICS_FEEDBACK_WAIT_MS
|
|
4709
4173
|
});
|
|
4710
4174
|
const kept = waited.diagnostics.filter((diagnostic) => !(waited.timedOut && diagnostic.source === "rustc")).map(diagnosticRecord);
|
|
4711
|
-
|
|
4175
|
+
const out = {
|
|
4712
4176
|
...base,
|
|
4713
4177
|
diagnostics: kept,
|
|
4714
4178
|
summary: diagnosticsSummary(kept),
|
|
4715
4179
|
check: waited.timedOut ? "timeout-dropped-rustc" : "completed"
|
|
4716
4180
|
};
|
|
4181
|
+
if (client.serverCapabilities !== void 0 && "diagnosticProvider" in client.serverCapabilities) try {
|
|
4182
|
+
const pulled = await sendRequest(client, "textDocument/diagnostic", { textDocument: { uri } });
|
|
4183
|
+
if (pulled !== null && Array.isArray(pulled.items)) {
|
|
4184
|
+
const items = pulled.items.map((d) => diagnosticRecord(d));
|
|
4185
|
+
return {
|
|
4186
|
+
...out,
|
|
4187
|
+
diagnostics: items,
|
|
4188
|
+
summary: diagnosticsSummary(items),
|
|
4189
|
+
pull: true
|
|
4190
|
+
};
|
|
4191
|
+
}
|
|
4192
|
+
} catch {}
|
|
4193
|
+
return out;
|
|
4717
4194
|
}
|
|
4718
4195
|
case "definition": {
|
|
4719
4196
|
const result = await lspPositionRequest(client, "textDocument/definition", {
|
|
@@ -4780,13 +4257,59 @@ const lspDevice = {
|
|
|
4780
4257
|
default: throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: unhandled action "${String(action)}"`);
|
|
4781
4258
|
}
|
|
4782
4259
|
},
|
|
4783
|
-
summary: "LSP queries over stdio language servers (defaults.json registry) — diagnostics / definition / references / hover on {file,line,character}"
|
|
4260
|
+
summary: "LSP queries over stdio language servers (defaults.json registry) — diagnostics / definition / references / hover on {file,line,character}",
|
|
4261
|
+
read: lspDeviceRead
|
|
4784
4262
|
};
|
|
4263
|
+
/** One-level recursion for fan-out: re-dispatch with a specific `server`. */
|
|
4264
|
+
const thisDispatch = (args) => lspDevice.execute(args);
|
|
4265
|
+
const READ_ACTIONS = new Set([
|
|
4266
|
+
"status",
|
|
4267
|
+
"diagnostics",
|
|
4268
|
+
"definition",
|
|
4269
|
+
"references",
|
|
4270
|
+
"hover"
|
|
4271
|
+
]);
|
|
4272
|
+
function parseReadQuery(subpath) {
|
|
4273
|
+
const qIndex = subpath.indexOf("?");
|
|
4274
|
+
const action = qIndex === -1 ? subpath : subpath.slice(0, qIndex);
|
|
4275
|
+
if (!READ_ACTIONS.has(action)) return void 0;
|
|
4276
|
+
const args = { action };
|
|
4277
|
+
if (qIndex !== -1) for (const [k, v] of new URLSearchParams(subpath.slice(qIndex + 1))) args[k] = k === "all" || k === "apply" ? v === "1" || v === "true" : v;
|
|
4278
|
+
return {
|
|
4279
|
+
action,
|
|
4280
|
+
args
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
async function lspDeviceRead(subpath, session) {
|
|
4284
|
+
const parsed = parseReadQuery(subpath);
|
|
4285
|
+
if (parsed === void 0) return `unknown read path dvc://lsp/${subpath} — reads: status | diagnostics?file=…[&all=1] | definition?file=&line=[&character=|&symbol=] | references?… | hover?…`;
|
|
4286
|
+
if (parsed.action === "status") {
|
|
4287
|
+
if (session !== void 0) return JSON.stringify({
|
|
4288
|
+
ok: true,
|
|
4289
|
+
gate: lspGateState(session)
|
|
4290
|
+
});
|
|
4291
|
+
return JSON.stringify({
|
|
4292
|
+
ok: true,
|
|
4293
|
+
note: "per-session gate — read from a session for its gate (session missing in read env)"
|
|
4294
|
+
});
|
|
4295
|
+
}
|
|
4296
|
+
const result = await lspDevice.execute(parsed.args);
|
|
4297
|
+
return JSON.stringify(result, null, 2);
|
|
4298
|
+
}
|
|
4785
4299
|
/**
|
|
4786
4300
|
* Mount the lsp device. `registry` defaults to the dvc:// module-level registry
|
|
4787
4301
|
* (`registerDvcDevice`); tests may pass their own recorder instead.
|
|
4788
4302
|
*/
|
|
4789
4303
|
function installLspDevices(registry = registerDvcDevice) {
|
|
4304
|
+
registerLspGateTransport(async ({ kind, filePath, content }) => {
|
|
4305
|
+
const { client } = await obtainClient({}, filePath);
|
|
4306
|
+
if (kind === "read") {
|
|
4307
|
+
await ensureFileOpen(client, filePath);
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
await syncFileContent(client, filePath, typeof content === "string" && content !== "" ? content : await import("node:fs/promises").then((fs) => fs.readFile(filePath, "utf-8")));
|
|
4311
|
+
await notifyDidSave(client, filePath);
|
|
4312
|
+
});
|
|
4790
4313
|
registry("lsp", lspDevice);
|
|
4791
4314
|
}
|
|
4792
4315
|
|
|
@@ -5364,8 +4887,8 @@ function loadSectionText() {
|
|
|
5364
4887
|
}
|
|
5365
4888
|
const SECTION_TEXT = loadSectionText();
|
|
5366
4889
|
/** Render the section when the URL capability is on; `undefined` gates it off. */
|
|
5367
|
-
function generalSection(gates) {
|
|
5368
|
-
if (!gates.urlSchemes) return void 0;
|
|
4890
|
+
function generalSection(gates$1) {
|
|
4891
|
+
if (!gates$1.urlSchemes) return void 0;
|
|
5369
4892
|
return {
|
|
5370
4893
|
name: GENERAL_SECTION_NAME,
|
|
5371
4894
|
order: GENERAL_SECTION_ORDER,
|
|
@@ -5418,9 +4941,24 @@ function lookupOptions(env) {
|
|
|
5418
4941
|
* maps a URL to its on-disk location so `grep`/`glob` can search the real
|
|
5419
4942
|
* files instead of resolved text.
|
|
5420
4943
|
*/
|
|
4944
|
+
/**
|
|
4945
|
+
* Render the cwd-scoped skill catalog: one line per winning summary
|
|
4946
|
+
* (`name — description`, plus `whenToUse` when present). Empty registry →
|
|
4947
|
+
* an explicit empty answer, never an error.
|
|
4948
|
+
*/
|
|
4949
|
+
async function renderSkillList(skills, env) {
|
|
4950
|
+
const all = await skills.list(lookupOptions(env));
|
|
4951
|
+
if (all.length === 0) return "No skills available in this workspace scope.";
|
|
4952
|
+
const lines = all.map((skill) => {
|
|
4953
|
+
const when = skill.whenToUse !== void 0 && skill.whenToUse !== "" ? ` (use when: ${skill.whenToUse})` : "";
|
|
4954
|
+
return `skill://${skill.name} — ${skill.description}${when}`;
|
|
4955
|
+
});
|
|
4956
|
+
return `${all.length} available skill(s):\n${lines.join("\n")}`;
|
|
4957
|
+
}
|
|
5421
4958
|
function createSkillHandler(deps) {
|
|
5422
4959
|
return {
|
|
5423
4960
|
async resolve(env, path$1) {
|
|
4961
|
+
if (path$1 === "" || path$1 === "/") return await renderSkillList(deps.skills, env);
|
|
5424
4962
|
const { name: name$2, subpath } = splitSkillPath(path$1);
|
|
5425
4963
|
const skill = await deps.skills.get(name$2, lookupOptions(env));
|
|
5426
4964
|
if (skill === void 0) throw new UrlSchemesError("URL_SKILL_NOT_FOUND", `skill "${name$2}" is unknown or no longer available`);
|
|
@@ -5574,6 +5112,26 @@ function isSchemeUrl$2(raw) {
|
|
|
5574
5112
|
throw error;
|
|
5575
5113
|
}
|
|
5576
5114
|
}
|
|
5115
|
+
/**
|
|
5116
|
+
* Split a URL-with-glob pattern into `{ urlPart, globTail }`: the first glob
|
|
5117
|
+
* metacharacter (`*?[`) in the path portion starts the glob tail, which the
|
|
5118
|
+
* path-backed branch applies WITHIN the resource's real disk directory
|
|
5119
|
+
* (`skill://grp/*` → root = the skill dir, pattern `*`). A URL with no
|
|
5120
|
+
* metacharacters keeps the bare-URL listing behavior (tail empty → the
|
|
5121
|
+
* all-depth default pattern).
|
|
5122
|
+
*/
|
|
5123
|
+
function splitUrlGlob(pattern) {
|
|
5124
|
+
const metachar = pattern.search(/[*?\[]/);
|
|
5125
|
+
if (metachar === -1) return {
|
|
5126
|
+
urlPart: pattern,
|
|
5127
|
+
globTail: ""
|
|
5128
|
+
};
|
|
5129
|
+
const tail = pattern.slice(metachar).replace(/^\/+/, "");
|
|
5130
|
+
return {
|
|
5131
|
+
urlPart: pattern.slice(0, metachar).replace(/\/+$/, ""),
|
|
5132
|
+
globTail: tail
|
|
5133
|
+
};
|
|
5134
|
+
}
|
|
5577
5135
|
/** Structured error for calls that cannot run without the native delegate. */
|
|
5578
5136
|
function nativeUnavailable$1() {
|
|
5579
5137
|
return new UrlSchemesError("NATIVE_GLOB_UNAVAILABLE", "the host did not deploy a native glob tool — URL-aware glob cannot delegate discovery");
|
|
@@ -5626,12 +5184,14 @@ function createGlobTool(deps) {
|
|
|
5626
5184
|
async execute(args, exec) {
|
|
5627
5185
|
if (isSchemeUrl$2(args.pattern)) {
|
|
5628
5186
|
const env = execEnv$1(exec, args.pattern);
|
|
5629
|
-
const
|
|
5187
|
+
const { urlPart, globTail } = splitUrlGlob(args.pattern);
|
|
5188
|
+
const diskDir = await resolver.resolvePath(env, urlPart);
|
|
5630
5189
|
if (diskDir !== void 0) {
|
|
5631
5190
|
if (nativeGlob === void 0) throw nativeUnavailable$1();
|
|
5191
|
+
const rootedPattern = globTail === "" ? "**/*" : globTail;
|
|
5632
5192
|
return nativeGlob.execute({
|
|
5633
5193
|
...args,
|
|
5634
|
-
pattern:
|
|
5194
|
+
pattern: rootedPattern,
|
|
5635
5195
|
path: diskDir
|
|
5636
5196
|
}, exec);
|
|
5637
5197
|
}
|
|
@@ -5752,10 +5312,25 @@ function createGrepTool(deps) {
|
|
|
5752
5312
|
if (args.path !== void 0 && isSchemeUrl$1(args.path)) {
|
|
5753
5313
|
const env = execEnv(exec, args.path);
|
|
5754
5314
|
const diskPath = await resolver.resolvePath(env, args.path);
|
|
5755
|
-
if (diskPath !== void 0)
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5315
|
+
if (diskPath !== void 0) {
|
|
5316
|
+
const result = await nativeGrep.execute({
|
|
5317
|
+
...args,
|
|
5318
|
+
path: diskPath
|
|
5319
|
+
}, exec);
|
|
5320
|
+
if (!Array.isArray(result.matches)) return result;
|
|
5321
|
+
const prefix = diskPath.endsWith("/") ? diskPath : `${diskPath}/`;
|
|
5322
|
+
return { matches: result.matches.map((m) => {
|
|
5323
|
+
if (m.path.startsWith(prefix)) return {
|
|
5324
|
+
...m,
|
|
5325
|
+
path: `${args.path}/${m.path.slice(prefix.length)}`
|
|
5326
|
+
};
|
|
5327
|
+
if (!m.path.startsWith("/")) return {
|
|
5328
|
+
...m,
|
|
5329
|
+
path: `${args.path}/${m.path}`
|
|
5330
|
+
};
|
|
5331
|
+
return m;
|
|
5332
|
+
}) };
|
|
5333
|
+
}
|
|
5759
5334
|
return withTempMaterialization(await resolver.resolve(env, args.path), (tempDir) => nativeGrep.execute({
|
|
5760
5335
|
...args,
|
|
5761
5336
|
path: join(tempDir, "content.txt")
|
|
@@ -5767,7 +5342,231 @@ function createGrepTool(deps) {
|
|
|
5767
5342
|
}
|
|
5768
5343
|
|
|
5769
5344
|
//#endregion
|
|
5770
|
-
//#region src/url-schemes/
|
|
5345
|
+
//#region src/url-schemes/tools/read.ts
|
|
5346
|
+
/** Mirrors `parseUrl`'s scheme prefix so the fork matches the resolver exactly. */
|
|
5347
|
+
/**
|
|
5348
|
+
* Shape args toward the delegate's declared parameters: move the file path
|
|
5349
|
+
* between the `path`/`file_path` aliases according to which key the
|
|
5350
|
+
* delegate's schema actually declares (host-native read = `file_path`,
|
|
5351
|
+
* hashline = `path`). Unknown-key tolerant delegates are unaffected.
|
|
5352
|
+
*/
|
|
5353
|
+
function shapeArgsForDelegate(delegate, args, rawPath) {
|
|
5354
|
+
const shaped = { ...args };
|
|
5355
|
+
if (rawPath === "") {
|
|
5356
|
+
delete shaped.path;
|
|
5357
|
+
delete shaped.file_path;
|
|
5358
|
+
return shaped;
|
|
5359
|
+
}
|
|
5360
|
+
const props = delegate.parameters?.properties;
|
|
5361
|
+
if (props === void 0) return shaped;
|
|
5362
|
+
const declaresPath = props["path"] !== void 0;
|
|
5363
|
+
const declaresFilePath = props["file_path"] !== void 0;
|
|
5364
|
+
if (declaresFilePath && !declaresPath) {
|
|
5365
|
+
shaped.file_path = rawPath;
|
|
5366
|
+
delete shaped.path;
|
|
5367
|
+
} else if (declaresPath && !declaresFilePath) {
|
|
5368
|
+
shaped.path = rawPath;
|
|
5369
|
+
delete shaped.file_path;
|
|
5370
|
+
} else {
|
|
5371
|
+
shaped.path = rawPath;
|
|
5372
|
+
shaped.file_path = rawPath;
|
|
5373
|
+
}
|
|
5374
|
+
return shaped;
|
|
5375
|
+
}
|
|
5376
|
+
const SCHEME_URL_RE = /^[a-z][a-z0-9]*:\/\//;
|
|
5377
|
+
/**
|
|
5378
|
+
* Build the scheme-aware `read` wrapper. The URL branch builds the resolver
|
|
5379
|
+
* env per call (agent + cwd + rawUrl); the scheme handlers read whichever
|
|
5380
|
+
* fields they need off it. File paths delegate to the terminal delegate so
|
|
5381
|
+
* the owning feature (hashline anchors or the native read) surfaces its own
|
|
5382
|
+
* result — never a silent reimplementation here.
|
|
5383
|
+
*/
|
|
5384
|
+
function createSchemeReadTool(deps) {
|
|
5385
|
+
const { resolver, capturedRead } = deps;
|
|
5386
|
+
return defineTool({
|
|
5387
|
+
name: "read",
|
|
5388
|
+
description: "Read a text file (each line returned as `HASH│content` with a 3-char hash anchor for later edit calls). File reads page with offset/limit. Also accepts resource URIs (skill://, agent://, dsh://, dvc://, ctx://, http(s)://).",
|
|
5389
|
+
parameters: {
|
|
5390
|
+
path: {
|
|
5391
|
+
type: "string",
|
|
5392
|
+
description: "File path, or a resource URI (skill://, agent://, dsh://, dvc://, ctx://, http(s)://)."
|
|
5393
|
+
},
|
|
5394
|
+
file_path: {
|
|
5395
|
+
type: "string",
|
|
5396
|
+
description: "Alias of `path` (accepted for host-native compatibility)."
|
|
5397
|
+
},
|
|
5398
|
+
offset: {
|
|
5399
|
+
type: "number",
|
|
5400
|
+
description: "Line number to start reading from (1-indexed, file reads only)"
|
|
5401
|
+
},
|
|
5402
|
+
limit: {
|
|
5403
|
+
type: "number",
|
|
5404
|
+
description: "Maximum number of lines to read (file reads only)"
|
|
5405
|
+
}
|
|
5406
|
+
},
|
|
5407
|
+
output: {
|
|
5408
|
+
schema: { type: "string" },
|
|
5409
|
+
render: (_args, value) => [{
|
|
5410
|
+
type: "text",
|
|
5411
|
+
text: value
|
|
5412
|
+
}]
|
|
5413
|
+
},
|
|
5414
|
+
async execute(args, exec) {
|
|
5415
|
+
const rawPath = typeof args?.path === "string" ? args.path : typeof args?.file_path === "string" ? args.file_path : "";
|
|
5416
|
+
if (resolver !== void 0 && SCHEME_URL_RE.test(rawPath)) {
|
|
5417
|
+
const cwd = exec.agent?.session.header.cwd;
|
|
5418
|
+
const env = cwd === void 0 ? {
|
|
5419
|
+
agent: exec.agent,
|
|
5420
|
+
rawUrl: rawPath
|
|
5421
|
+
} : {
|
|
5422
|
+
agent: exec.agent,
|
|
5423
|
+
cwd,
|
|
5424
|
+
rawUrl: rawPath
|
|
5425
|
+
};
|
|
5426
|
+
return resolver.resolve(env, rawPath);
|
|
5427
|
+
}
|
|
5428
|
+
if (capturedRead !== void 0 && capturedRead.execute !== void 0) {
|
|
5429
|
+
const delegated = await capturedRead.execute(shapeArgsForDelegate(capturedRead, args, rawPath), exec);
|
|
5430
|
+
if (typeof delegated === "string") return delegated;
|
|
5431
|
+
return delegated === void 0 || delegated === null ? "" : JSON.stringify(delegated);
|
|
5432
|
+
}
|
|
5433
|
+
throw new UrlSchemesError("NATIVE_READ_UNAVAILABLE", "no read delegate is available: neither hashline nor the native read is in place");
|
|
5434
|
+
}
|
|
5435
|
+
});
|
|
5436
|
+
}
|
|
5437
|
+
|
|
5438
|
+
//#endregion
|
|
5439
|
+
//#region src/url-schemes/tools/write.ts
|
|
5440
|
+
/**
|
|
5441
|
+
* Schemes the URL schema registers (v0.1.8d): the default write dispatch
|
|
5442
|
+
* keys off this table; the integration step can replace the whole
|
|
5443
|
+
* dispatch through `writeScheme` when it needs the live registry.
|
|
5444
|
+
*
|
|
5445
|
+
* Rendered from `catalog.ts` rather than written out here — this literal was
|
|
5446
|
+
* one of the hand-maintained copies that drifted from the scheme set.
|
|
5447
|
+
*/
|
|
5448
|
+
const REGISTERED_SCHEMES = SCHEME_NAMES;
|
|
5449
|
+
/** Detects a `scheme://` prefix with the resolver layer's own parser. */
|
|
5450
|
+
function isSchemeUrl(raw) {
|
|
5451
|
+
try {
|
|
5452
|
+
parseUrl(raw);
|
|
5453
|
+
return true;
|
|
5454
|
+
} catch (error) {
|
|
5455
|
+
if (error instanceof UrlSchemesError && error.code === "URL_NO_SCHEME") return false;
|
|
5456
|
+
throw error;
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
/**
|
|
5460
|
+
* Default scheme-write dispatch: `dvc://` routes through the device
|
|
5461
|
+
* registry (`dispatchDvcWrite`); every other write channel is rejected.
|
|
5462
|
+
*/
|
|
5463
|
+
async function defaultSchemeWrite(scheme, path$1, content, meta = {}) {
|
|
5464
|
+
if (scheme === "dvc") {
|
|
5465
|
+
const result = await dispatchDvcWrite(path$1, content, meta.session);
|
|
5466
|
+
return {
|
|
5467
|
+
path: `dvc://${path$1}`,
|
|
5468
|
+
operation: "execute",
|
|
5469
|
+
before: "",
|
|
5470
|
+
after: JSON.stringify(result, null, 2)
|
|
5471
|
+
};
|
|
5472
|
+
}
|
|
5473
|
+
if (scheme === "ctx") throw new UrlSchemesError("URL_READ_ONLY", `ctx:// is a curated read-only snapshot — write to ctx://${path$1} is not supported`);
|
|
5474
|
+
if (REGISTERED_SCHEMES.includes(scheme)) throw new UrlSchemesError("URL_WRITE_UNSUPPORTED", `write to ${scheme}:// is not supported (read-only scheme, or its write channel is not wired yet)`);
|
|
5475
|
+
throw new UrlSchemesError("URL_UNREGISTERED_SCHEME", `no handler registered for scheme "${scheme}" (registered: ${REGISTERED_SCHEMES.join(", ")})`);
|
|
5476
|
+
}
|
|
5477
|
+
/**
|
|
5478
|
+
* Build the `write` {@link ToolDefinition}: `scheme://` paths route to the
|
|
5479
|
+
* scheme write dispatch, ordinary paths delegate to the captured native
|
|
5480
|
+
* write definition with args and exec passed through untouched.
|
|
5481
|
+
*/
|
|
5482
|
+
function createWriteTool(deps) {
|
|
5483
|
+
const { nativeWrite, postWrite, preWriteFormat, sandbox } = deps;
|
|
5484
|
+
const writeScheme = deps.writeScheme ?? defaultSchemeWrite;
|
|
5485
|
+
return defineTool({
|
|
5486
|
+
name: "write",
|
|
5487
|
+
description: "Create or fully replace a UTF-8 text file.",
|
|
5488
|
+
parameters: {
|
|
5489
|
+
file_path: {
|
|
5490
|
+
type: "string",
|
|
5491
|
+
required: true,
|
|
5492
|
+
description: "Path to write, resolved by the filesystem backend."
|
|
5493
|
+
},
|
|
5494
|
+
content: {
|
|
5495
|
+
type: "string",
|
|
5496
|
+
required: true,
|
|
5497
|
+
description: "Full UTF-8 text content to write."
|
|
5498
|
+
},
|
|
5499
|
+
...sandbox !== void 0 && sandbox.escalationModes.length > 0 ? sandbox.schemaFields() : {}
|
|
5500
|
+
},
|
|
5501
|
+
output: {
|
|
5502
|
+
schema: {
|
|
5503
|
+
type: "object",
|
|
5504
|
+
additionalProperties: false,
|
|
5505
|
+
properties: {
|
|
5506
|
+
path: {
|
|
5507
|
+
type: "string",
|
|
5508
|
+
required: true
|
|
5509
|
+
},
|
|
5510
|
+
operation: {
|
|
5511
|
+
type: "string",
|
|
5512
|
+
required: true,
|
|
5513
|
+
enum: [
|
|
5514
|
+
"create",
|
|
5515
|
+
"update",
|
|
5516
|
+
"execute"
|
|
5517
|
+
]
|
|
5518
|
+
},
|
|
5519
|
+
before: {
|
|
5520
|
+
required: true,
|
|
5521
|
+
oneOf: [{ type: "string" }, { type: "null" }]
|
|
5522
|
+
},
|
|
5523
|
+
after: {
|
|
5524
|
+
type: "string",
|
|
5525
|
+
required: true
|
|
5526
|
+
},
|
|
5527
|
+
diagnostics: { type: "string" }
|
|
5528
|
+
}
|
|
5529
|
+
},
|
|
5530
|
+
render: (_args, value) => {
|
|
5531
|
+
const verb = value.operation === "update" ? "Updated" : value.operation === "execute" ? "Executed" : "Created";
|
|
5532
|
+
let text = value.diagnostics === void 0 ? `${verb} ${value.path}` : `${verb} ${value.path}\n${value.diagnostics}`;
|
|
5533
|
+
if (value.operation === "execute" && value.after !== void 0) text += `\n${value.after}`;
|
|
5534
|
+
return [{
|
|
5535
|
+
type: "text",
|
|
5536
|
+
text
|
|
5537
|
+
}];
|
|
5538
|
+
}
|
|
5539
|
+
},
|
|
5540
|
+
async execute(args, exec) {
|
|
5541
|
+
if (isSchemeUrl(args.file_path)) {
|
|
5542
|
+
const parsed = parseUrl(args.file_path);
|
|
5543
|
+
return writeScheme(parsed.scheme, parsed.path, args.content, { session: exec.agent?.id });
|
|
5544
|
+
}
|
|
5545
|
+
if (nativeWrite === void 0) throw new UrlSchemesError("NATIVE_WRITE_UNAVAILABLE", "the host did not deploy a native write tool — URL-aware write cannot delegate filesystem writes");
|
|
5546
|
+
let content = args.content;
|
|
5547
|
+
try {
|
|
5548
|
+
const formatted = await preWriteFormat?.(args.file_path, content);
|
|
5549
|
+
if (formatted !== void 0 && formatted !== content) content = formatted;
|
|
5550
|
+
} catch {}
|
|
5551
|
+
const writeArgs = content === args.content ? args : {
|
|
5552
|
+
...args,
|
|
5553
|
+
content
|
|
5554
|
+
};
|
|
5555
|
+
const outcome = await nativeWrite.execute(writeArgs, exec);
|
|
5556
|
+
try {
|
|
5557
|
+
const diagnostics = await postWrite?.(args.file_path, content);
|
|
5558
|
+
if (diagnostics !== void 0) return {
|
|
5559
|
+
...outcome,
|
|
5560
|
+
diagnostics
|
|
5561
|
+
};
|
|
5562
|
+
} catch {}
|
|
5563
|
+
return outcome;
|
|
5564
|
+
}
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
|
|
5568
|
+
//#endregion
|
|
5569
|
+
//#region src/hashline/constants.js
|
|
5771
5570
|
const MAX_BYTES = 100 * 1024 * 1024;
|
|
5772
5571
|
const MAX_READ_LINE_BYTES = 200 * 1024;
|
|
5773
5572
|
const HASH_STORE_BUSY_TIMEOUT = 1e3;
|
|
@@ -5779,7 +5578,7 @@ const NOOP_LOOP_THRESHOLD = 3;
|
|
|
5779
5578
|
const NEW_CONTENT_NOT_STRING_MSG = "[E_BAD_SHAPE] \"replacement_text\" must be a string with \\n line separators, not an array. Do not pass an array of lines — pass the replacement text as one string: \"line1\\nline2\". Use \"\" to delete a range.";
|
|
5780
5579
|
|
|
5781
5580
|
//#endregion
|
|
5782
|
-
//#region src/
|
|
5581
|
+
//#region src/hashline/utils.js
|
|
5783
5582
|
function isRec(value) {
|
|
5784
5583
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5785
5584
|
}
|
|
@@ -5832,7 +5631,7 @@ function clipLine(line, maxLen = 200) {
|
|
|
5832
5631
|
}
|
|
5833
5632
|
|
|
5834
5633
|
//#endregion
|
|
5835
|
-
//#region src/
|
|
5634
|
+
//#region src/hashline/contract.js
|
|
5836
5635
|
const normalizedEdit = Symbol("normalizedEdit");
|
|
5837
5636
|
function isNormalizedEdit(input) {
|
|
5838
5637
|
return isRec(input) && input[normalizedEdit] === true;
|
|
@@ -5923,7 +5722,7 @@ function assertUndoRequest(request) {
|
|
|
5923
5722
|
}
|
|
5924
5723
|
|
|
5925
5724
|
//#endregion
|
|
5926
|
-
//#region src/
|
|
5725
|
+
//#region src/hashline/paths.js
|
|
5927
5726
|
/**
|
|
5928
5727
|
* On-disk home for dsh-better-edit state: ONE centralized store per harness
|
|
5929
5728
|
* home — `$DSH_HOME/storages/dsh-better-edit` (default
|
|
@@ -5940,11 +5739,11 @@ function hashStorePath() {
|
|
|
5940
5739
|
}
|
|
5941
5740
|
|
|
5942
5741
|
//#endregion
|
|
5943
|
-
//#region src/
|
|
5742
|
+
//#region src/hashline/fs-write.js
|
|
5944
5743
|
const STALE_TEMP_MS = 3600 * 1e3;
|
|
5945
5744
|
|
|
5946
5745
|
//#endregion
|
|
5947
|
-
//#region src/
|
|
5746
|
+
//#region src/hashline/hashline/hash-assign.js
|
|
5948
5747
|
let hasher = null;
|
|
5949
5748
|
function getH() {
|
|
5950
5749
|
if (hasher) return hasher;
|
|
@@ -6158,7 +5957,7 @@ function mapStableHashes(oldContent, oldHashes, newContent, removedHashes) {
|
|
|
6158
5957
|
}
|
|
6159
5958
|
|
|
6160
5959
|
//#endregion
|
|
6161
|
-
//#region src/
|
|
5960
|
+
//#region src/hashline/hash-store.js
|
|
6162
5961
|
function isValidHashList(value) {
|
|
6163
5962
|
if (!Array.isArray(value)) return false;
|
|
6164
5963
|
for (const hash of value) if (typeof hash !== "string" || !HASH_RE.test(hash)) return false;
|
|
@@ -6648,7 +6447,7 @@ async function upsertSnapshotFor(path$1, checksum, lineCount, hashes) {
|
|
|
6648
6447
|
}
|
|
6649
6448
|
|
|
6650
6449
|
//#endregion
|
|
6651
|
-
//#region src/
|
|
6450
|
+
//#region src/hashline/hashline/hash.js
|
|
6652
6451
|
let defaultHashSnapshotIO;
|
|
6653
6452
|
function snapshotIOFor(store) {
|
|
6654
6453
|
if (store) return {
|
|
@@ -6697,7 +6496,7 @@ async function lineHashes(content, path$1, previous, ioOrStore, persist) {
|
|
|
6697
6496
|
}
|
|
6698
6497
|
|
|
6699
6498
|
//#endregion
|
|
6700
|
-
//#region src/
|
|
6499
|
+
//#region src/hashline/session-view.js
|
|
6701
6500
|
const current = new AsyncLocalStorage();
|
|
6702
6501
|
function withWorkspace(cwd, fn) {
|
|
6703
6502
|
return current.run(cwd, fn);
|
|
@@ -6918,7 +6717,7 @@ async function scanDrift(input) {
|
|
|
6918
6717
|
}
|
|
6919
6718
|
|
|
6920
6719
|
//#endregion
|
|
6921
|
-
//#region src/
|
|
6720
|
+
//#region src/hashline/hashline/anchor-pipeline.js
|
|
6922
6721
|
function diagRef(ref) {
|
|
6923
6722
|
const trimmed = ref.trim();
|
|
6924
6723
|
if (!trimmed.length) return `[E_BAD_REF] Invalid anchor. Expected a 3-char alphanumeric anchor (e.g. "aB3").`;
|
|
@@ -7561,7 +7360,7 @@ function changedRange(original, result) {
|
|
|
7561
7360
|
}
|
|
7562
7361
|
|
|
7563
7362
|
//#endregion
|
|
7564
|
-
//#region src/
|
|
7363
|
+
//#region src/hashline/edit-diff.js
|
|
7565
7364
|
function detectEnding(content) {
|
|
7566
7365
|
const lfIdx = content.indexOf("\n");
|
|
7567
7366
|
if (lfIdx === -1) return content.indexOf("\r") >= 0 ? "\r" : "\n";
|
|
@@ -7676,7 +7475,7 @@ function genDiff(oldContent, newContent, contextLines = 2, newContentHashes, old
|
|
|
7676
7475
|
}
|
|
7677
7476
|
|
|
7678
7477
|
//#endregion
|
|
7679
|
-
//#region src/
|
|
7478
|
+
//#region src/hashline/file-view.js
|
|
7680
7479
|
const MAX_HASH_LINES$1 = HASH_SPACE;
|
|
7681
7480
|
const DEFAULT_MAX_LINES = 2e3;
|
|
7682
7481
|
const DEFAULT_MAX_BYTES = 50 * 1024;
|
|
@@ -7900,7 +7699,7 @@ async function readView(io, path$1, cwd, opts = {}) {
|
|
|
7900
7699
|
}
|
|
7901
7700
|
|
|
7902
7701
|
//#endregion
|
|
7903
|
-
//#region src/
|
|
7702
|
+
//#region src/hashline/fs-bridge.js
|
|
7904
7703
|
/**
|
|
7905
7704
|
* Map an `ctx.fs` failure onto the hashline model-facing vocabulary so the
|
|
7906
7705
|
* model sees the same structured error codes as the pure pipeline.
|
|
@@ -7919,296 +7718,95 @@ function mapFsError(error, displayPath) {
|
|
|
7919
7718
|
if (code === "FS_ABORTED") throw new Error("Operation aborted");
|
|
7920
7719
|
}
|
|
7921
7720
|
throw error;
|
|
7922
|
-
}
|
|
7923
|
-
/** FileIO over the deployment's `ctx.fs` service. */
|
|
7924
|
-
function ctxFsIO(fs, ctx) {
|
|
7925
|
-
return {
|
|
7926
|
-
async resolve(path$1, cwd, signal) {
|
|
7927
|
-
const target = await fs.resolve(path$1, {
|
|
7928
|
-
...cwd !== void 0 ? { cwd } : {},
|
|
7929
|
-
...signal !== void 0 ? { signal } : {}
|
|
7930
|
-
});
|
|
7931
|
-
return fs.processPath(target);
|
|
7932
|
-
},
|
|
7933
|
-
async readText(absolutePath, signal) {
|
|
7934
|
-
try {
|
|
7935
|
-
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7936
|
-
return await fs.readText(target, signal);
|
|
7937
|
-
} catch (error) {
|
|
7938
|
-
return mapFsError(error, absolutePath);
|
|
7939
|
-
}
|
|
7940
|
-
},
|
|
7941
|
-
async writeText(absolutePath, content, signal, exec, sandboxPolicy) {
|
|
7942
|
-
try {
|
|
7943
|
-
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7944
|
-
const intent = await ctx.waterfall("fs/write-intent", target, exec, () => void 0);
|
|
7945
|
-
const outcome = await fs.writeText(target, content, intent, signal, sandboxPolicy);
|
|
7946
|
-
ctx.emit("fs/observed", target, {
|
|
7947
|
-
kind: "present",
|
|
7948
|
-
version: outcome.version
|
|
7949
|
-
}, exec);
|
|
7950
|
-
} catch (error) {
|
|
7951
|
-
return mapFsError(error, absolutePath);
|
|
7952
|
-
}
|
|
7953
|
-
},
|
|
7954
|
-
async emitObserved(absolutePath, exec, signal) {
|
|
7955
|
-
try {
|
|
7956
|
-
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7957
|
-
const info = await fs.stat(target, signal);
|
|
7958
|
-
if (info !== void 0) ctx.emit("fs/observed", target, {
|
|
7959
|
-
kind: "present",
|
|
7960
|
-
version: info.version
|
|
7961
|
-
}, exec);
|
|
7962
|
-
} catch (error) {
|
|
7963
|
-
console.error(`dsh-better-edit: fs/observed emission failed for ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7964
|
-
}
|
|
7965
|
-
},
|
|
7966
|
-
async statVersion(absolutePath, signal) {
|
|
7967
|
-
try {
|
|
7968
|
-
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7969
|
-
return (await fs.stat(target, signal))?.version ?? void 0;
|
|
7970
|
-
} catch {
|
|
7971
|
-
return;
|
|
7972
|
-
}
|
|
7973
|
-
}
|
|
7974
|
-
};
|
|
7975
|
-
}
|
|
7976
|
-
|
|
7977
|
-
//#endregion
|
|
7978
|
-
//#region src/url-schemes/vendored/hashline/read-and-serve.js
|
|
7979
|
-
/** Appended when the file had non-UTF-8 bytes; editing rewrites it as UTF-8. */
|
|
7980
|
-
const UTF8_REWRITE_NOTE = "[Non-UTF-8 bytes shown as U+FFFD; editing rewrites the file as UTF-8.]";
|
|
7981
|
-
/**
|
|
7982
|
-
* Perform one read-and-serve: normalize the file at `rawPath`, render its
|
|
7983
|
-
* hashline preview, record the shown rows as served for the session, and clear
|
|
7984
|
-
* the reported-drift marks (a fresh read resets them). The returned text
|
|
7985
|
-
* carries the UTF-8 rewrite note when the file had decode errors.
|
|
7986
|
-
*
|
|
7987
|
-
* Emits nothing on the fs-observation gate — callers that need the
|
|
7988
|
-
* observation recorded (the `read` tool) do that themselves with their exec
|
|
7989
|
-
* context.
|
|
7990
|
-
*/
|
|
7991
|
-
async function readAndServe(io, rawPath, cwd, options) {
|
|
7992
|
-
const { signal } = options;
|
|
7993
|
-
abortIf(signal);
|
|
7994
|
-
const view = await readView(io, rawPath, cwd, {
|
|
7995
|
-
offset: options.offset,
|
|
7996
|
-
limit: options.limit,
|
|
7997
|
-
signal
|
|
7998
|
-
});
|
|
7999
|
-
if (view.served.length > 0) await recordServed(view.absolutePath, view.served, view.hashes.length);
|
|
8000
|
-
await clearDriftReported(view.absolutePath);
|
|
8001
|
-
return {
|
|
8002
|
-
text: view.hadUtf8DecodeErrors ? `${view.text}\n\n${UTF8_REWRITE_NOTE}` : view.text,
|
|
8003
|
-
served: view.served,
|
|
8004
|
-
hadUtf8DecodeErrors: view.hadUtf8DecodeErrors,
|
|
8005
|
-
absolutePath: view.absolutePath
|
|
8006
|
-
};
|
|
8007
|
-
}
|
|
8008
|
-
|
|
8009
|
-
//#endregion
|
|
8010
|
-
//#region src/url-schemes/tools/read.ts
|
|
8011
|
-
/** Mirrors `parseUrl`'s scheme prefix so the fork matches the resolver exactly. */
|
|
8012
|
-
const SCHEME_URL_RE = /^[a-z][a-z0-9]*:\/\//;
|
|
8013
|
-
/**
|
|
8014
|
-
* Build the URL-aware `read` tool.
|
|
8015
|
-
*
|
|
8016
|
-
* The URL branch builds the resolver env per call (agent + cwd + rawUrl); the
|
|
8017
|
-
* scheme handlers read whichever fields they need off it.
|
|
8018
|
-
*/
|
|
8019
|
-
function createReadTool(deps) {
|
|
8020
|
-
const { resolver, fs, ctx, gates, capturedRead } = deps;
|
|
8021
|
-
const io = ctxFsIO(fs, ctx);
|
|
8022
|
-
return defineTool({
|
|
8023
|
-
name: "read",
|
|
8024
|
-
description: "Read a text file (each line returned as `HASH│content` with a 3-char hash anchor for later edit calls). File reads page with offset/limit.",
|
|
8025
|
-
parameters: {
|
|
8026
|
-
path: {
|
|
8027
|
-
type: "string",
|
|
8028
|
-
description: "File path (hashline-anchored read)."
|
|
8029
|
-
},
|
|
8030
|
-
offset: {
|
|
8031
|
-
type: "number",
|
|
8032
|
-
description: "Line number to start reading from (1-indexed, file reads only)"
|
|
8033
|
-
},
|
|
8034
|
-
limit: {
|
|
8035
|
-
type: "number",
|
|
8036
|
-
description: "Maximum number of lines to read (file reads only)"
|
|
8037
|
-
}
|
|
8038
|
-
},
|
|
8039
|
-
output: {
|
|
8040
|
-
schema: { type: "string" },
|
|
8041
|
-
render: (_args, value) => [{
|
|
8042
|
-
type: "text",
|
|
8043
|
-
text: value
|
|
8044
|
-
}]
|
|
8045
|
-
},
|
|
8046
|
-
async execute(args, exec) {
|
|
8047
|
-
const canonical = normalizeRequest(args);
|
|
8048
|
-
assertReadRequest(canonical);
|
|
8049
|
-
const rawPath = canonical.path;
|
|
8050
|
-
const isScheme = SCHEME_URL_RE.test(rawPath);
|
|
8051
|
-
if (gates.urlSchemes && isScheme) {
|
|
8052
|
-
const cwd = exec.agent?.session.header.cwd;
|
|
8053
|
-
const env = cwd === void 0 ? {
|
|
8054
|
-
agent: exec.agent,
|
|
8055
|
-
rawUrl: rawPath
|
|
8056
|
-
} : {
|
|
8057
|
-
agent: exec.agent,
|
|
8058
|
-
cwd,
|
|
8059
|
-
rawUrl: rawPath
|
|
8060
|
-
};
|
|
8061
|
-
return resolver.resolve(env, rawPath);
|
|
8062
|
-
}
|
|
8063
|
-
if (gates.hashline && !isScheme) return withWorkspace(execCwd(exec), async () => {
|
|
8064
|
-
const cwd = execCwd(exec);
|
|
8065
|
-
const signal = exec.signal;
|
|
8066
|
-
const { text, absolutePath } = await readAndServe(io, rawPath, cwd, {
|
|
8067
|
-
signal,
|
|
8068
|
-
offset: canonical.offset,
|
|
8069
|
-
limit: canonical.limit
|
|
8070
|
-
});
|
|
8071
|
-
await io.emitObserved(absolutePath, exec, signal);
|
|
8072
|
-
return text;
|
|
8073
|
-
});
|
|
8074
|
-
if (capturedRead !== void 0 && capturedRead.execute !== void 0) return capturedRead.execute(args, exec);
|
|
8075
|
-
throw new UrlSchemesError("NATIVE_READ_UNAVAILABLE", "no read delegate is available: urlSchemes and hashline gates are both disabled");
|
|
8076
|
-
}
|
|
8077
|
-
});
|
|
8078
|
-
}
|
|
8079
|
-
|
|
8080
|
-
//#endregion
|
|
8081
|
-
//#region src/url-schemes/tools/write.ts
|
|
8082
|
-
/**
|
|
8083
|
-
* Schemes the URL schema registers (v0.1.8d): the default write dispatch
|
|
8084
|
-
* keys off this table; the integration step can replace the whole
|
|
8085
|
-
* dispatch through `writeScheme` when it needs the live registry.
|
|
8086
|
-
*
|
|
8087
|
-
* Rendered from `catalog.ts` rather than written out here — this literal was
|
|
8088
|
-
* one of the hand-maintained copies that drifted from the scheme set.
|
|
8089
|
-
*/
|
|
8090
|
-
const REGISTERED_SCHEMES = SCHEME_NAMES;
|
|
8091
|
-
/** Detects a `scheme://` prefix with the resolver layer's own parser. */
|
|
8092
|
-
function isSchemeUrl(raw) {
|
|
8093
|
-
try {
|
|
8094
|
-
parseUrl(raw);
|
|
8095
|
-
return true;
|
|
8096
|
-
} catch (error) {
|
|
8097
|
-
if (error instanceof UrlSchemesError && error.code === "URL_NO_SCHEME") return false;
|
|
8098
|
-
throw error;
|
|
8099
|
-
}
|
|
8100
|
-
}
|
|
8101
|
-
/**
|
|
8102
|
-
* Default scheme-write dispatch: `dvc://` routes through the device
|
|
8103
|
-
* registry (`dispatchDvcWrite`); every other write channel is rejected.
|
|
8104
|
-
*/
|
|
8105
|
-
async function defaultSchemeWrite(scheme, path$1, content) {
|
|
8106
|
-
if (scheme === "dvc") {
|
|
8107
|
-
const result = await dispatchDvcWrite(path$1, content);
|
|
8108
|
-
return {
|
|
8109
|
-
path: `dvc://${path$1}`,
|
|
8110
|
-
operation: "execute",
|
|
8111
|
-
before: "",
|
|
8112
|
-
after: JSON.stringify(result, null, 2)
|
|
8113
|
-
};
|
|
8114
|
-
}
|
|
8115
|
-
if (scheme === "ctx") throw new UrlSchemesError("URL_READ_ONLY", `ctx:// is a curated read-only snapshot — write to ctx://${path$1} is not supported`);
|
|
8116
|
-
if (REGISTERED_SCHEMES.includes(scheme)) throw new UrlSchemesError("URL_WRITE_UNSUPPORTED", `write to ${scheme}:// is not supported (read-only scheme, or its write channel is not wired yet)`);
|
|
8117
|
-
throw new UrlSchemesError("URL_UNREGISTERED_SCHEME", `no handler registered for scheme "${scheme}" (registered: ${REGISTERED_SCHEMES.join(", ")})`);
|
|
8118
|
-
}
|
|
8119
|
-
/**
|
|
8120
|
-
* Build the `write` {@link ToolDefinition}: `scheme://` paths route to the
|
|
8121
|
-
* scheme write dispatch, ordinary paths delegate to the captured native
|
|
8122
|
-
* write definition with args and exec passed through untouched.
|
|
8123
|
-
*/
|
|
8124
|
-
function createWriteTool(deps) {
|
|
8125
|
-
const { nativeWrite, postWrite, preWriteFormat, sandbox } = deps;
|
|
8126
|
-
const writeScheme = deps.writeScheme ?? defaultSchemeWrite;
|
|
8127
|
-
return defineTool({
|
|
8128
|
-
name: "write",
|
|
8129
|
-
description: "Create or fully replace a UTF-8 text file.",
|
|
8130
|
-
parameters: {
|
|
8131
|
-
file_path: {
|
|
8132
|
-
type: "string",
|
|
8133
|
-
required: true,
|
|
8134
|
-
description: "Path to write, resolved by the filesystem backend."
|
|
8135
|
-
},
|
|
8136
|
-
content: {
|
|
8137
|
-
type: "string",
|
|
8138
|
-
required: true,
|
|
8139
|
-
description: "Full UTF-8 text content to write."
|
|
8140
|
-
},
|
|
8141
|
-
...sandbox !== void 0 && sandbox.escalationModes.length > 0 ? sandbox.schemaFields() : {}
|
|
8142
|
-
},
|
|
8143
|
-
output: {
|
|
8144
|
-
schema: {
|
|
8145
|
-
type: "object",
|
|
8146
|
-
additionalProperties: false,
|
|
8147
|
-
properties: {
|
|
8148
|
-
path: {
|
|
8149
|
-
type: "string",
|
|
8150
|
-
required: true
|
|
8151
|
-
},
|
|
8152
|
-
operation: {
|
|
8153
|
-
type: "string",
|
|
8154
|
-
required: true,
|
|
8155
|
-
enum: [
|
|
8156
|
-
"create",
|
|
8157
|
-
"update",
|
|
8158
|
-
"execute"
|
|
8159
|
-
]
|
|
8160
|
-
},
|
|
8161
|
-
before: {
|
|
8162
|
-
required: true,
|
|
8163
|
-
oneOf: [{ type: "string" }, { type: "null" }]
|
|
8164
|
-
},
|
|
8165
|
-
after: {
|
|
8166
|
-
type: "string",
|
|
8167
|
-
required: true
|
|
8168
|
-
},
|
|
8169
|
-
diagnostics: { type: "string" }
|
|
8170
|
-
}
|
|
8171
|
-
},
|
|
8172
|
-
render: (_args, value) => {
|
|
8173
|
-
const verb = value.operation === "update" ? "Updated" : value.operation === "execute" ? "Executed" : "Created";
|
|
8174
|
-
let text = value.diagnostics === void 0 ? `${verb} ${value.path}` : `${verb} ${value.path}\n${value.diagnostics}`;
|
|
8175
|
-
if (value.operation === "execute" && value.after !== void 0) text += `\n${value.after}`;
|
|
8176
|
-
return [{
|
|
8177
|
-
type: "text",
|
|
8178
|
-
text
|
|
8179
|
-
}];
|
|
7721
|
+
}
|
|
7722
|
+
/** FileIO over the deployment's `ctx.fs` service. */
|
|
7723
|
+
function ctxFsIO(fs, ctx) {
|
|
7724
|
+
return {
|
|
7725
|
+
async resolve(path$1, cwd, signal) {
|
|
7726
|
+
const target = await fs.resolve(path$1, {
|
|
7727
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
7728
|
+
...signal !== void 0 ? { signal } : {}
|
|
7729
|
+
});
|
|
7730
|
+
return fs.processPath(target);
|
|
7731
|
+
},
|
|
7732
|
+
async readText(absolutePath, signal) {
|
|
7733
|
+
try {
|
|
7734
|
+
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7735
|
+
return await fs.readText(target, signal);
|
|
7736
|
+
} catch (error) {
|
|
7737
|
+
return mapFsError(error, absolutePath);
|
|
8180
7738
|
}
|
|
8181
7739
|
},
|
|
8182
|
-
async
|
|
8183
|
-
|
|
8184
|
-
const
|
|
8185
|
-
|
|
7740
|
+
async writeText(absolutePath, content, signal, exec, sandboxPolicy) {
|
|
7741
|
+
try {
|
|
7742
|
+
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7743
|
+
const intent = await ctx.waterfall("fs/write-intent", target, exec, () => void 0);
|
|
7744
|
+
const outcome = await fs.writeText(target, content, intent, signal, sandboxPolicy);
|
|
7745
|
+
ctx.emit("fs/observed", target, {
|
|
7746
|
+
kind: "present",
|
|
7747
|
+
version: outcome.version
|
|
7748
|
+
}, exec);
|
|
7749
|
+
} catch (error) {
|
|
7750
|
+
return mapFsError(error, absolutePath);
|
|
8186
7751
|
}
|
|
8187
|
-
|
|
8188
|
-
|
|
7752
|
+
},
|
|
7753
|
+
async emitObserved(absolutePath, exec, signal) {
|
|
8189
7754
|
try {
|
|
8190
|
-
const
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
}
|
|
8197
|
-
|
|
7755
|
+
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7756
|
+
const info = await fs.stat(target, signal);
|
|
7757
|
+
if (info !== void 0) ctx.emit("fs/observed", target, {
|
|
7758
|
+
kind: "present",
|
|
7759
|
+
version: info.version
|
|
7760
|
+
}, exec);
|
|
7761
|
+
} catch (error) {
|
|
7762
|
+
console.error(`dsh-better-edit: fs/observed emission failed for ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7763
|
+
}
|
|
7764
|
+
},
|
|
7765
|
+
async statVersion(absolutePath, signal) {
|
|
8198
7766
|
try {
|
|
8199
|
-
const
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
} catch {}
|
|
8205
|
-
return outcome;
|
|
7767
|
+
const target = await fs.resolve(absolutePath, { ...signal !== void 0 ? { signal } : {} });
|
|
7768
|
+
return (await fs.stat(target, signal))?.version ?? void 0;
|
|
7769
|
+
} catch {
|
|
7770
|
+
return;
|
|
7771
|
+
}
|
|
8206
7772
|
}
|
|
7773
|
+
};
|
|
7774
|
+
}
|
|
7775
|
+
|
|
7776
|
+
//#endregion
|
|
7777
|
+
//#region src/hashline/read-and-serve.js
|
|
7778
|
+
/** Appended when the file had non-UTF-8 bytes; editing rewrites it as UTF-8. */
|
|
7779
|
+
const UTF8_REWRITE_NOTE = "[Non-UTF-8 bytes shown as U+FFFD; editing rewrites the file as UTF-8.]";
|
|
7780
|
+
/**
|
|
7781
|
+
* Perform one read-and-serve: normalize the file at `rawPath`, render its
|
|
7782
|
+
* hashline preview, record the shown rows as served for the session, and clear
|
|
7783
|
+
* the reported-drift marks (a fresh read resets them). The returned text
|
|
7784
|
+
* carries the UTF-8 rewrite note when the file had decode errors.
|
|
7785
|
+
*
|
|
7786
|
+
* Emits nothing on the fs-observation gate — callers that need the
|
|
7787
|
+
* observation recorded (the `read` tool) do that themselves with their exec
|
|
7788
|
+
* context.
|
|
7789
|
+
*/
|
|
7790
|
+
async function readAndServe(io, rawPath, cwd, options) {
|
|
7791
|
+
const { signal } = options;
|
|
7792
|
+
abortIf(signal);
|
|
7793
|
+
const view = await readView(io, rawPath, cwd, {
|
|
7794
|
+
offset: options.offset,
|
|
7795
|
+
limit: options.limit,
|
|
7796
|
+
signal
|
|
8207
7797
|
});
|
|
7798
|
+
if (view.served.length > 0) await recordServed(view.absolutePath, view.served, view.hashes.length);
|
|
7799
|
+
await clearDriftReported(view.absolutePath);
|
|
7800
|
+
return {
|
|
7801
|
+
text: view.hadUtf8DecodeErrors ? `${view.text}\n\n${UTF8_REWRITE_NOTE}` : view.text,
|
|
7802
|
+
served: view.served,
|
|
7803
|
+
hadUtf8DecodeErrors: view.hadUtf8DecodeErrors,
|
|
7804
|
+
absolutePath: view.absolutePath
|
|
7805
|
+
};
|
|
8208
7806
|
}
|
|
8209
7807
|
|
|
8210
7808
|
//#endregion
|
|
8211
|
-
//#region src/
|
|
7809
|
+
//#region src/hashline/sandbox.js
|
|
8212
7810
|
/**
|
|
8213
7811
|
* The filesystem escalation API: advertisement gating, per-call policy
|
|
8214
7812
|
* resolution, the one-approved wider retry, and denial-marker mapping. A pure
|
|
@@ -8294,7 +7892,7 @@ var FsSandboxController = class {
|
|
|
8294
7892
|
};
|
|
8295
7893
|
|
|
8296
7894
|
//#endregion
|
|
8297
|
-
//#region src/
|
|
7895
|
+
//#region src/hashline/undo-edit.js
|
|
8298
7896
|
/** Load the last undo row for a path from the active store, if any. */
|
|
8299
7897
|
async function readUndo(path$1) {
|
|
8300
7898
|
return (await loadHashStore()).getUndo(path$1);
|
|
@@ -8376,7 +7974,7 @@ async function clearUndo(path$1) {
|
|
|
8376
7974
|
}
|
|
8377
7975
|
|
|
8378
7976
|
//#endregion
|
|
8379
|
-
//#region src/
|
|
7977
|
+
//#region src/hashline/noop-guard.js
|
|
8380
7978
|
const noopLoopTracker = /* @__PURE__ */ new Map();
|
|
8381
7979
|
function noopPayloadKey(absolutePath, removeFrom, removeTo, replacementText) {
|
|
8382
7980
|
return JSON.stringify([
|
|
@@ -8400,7 +7998,7 @@ function clearNoopLoop(absolutePath) {
|
|
|
8400
7998
|
}
|
|
8401
7999
|
|
|
8402
8000
|
//#endregion
|
|
8403
|
-
//#region src/
|
|
8001
|
+
//#region src/hashline/edit-engine.js
|
|
8404
8002
|
/** The hashes a range edit removes, for stable re-hash bookkeeping. */
|
|
8405
8003
|
function collectRemovedHashes(edit, originalHashes) {
|
|
8406
8004
|
const removedHashes = /* @__PURE__ */ new Set();
|
|
@@ -8735,7 +8333,7 @@ async function persistUndoAndWrite(opts) {
|
|
|
8735
8333
|
}
|
|
8736
8334
|
|
|
8737
8335
|
//#endregion
|
|
8738
|
-
//#region src/
|
|
8336
|
+
//#region src/hashline/edit-response.js
|
|
8739
8337
|
function buildMetrics(args) {
|
|
8740
8338
|
const metrics = {
|
|
8741
8339
|
edits_attempted: args.editsAttempted,
|
|
@@ -8826,7 +8424,7 @@ function buildBatchResult(sections) {
|
|
|
8826
8424
|
}
|
|
8827
8425
|
|
|
8828
8426
|
//#endregion
|
|
8829
|
-
//#region src/
|
|
8427
|
+
//#region src/hashline/mutation.js
|
|
8830
8428
|
/**
|
|
8831
8429
|
* Deep seam: execute the full mutation lifecycle.
|
|
8832
8430
|
*
|
|
@@ -8929,7 +8527,7 @@ async function commit(opts) {
|
|
|
8929
8527
|
}
|
|
8930
8528
|
|
|
8931
8529
|
//#endregion
|
|
8932
|
-
//#region src/
|
|
8530
|
+
//#region src/hashline/prompts.js
|
|
8933
8531
|
/**
|
|
8934
8532
|
* Model-facing prompt text for the hashline tools, embedded so the bundle
|
|
8935
8533
|
* ships no external prompt files. Each tool's schema `description` is short;
|
|
@@ -8964,7 +8562,7 @@ const UNDO_GUIDANCE = {
|
|
|
8964
8562
|
};
|
|
8965
8563
|
|
|
8966
8564
|
//#endregion
|
|
8967
|
-
//#region src/
|
|
8565
|
+
//#region src/hashline/tool-edit.js
|
|
8968
8566
|
async function resolveNullPath(edits) {
|
|
8969
8567
|
if (edits.length === 0) return void 0;
|
|
8970
8568
|
const first = edits[0];
|
|
@@ -9064,7 +8662,7 @@ function registerEditTool(_rootCtx, agentCtx, io, sandbox) {
|
|
|
9064
8662
|
}
|
|
9065
8663
|
|
|
9066
8664
|
//#endregion
|
|
9067
|
-
//#region src/
|
|
8665
|
+
//#region src/hashline/tool-undo.js
|
|
9068
8666
|
/**
|
|
9069
8667
|
* Register the `undo_last_edit` tool on the calling agent's scope.
|
|
9070
8668
|
* @param _rootCtx - host context.
|
|
@@ -9164,7 +8762,7 @@ function registerUndoTool(_rootCtx, agentCtx, io, sandbox) {
|
|
|
9164
8762
|
}
|
|
9165
8763
|
|
|
9166
8764
|
//#endregion
|
|
9167
|
-
//#region src/
|
|
8765
|
+
//#region src/hashline/guidance/parse.js
|
|
9168
8766
|
/**
|
|
9169
8767
|
* Pure parser for per-preset guidance override files.
|
|
9170
8768
|
*
|
|
@@ -9235,7 +8833,7 @@ function isBlankOverride(content) {
|
|
|
9235
8833
|
}
|
|
9236
8834
|
|
|
9237
8835
|
//#endregion
|
|
9238
|
-
//#region src/
|
|
8836
|
+
//#region src/hashline/guidance/resolve.js
|
|
9239
8837
|
/**
|
|
9240
8838
|
* Render one tool's guidance as its intro line, a blank line, then bullets.
|
|
9241
8839
|
* Uniform across the four sections: no tool-schema description is duplicated
|
|
@@ -9333,7 +8931,7 @@ async function composeSections(presetId, homeDir) {
|
|
|
9333
8931
|
}
|
|
9334
8932
|
|
|
9335
8933
|
//#endregion
|
|
9336
|
-
//#region src/
|
|
8934
|
+
//#region src/hashline/guidance/materialize.js
|
|
9337
8935
|
/** The presets shipped by the harness, each seeded with editable guidance. */
|
|
9338
8936
|
const DEFAULT_PRESETS = [
|
|
9339
8937
|
"standard",
|
|
@@ -9508,6 +9106,138 @@ async function ensurePresetGuidance(homeDir) {
|
|
|
9508
9106
|
}));
|
|
9509
9107
|
}
|
|
9510
9108
|
|
|
9109
|
+
//#endregion
|
|
9110
|
+
//#region src/hashline/install.ts
|
|
9111
|
+
/**
|
|
9112
|
+
* One-time process init: warm the hasher and materialize the editable
|
|
9113
|
+
* per-preset guidance overrides (idempotent; failures are noise, never a
|
|
9114
|
+
* failed boot).
|
|
9115
|
+
*/
|
|
9116
|
+
function initHashlineRuntime() {
|
|
9117
|
+
initHasher().catch(() => {});
|
|
9118
|
+
ensurePresetGuidance(configDir()).catch(() => {});
|
|
9119
|
+
}
|
|
9120
|
+
/**
|
|
9121
|
+
* The hashline `read` doer: every path is a filesystem path run through the
|
|
9122
|
+
* anchored read-and-serve pipeline (`HASH│content` anchors + served-row store
|
|
9123
|
+
* that the `edit` tool consumes). No scheme knowledge — scheme-aware wrapping
|
|
9124
|
+
* is the caller's business (composition root chains this under its wrapper;
|
|
9125
|
+
* standalone callers register it as `read` directly).
|
|
9126
|
+
*/
|
|
9127
|
+
function createHashlineReadTool(deps) {
|
|
9128
|
+
const { io } = deps;
|
|
9129
|
+
return defineTool({
|
|
9130
|
+
name: "read",
|
|
9131
|
+
description: "Read a text file (each line returned as `HASH│content` with a 3-char hash anchor for later edit calls). File reads page with offset/limit.",
|
|
9132
|
+
parameters: {
|
|
9133
|
+
path: {
|
|
9134
|
+
type: "string",
|
|
9135
|
+
description: "File path (hashline-anchored read)."
|
|
9136
|
+
},
|
|
9137
|
+
offset: {
|
|
9138
|
+
type: "number",
|
|
9139
|
+
description: "Line number to start reading from (1-indexed, file reads only)"
|
|
9140
|
+
},
|
|
9141
|
+
limit: {
|
|
9142
|
+
type: "number",
|
|
9143
|
+
description: "Maximum number of lines to read (file reads only)"
|
|
9144
|
+
}
|
|
9145
|
+
},
|
|
9146
|
+
output: {
|
|
9147
|
+
schema: { type: "string" },
|
|
9148
|
+
render: (_args, value) => [{
|
|
9149
|
+
type: "text",
|
|
9150
|
+
text: value
|
|
9151
|
+
}]
|
|
9152
|
+
},
|
|
9153
|
+
async execute(args, exec) {
|
|
9154
|
+
const canonical = normalizeRequest(args);
|
|
9155
|
+
assertReadRequest(canonical);
|
|
9156
|
+
const rawPath = canonical.path;
|
|
9157
|
+
return withWorkspace(execCwd(exec), async () => {
|
|
9158
|
+
const cwd = execCwd(exec);
|
|
9159
|
+
const signal = exec.signal;
|
|
9160
|
+
const { text, absolutePath } = await readAndServe(io, rawPath, cwd, {
|
|
9161
|
+
signal,
|
|
9162
|
+
offset: canonical.offset,
|
|
9163
|
+
limit: canonical.limit
|
|
9164
|
+
});
|
|
9165
|
+
await io.emitObserved(absolutePath, exec, signal);
|
|
9166
|
+
return text;
|
|
9167
|
+
});
|
|
9168
|
+
}
|
|
9169
|
+
});
|
|
9170
|
+
}
|
|
9171
|
+
/**
|
|
9172
|
+
* Register the hashline feature on one agent's own scope layer (async: the
|
|
9173
|
+
* `edit`/`undo` tools, the tool guidance sections (preset-aware, compiled
|
|
9174
|
+
* defaults on failure), and the lsp post-edit diagnostics hook. `read` is NOT
|
|
9175
|
+
* registered here — the caller owns the single `read` registration and should
|
|
9176
|
+
* register {@link createHashlineReadTool}'s output (directly or chained).
|
|
9177
|
+
*/
|
|
9178
|
+
async function installHashline(rootCtx, agent, opts = {}) {
|
|
9179
|
+
const disposers = [];
|
|
9180
|
+
const sandbox = opts.sandbox ?? new FsSandboxController(rootCtx);
|
|
9181
|
+
const io = ctxFsIO(rootCtx.fs, rootCtx);
|
|
9182
|
+
const readTool = createHashlineReadTool({ io });
|
|
9183
|
+
disposers.push(registerEditTool(rootCtx, agent.ctx, io, sandbox));
|
|
9184
|
+
disposers.push(registerUndoTool(rootCtx, agent.ctx, io, sandbox));
|
|
9185
|
+
const diagnosticsHook = opts.editFeedback;
|
|
9186
|
+
if (diagnosticsHook !== void 0) disposers.push(agent.ctx.on("tools/post-execute", async (exec, result, next) => {
|
|
9187
|
+
const decision = await next();
|
|
9188
|
+
if (exec.name !== "edit" || result.isError) return decision;
|
|
9189
|
+
const rawPath = exec.arguments?.path;
|
|
9190
|
+
if (typeof rawPath !== "string" || rawPath === "") return decision;
|
|
9191
|
+
try {
|
|
9192
|
+
const content = await io.readText(rawPath, exec.signal);
|
|
9193
|
+
if (typeof content !== "string") return decision;
|
|
9194
|
+
const summary = await diagnosticsHook(rawPath, content);
|
|
9195
|
+
if (summary === void 0) return decision;
|
|
9196
|
+
const decisionRecord = decision;
|
|
9197
|
+
if (decisionRecord.kind !== "accept") return decision;
|
|
9198
|
+
decisionRecord.content = [...decisionRecord.content ?? result.content ?? [], {
|
|
9199
|
+
type: "text",
|
|
9200
|
+
text: `\n${summary}`
|
|
9201
|
+
}];
|
|
9202
|
+
return decision;
|
|
9203
|
+
} catch {
|
|
9204
|
+
return decision;
|
|
9205
|
+
}
|
|
9206
|
+
}));
|
|
9207
|
+
try {
|
|
9208
|
+
const agentPresets = rootCtx.get("agentPresets");
|
|
9209
|
+
let sections = GUIDANCE_SECTIONS.map((section) => ({
|
|
9210
|
+
name: section.name,
|
|
9211
|
+
order: section.defaultOrder,
|
|
9212
|
+
text: section.renderDefault()
|
|
9213
|
+
}));
|
|
9214
|
+
if (agentPresets !== void 0) try {
|
|
9215
|
+
sections = (await composeSections(agentPresets.composedPreset(agent.ctx), configDir())).map((section) => ({
|
|
9216
|
+
name: section.name,
|
|
9217
|
+
order: section.order,
|
|
9218
|
+
text: section.text
|
|
9219
|
+
}));
|
|
9220
|
+
} catch {}
|
|
9221
|
+
for (const section of sections) disposers.push(agent.ctx.systemPrompt.section(section));
|
|
9222
|
+
} catch {}
|
|
9223
|
+
return {
|
|
9224
|
+
disposers,
|
|
9225
|
+
io,
|
|
9226
|
+
sandbox,
|
|
9227
|
+
readTool
|
|
9228
|
+
};
|
|
9229
|
+
}
|
|
9230
|
+
|
|
9231
|
+
//#endregion
|
|
9232
|
+
//#region src/url-schemes/gates.ts
|
|
9233
|
+
/** Every key defaults on — the service is opt-out, not opt-in. */
|
|
9234
|
+
function resolveGates(config) {
|
|
9235
|
+
return {
|
|
9236
|
+
urlSchemes: config?.urlSchemes !== false,
|
|
9237
|
+
hashline: config?.hashline !== false
|
|
9238
|
+
};
|
|
9239
|
+
}
|
|
9240
|
+
|
|
9511
9241
|
//#endregion
|
|
9512
9242
|
//#region src/url-schemes/index.ts
|
|
9513
9243
|
/** Cordis plugin name. */
|
|
@@ -9529,13 +9259,6 @@ const inject$1 = [
|
|
|
9529
9259
|
"agents",
|
|
9530
9260
|
"sessionPersistence"
|
|
9531
9261
|
];
|
|
9532
|
-
/** Every key defaults on — the service is opt-out, not opt-in. */
|
|
9533
|
-
function resolveGates(config) {
|
|
9534
|
-
return {
|
|
9535
|
-
urlSchemes: config?.urlSchemes !== false,
|
|
9536
|
-
hashline: config?.hashline !== false
|
|
9537
|
-
};
|
|
9538
|
-
}
|
|
9539
9262
|
/** Register the four URL-aware tools on one agent's own scope layer. */
|
|
9540
9263
|
/**
|
|
9541
9264
|
* The lsp feedback loop (native-tools Wave3): both hooks route through the
|
|
@@ -9588,71 +9311,39 @@ function buildLspWriteFeedback() {
|
|
|
9588
9311
|
postWrite: (filePath, content) => lspFeedback("diagnostics", filePath, content)
|
|
9589
9312
|
};
|
|
9590
9313
|
}
|
|
9591
|
-
function installAgentTools(rootCtx, agent, resolver, gates) {
|
|
9314
|
+
async function installAgentTools(rootCtx, agent, resolver, gates$1) {
|
|
9592
9315
|
agent.ctx.effect(async () => {
|
|
9593
9316
|
const native = captureNativeTools(rootCtx, agent);
|
|
9594
9317
|
const disposers = [];
|
|
9595
9318
|
const hashlineSandbox = new FsSandboxController(rootCtx);
|
|
9596
|
-
|
|
9597
|
-
if (gates.hashline)
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9319
|
+
let readDelegate = native.read;
|
|
9320
|
+
if (gates$1.hashline) {
|
|
9321
|
+
const hashline = await installHashline(rootCtx, agent, {
|
|
9322
|
+
sandbox: hashlineSandbox,
|
|
9323
|
+
editFeedback: buildLspWriteFeedback().postWrite
|
|
9324
|
+
});
|
|
9325
|
+
disposers.push(...hashline.disposers);
|
|
9326
|
+
readDelegate = hashline.readTool;
|
|
9327
|
+
}
|
|
9328
|
+
if (gates$1.urlSchemes || gates$1.hashline) {
|
|
9329
|
+
const readTool = gates$1.urlSchemes && readDelegate !== void 0 ? createSchemeReadTool({
|
|
9330
|
+
resolver,
|
|
9331
|
+
capturedRead: readDelegate
|
|
9332
|
+
}) : readDelegate;
|
|
9333
|
+
if (readTool !== void 0) disposers.push(agent.ctx.tools.register(readTool));
|
|
9334
|
+
}
|
|
9335
|
+
if (gates$1.urlSchemes) disposers.push(agent.ctx.tools.register(createWriteTool({
|
|
9605
9336
|
nativeWrite: native.write,
|
|
9606
9337
|
sandbox: hashlineSandbox,
|
|
9607
9338
|
...buildLspWriteFeedback()
|
|
9608
9339
|
})));
|
|
9609
|
-
if (gates.hashline) {
|
|
9610
|
-
disposers.push(registerEditTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
|
|
9611
|
-
disposers.push(registerUndoTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
|
|
9612
|
-
}
|
|
9613
|
-
if (gates.hashline) {
|
|
9614
|
-
const diagnosticsHook = buildLspWriteFeedback().postWrite;
|
|
9615
|
-
disposers.push(agent.ctx.on("tools/post-execute", async (exec, result, next) => {
|
|
9616
|
-
const decision = await next();
|
|
9617
|
-
if (exec.name !== "edit" || result.isError) return decision;
|
|
9618
|
-
const rawPath = exec.arguments?.path;
|
|
9619
|
-
if (typeof rawPath !== "string" || rawPath === "") return decision;
|
|
9620
|
-
try {
|
|
9621
|
-
const content = await hashlineIo.readText(rawPath, exec.signal);
|
|
9622
|
-
if (typeof content !== "string") return decision;
|
|
9623
|
-
const summary = await diagnosticsHook(rawPath, content);
|
|
9624
|
-
if (summary === void 0) return decision;
|
|
9625
|
-
const decisionRecord = decision;
|
|
9626
|
-
if (decisionRecord.kind !== "accept") return decision;
|
|
9627
|
-
decisionRecord.content = [...decisionRecord.content ?? result.content ?? [], {
|
|
9628
|
-
type: "text",
|
|
9629
|
-
text: `\n${summary}`
|
|
9630
|
-
}];
|
|
9631
|
-
return decision;
|
|
9632
|
-
} catch {
|
|
9633
|
-
return decision;
|
|
9634
|
-
}
|
|
9635
|
-
}));
|
|
9636
|
-
}
|
|
9637
9340
|
try {
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
text: section.renderDefault()
|
|
9643
|
-
}));
|
|
9644
|
-
if (agentPresets !== void 0) try {
|
|
9645
|
-
sections = (await composeSections(agentPresets.composedPreset(agent.ctx), configDir())).map((section) => ({
|
|
9646
|
-
name: section.name,
|
|
9647
|
-
order: section.order,
|
|
9648
|
-
text: section.text
|
|
9649
|
-
}));
|
|
9650
|
-
} catch {}
|
|
9651
|
-
for (const section of sections) disposers.push(agent.ctx.systemPrompt.section(section));
|
|
9652
|
-
const general = generalSection(gates);
|
|
9653
|
-
if (general !== void 0) disposers.push(agent.ctx.systemPrompt.section(general));
|
|
9341
|
+
if (gates$1.urlSchemes) {
|
|
9342
|
+
const general = generalSection(gates$1);
|
|
9343
|
+
if (general !== void 0) disposers.push(agent.ctx.systemPrompt.section(general));
|
|
9344
|
+
}
|
|
9654
9345
|
} catch {}
|
|
9655
|
-
if (gates.urlSchemes) {
|
|
9346
|
+
if (gates$1.urlSchemes) {
|
|
9656
9347
|
disposers.push(agent.ctx.tools.register(createGrepTool({
|
|
9657
9348
|
resolver,
|
|
9658
9349
|
nativeGrep: native.grep
|
|
@@ -9662,6 +9353,31 @@ function installAgentTools(rootCtx, agent, resolver, gates) {
|
|
|
9662
9353
|
nativeGlob: native.glob
|
|
9663
9354
|
})));
|
|
9664
9355
|
}
|
|
9356
|
+
const sessionId = agent.id;
|
|
9357
|
+
disposers.push(agent.ctx.on("tools/post-execute", async (exec, result, next) => {
|
|
9358
|
+
const decision = await next();
|
|
9359
|
+
const name$2 = exec.name;
|
|
9360
|
+
if (name$2 !== "read" && name$2 !== "edit" && name$2 !== "write") return decision;
|
|
9361
|
+
const args = exec.arguments;
|
|
9362
|
+
const filePath = args?.path ?? args?.file_path;
|
|
9363
|
+
if (typeof filePath !== "string" || filePath === "") return decision;
|
|
9364
|
+
if (name$2 === "read") {
|
|
9365
|
+
lspGateSyncOnLand(sessionId, "read", filePath);
|
|
9366
|
+
return decision;
|
|
9367
|
+
}
|
|
9368
|
+
lspGateSyncOnLand(sessionId, name$2, filePath);
|
|
9369
|
+
if (result.isError) return decision;
|
|
9370
|
+
const notice = lspGateNotice(sessionId, filePath);
|
|
9371
|
+
if (notice === void 0) return decision;
|
|
9372
|
+
const record = decision;
|
|
9373
|
+
if (record.kind !== "accept") return decision;
|
|
9374
|
+
record.content = [...record.content ?? result.content ?? [], {
|
|
9375
|
+
type: "text",
|
|
9376
|
+
text: `\n${notice}`
|
|
9377
|
+
}];
|
|
9378
|
+
return decision;
|
|
9379
|
+
}));
|
|
9380
|
+
disposers.push(() => disposeLspGate(sessionId));
|
|
9665
9381
|
return () => {
|
|
9666
9382
|
for (const dispose of disposers) dispose();
|
|
9667
9383
|
};
|
|
@@ -9669,8 +9385,7 @@ function installAgentTools(rootCtx, agent, resolver, gates) {
|
|
|
9669
9385
|
}
|
|
9670
9386
|
/** Mount the resolver + scheme handlers, then install the tools per agent. */
|
|
9671
9387
|
function apply$1(ctx, config) {
|
|
9672
|
-
|
|
9673
|
-
ensurePresetGuidance(configDir()).catch(() => {});
|
|
9388
|
+
initHashlineRuntime();
|
|
9674
9389
|
const resolver = new UrlResolver();
|
|
9675
9390
|
if (resolveGates(config).urlSchemes && ctx.fs) try {
|
|
9676
9391
|
wrapFsWithSchemes(ctx.fs, { settings: ctx.settings });
|
|
@@ -9702,11 +9417,9 @@ function apply$1(ctx, config) {
|
|
|
9702
9417
|
ctx.on("agent/session-start", ({ agent }) => {
|
|
9703
9418
|
if (registered.has(agent)) return;
|
|
9704
9419
|
registered.add(agent);
|
|
9705
|
-
|
|
9706
|
-
installAgentTools(ctx, agent, resolver, resolveGates(config));
|
|
9707
|
-
} catch (error) {
|
|
9420
|
+
installAgentTools(ctx, agent, resolver, resolveGates(config)).catch((error) => {
|
|
9708
9421
|
ctx.logger("dsh-url-schemes").warn(`failed to install URL-aware tools for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9709
|
-
}
|
|
9422
|
+
});
|
|
9710
9423
|
});
|
|
9711
9424
|
}
|
|
9712
9425
|
var url_schemes_default = {
|
|
@@ -10400,7 +10113,7 @@ function splitRoute(route) {
|
|
|
10400
10113
|
//#endregion
|
|
10401
10114
|
//#region src/failover/index.ts
|
|
10402
10115
|
/** The `failover` settings schema: two optional fallback slots, both default "not set". */
|
|
10403
|
-
const Config$
|
|
10116
|
+
const Config$2 = z.object({
|
|
10404
10117
|
fallback1: z.string().default(""),
|
|
10405
10118
|
fallback2: z.string().default("")
|
|
10406
10119
|
});
|
|
@@ -10416,9 +10129,9 @@ function overrideConfig(seed, to) {
|
|
|
10416
10129
|
/** Install the failover. A no-op composition degrades gracefully (empty chain = pass-through). */
|
|
10417
10130
|
function installFailover(ctx, config = defaultFailoverConfig) {
|
|
10418
10131
|
const logger = ctx.logger("llm-failover");
|
|
10419
|
-
let source = () => Config$
|
|
10132
|
+
let source = () => Config$2(config);
|
|
10420
10133
|
ctx.inject(["settings"], (sctx) => {
|
|
10421
|
-
sctx.settings.installSection(ctx, FAILOVER_SETTINGS_NS, Config$
|
|
10134
|
+
sctx.settings.installSection(ctx, FAILOVER_SETTINGS_NS, Config$2, Config$2(config), {
|
|
10422
10135
|
setSource: (current$1) => {
|
|
10423
10136
|
source = current$1;
|
|
10424
10137
|
},
|
|
@@ -10472,6 +10185,143 @@ function installFailover(ctx, config = defaultFailoverConfig) {
|
|
|
10472
10185
|
});
|
|
10473
10186
|
}
|
|
10474
10187
|
|
|
10188
|
+
//#endregion
|
|
10189
|
+
//#region src/compaction/config.ts
|
|
10190
|
+
/**
|
|
10191
|
+
* The `compaction-tuning` General-row config: the share of the routed context
|
|
10192
|
+
* window at which automatic condensation starts.
|
|
10193
|
+
*
|
|
10194
|
+
* Pure module — no `@deepseek-ai/*` value imports — so the host half and the
|
|
10195
|
+
* client half may both import it without dragging either runtime into the
|
|
10196
|
+
* browser bundle: the host takes the namespace and the schema defaults, the
|
|
10197
|
+
* client takes the namespace literal plus the stepper bounds.
|
|
10198
|
+
*
|
|
10199
|
+
* @module dashr/compaction/config
|
|
10200
|
+
*/
|
|
10201
|
+
/** The settings namespace the General row reads and writes. */
|
|
10202
|
+
const COMPACTION_SETTINGS_NS = "compaction-tuning";
|
|
10203
|
+
/** Upstream `compaction-basic`'s own default: condense at 80% of the window. */
|
|
10204
|
+
const DEFAULT_THRESHOLD_RATIO = .8;
|
|
10205
|
+
/**
|
|
10206
|
+
* Absolute recent-tail budget, in estimated tokens (~50 KB of English text).
|
|
10207
|
+
*
|
|
10208
|
+
* Composition-only, deliberately: the General row drives `thresholdRatio`
|
|
10209
|
+
* alone, so the recency window stays a deployment decision (its base layer is
|
|
10210
|
+
* the row config in `cordis.patch.yml`) rather than a second knob on the
|
|
10211
|
+
* settings page. The host half still applies it to the engine.
|
|
10212
|
+
*/
|
|
10213
|
+
const DEFAULT_RETAIN_TOKENS = 5e4;
|
|
10214
|
+
/** Ratio bounds the settings schema accepts (a config file may use either end). */
|
|
10215
|
+
const THRESHOLD_MIN_RATIO = .05;
|
|
10216
|
+
/** @see THRESHOLD_MIN_RATIO */
|
|
10217
|
+
const THRESHOLD_MAX_RATIO = 1;
|
|
10218
|
+
/** Spec defaults — `Config({})` must equal this. */
|
|
10219
|
+
const defaultCompactionTuningConfig = {
|
|
10220
|
+
thresholdRatio: DEFAULT_THRESHOLD_RATIO,
|
|
10221
|
+
retainTokens: DEFAULT_RETAIN_TOKENS
|
|
10222
|
+
};
|
|
10223
|
+
|
|
10224
|
+
//#endregion
|
|
10225
|
+
//#region src/compaction/index.ts
|
|
10226
|
+
/**
|
|
10227
|
+
* The `compaction-tuning` settings schema.
|
|
10228
|
+
*
|
|
10229
|
+
* `thresholdRatio` is the field the General row drives; `retainTokens` is
|
|
10230
|
+
* composition-only (see {@link DEFAULT_RETAIN_TOKENS}) but still validated and
|
|
10231
|
+
* applied, so the recency window keeps one source of truth.
|
|
10232
|
+
*/
|
|
10233
|
+
const Config$1 = z.object({
|
|
10234
|
+
thresholdRatio: z.number().step(.01).min(THRESHOLD_MIN_RATIO).max(THRESHOLD_MAX_RATIO).default(DEFAULT_THRESHOLD_RATIO),
|
|
10235
|
+
retainTokens: z.number().step(1).min(0).default(DEFAULT_RETAIN_TOKENS)
|
|
10236
|
+
});
|
|
10237
|
+
/**
|
|
10238
|
+
* Install the settings namespace and keep the live compaction engine in sync.
|
|
10239
|
+
* @param ctx - host context of the row that owns this capability.
|
|
10240
|
+
* @param config - composition base layer; schema defaults fill the rest.
|
|
10241
|
+
*/
|
|
10242
|
+
function installCompactionTuning(ctx, config = defaultCompactionTuningConfig) {
|
|
10243
|
+
const logger = ctx.logger("compaction-tuning");
|
|
10244
|
+
let source = () => Config$1(config);
|
|
10245
|
+
/** Smallest routed context window seen so far (advisory guard, best effort). */
|
|
10246
|
+
let minWindow;
|
|
10247
|
+
let probing = false;
|
|
10248
|
+
/**
|
|
10249
|
+
* Learn the routed context windows so a write can be refused when it would
|
|
10250
|
+
* leave `retainTokens >= floor(window x thresholdRatio)` — the condition
|
|
10251
|
+
* under which the engine raises `TargetPressureConfigError` per route and
|
|
10252
|
+
* automatic condensation quietly stops for that route.
|
|
10253
|
+
*/
|
|
10254
|
+
const probeWindows = () => {
|
|
10255
|
+
if (probing || minWindow !== void 0) return;
|
|
10256
|
+
const llm = ctx.get("llm");
|
|
10257
|
+
if (llm === void 0) return;
|
|
10258
|
+
probing = true;
|
|
10259
|
+
(async () => {
|
|
10260
|
+
try {
|
|
10261
|
+
let smallest;
|
|
10262
|
+
for (const provider of llm.listProviders()) for (const model of await llm.listModels(provider.id)) {
|
|
10263
|
+
const window = (await llm.resolveModelInfo(provider.id, model.id)).context?.contextWindow;
|
|
10264
|
+
if (typeof window !== "number" || window <= 0) continue;
|
|
10265
|
+
smallest = smallest === void 0 ? window : Math.min(smallest, window);
|
|
10266
|
+
}
|
|
10267
|
+
if (smallest !== void 0) {
|
|
10268
|
+
minWindow = smallest;
|
|
10269
|
+
logger.info(`smallest routed context window: ${smallest} tokens`);
|
|
10270
|
+
}
|
|
10271
|
+
} catch (error) {
|
|
10272
|
+
logger.warn(`context-window probe failed (retention guard disabled): ${error instanceof Error ? error.message : String(error)}`);
|
|
10273
|
+
} finally {
|
|
10274
|
+
probing = false;
|
|
10275
|
+
}
|
|
10276
|
+
})();
|
|
10277
|
+
};
|
|
10278
|
+
/**
|
|
10279
|
+
* Overwrite only this module's two fields on the engine's current policy and
|
|
10280
|
+
* hand the engine a fresh frozen object.
|
|
10281
|
+
* @param reason - short diagnostic tag for the log line.
|
|
10282
|
+
*/
|
|
10283
|
+
const push = (reason) => {
|
|
10284
|
+
const compaction = ctx.get("compaction");
|
|
10285
|
+
if (compaction === void 0) return;
|
|
10286
|
+
const base = compaction.config;
|
|
10287
|
+
if (base === void 0) return;
|
|
10288
|
+
const value = source();
|
|
10289
|
+
const next = {
|
|
10290
|
+
...base,
|
|
10291
|
+
thresholdRatio: value.thresholdRatio,
|
|
10292
|
+
retainTokens: value.retainTokens
|
|
10293
|
+
};
|
|
10294
|
+
delete next.retainRatio;
|
|
10295
|
+
compaction.config = Object.freeze(next);
|
|
10296
|
+
logger.info(`applied (${reason}): thresholdRatio=${value.thresholdRatio} retainTokens=${value.retainTokens}`);
|
|
10297
|
+
};
|
|
10298
|
+
ctx.inject(["compaction"], () => {
|
|
10299
|
+
push("compaction-ready");
|
|
10300
|
+
probeWindows();
|
|
10301
|
+
});
|
|
10302
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
10303
|
+
if (settingsCtx.settings.get(COMPACTION_SETTINGS_NS) !== void 0) {
|
|
10304
|
+
logger.info("settings namespace already served; leaving it to its owner");
|
|
10305
|
+
return;
|
|
10306
|
+
}
|
|
10307
|
+
settingsCtx.settings.installSection(ctx, COMPACTION_SETTINGS_NS, Config$1, Config$1(config), {
|
|
10308
|
+
setSource: (current$1) => {
|
|
10309
|
+
source = current$1;
|
|
10310
|
+
},
|
|
10311
|
+
onChange: () => {
|
|
10312
|
+
push("settings-updated");
|
|
10313
|
+
},
|
|
10314
|
+
validate: (value) => {
|
|
10315
|
+
if (minWindow === void 0) return;
|
|
10316
|
+
const thresholdTokens = Math.floor(minWindow * value.thresholdRatio);
|
|
10317
|
+
if (value.retainTokens >= thresholdTokens) throw new Error(`retainTokens (${value.retainTokens}) must stay below the threshold tokens for every routed model; the smallest window here is ${minWindow}, so ${value.thresholdRatio} leaves only ${thresholdTokens}`);
|
|
10318
|
+
}
|
|
10319
|
+
});
|
|
10320
|
+
push("settings-installed");
|
|
10321
|
+
probeWindows();
|
|
10322
|
+
});
|
|
10323
|
+
}
|
|
10324
|
+
|
|
10475
10325
|
//#endregion
|
|
10476
10326
|
//#region src/mobile/zoom-guard.ts
|
|
10477
10327
|
/**
|
|
@@ -11391,6 +11241,7 @@ function apply(ctx, config) {
|
|
|
11391
11241
|
ctx.plugin(DashrRuntime, pickRuntimeConfig(config));
|
|
11392
11242
|
ctx.plugin(url_schemes_default, config);
|
|
11393
11243
|
installFailover(ctx);
|
|
11244
|
+
installCompactionTuning(ctx);
|
|
11394
11245
|
installWebTrust(ctx, config);
|
|
11395
11246
|
const logger = ctx.logger("dashr-repl");
|
|
11396
11247
|
const maxParallel = resolveMaxParallelSubCalls(config.maxParallelSubCalls);
|