@deepseek-ai/dsh-lsp-stdio 0.1.1-rc.2 → 0.1.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/lsp/lsp-stdio/README.md
5
- README.md: beadc34ec738ae3f6511cae764240c0e9d8c61d7
6
- README.zh.md: 2d6516a25147fb7593e8d45d843315098cdff7d3
5
+ README.md: 7acb5a591f3cf50a02d01e96f9b4a26ff3bf693b
6
+ README.zh.md: 27020c1babbcff8acde890db5df11eaede45301a
package/README.md CHANGED
@@ -1,52 +1,138 @@
1
+ ---
2
+ description: "The stdio language-server provider for ctx.lsp: configured server commands, extension mappings, and bounded transient-open queries, for users and maintainers composing local code navigation."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-lsp-stdio
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- A **generic stdio language-server backend** for `ctx.lsp`. One plugin instance accepts a named server table and registers one isolated provider per entry. It reads through `ctx.fs` and launches through `ctx.subprocess`, so the server and source always inhabit the mounted execution world. This is a generic host, not a language-server catalog or installer — deployments configure commands and mappings explicitly; presets belong in `cordis.yml` overlays.
10
+ ## Summary
11
+
12
+ `dsh-lsp-stdio` turns configured local language-server commands into providers on `ctx.lsp`: give it a table of server commands and extension-to-language mappings, and agents get semantic code navigation over the files in those languages — definitions, references, implementations, and hover — served by real language servers. One plugin instance registers one isolated provider per configured server; each provider lazily starts one server process per workspace and opens the queried document transiently, so no document state accumulates between queries. Servers and sources always live in the mounted filesystem and subprocess execution world. It is a generic host, not a language-server catalog or installer — deployments configure commands explicitly. This package trusts its configured servers and adds no sandbox of its own.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
6
22
 
7
- Namespace plugin (`name` / `inject` / `Config` / `apply`, no default export).
23
+ -----
8
24
 
9
- ## What it does
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
10
27
 
11
- - Resolves every server-local setting before registration; an invalid mapping or registration conflict rolls back earlier entries, so a failed load leaves no provider routes.
12
- - Lazily single-flights one server process per `(server id, canonical workspace target)`. A live server error is not replayed; if the selected pooled transport fails before or during a read-only query, the provider awaits its disposal and retries that query once on a fresh process.
13
- - Uses a compatibility-first **transient-open** sequence per query: resolve and byte-bound the source while streaming it through `ctx.fs`, `textDocument/didOpen` (version 1, full text), the requested request, then `textDocument/didClose` in `finally`. A failed or canceled `didOpen` write terminates the instance before the pool can reuse it. Documents close after each call, so the first version needs no `didChange`, content cache, or document LRU.
14
- - Serializes each source-read/open/query/close lifecycle through one abortable per-workspace queue so queued calls read current source only when their turn starts; distinct workspaces run in parallel. Provider disposal aborts filesystem and protocol work, awaits workspace lookups that have not entered a queue, then drains every queue and server.
15
- - After protocol shutdown fails, terminates the server's descendant tree through the subprocess seam (POSIX process-group signaling; Windows `taskkill /T /F`). Tree-kill delivery is contained like every group signal — it races server exit — and quiescence is confirmed by the handle's tree-liveness wait rather than by the kill's own outcome.
16
- - Resolves the server executable, cwd, process, and protocol streams through `ctx.subprocess`; `initialize.processId` is `null` because another machine or PID namespace must not monitor the harness process.
17
- - Uses `ctx.fs` canonical containment, file URIs, and streamed text validation, but emits no `fs/observed`: only the LSP result is model-visible, so a query does not satisfy read-before-write policy.
28
+ Mount this provider when a deployment has local language servers for example `typescript-language-server` and wants the harness to navigate code through them. It needs filesystem and subprocess providers for the same execution world, plus the `dsh-lsp` seam and, for model access, `dsh-tool-lsp`.
18
29
 
19
- ## Configuration
30
+ ### Minimal configuration
20
31
 
21
- The `servers` record key is the stable provider id reserved on `ctx.lsp`; each value has this shape:
32
+ The `servers` record maps each stable provider id to one server command. The provider resolves every executable at load after credential scrubbing, so a bad entry prevents every provider from registering; processes launch lazily on the first matching query.
22
33
 
23
- | Server key | Default | Meaning |
34
+ ```yaml
35
+ - name: '@deepseek-ai/dsh-fs-local'
36
+ - name: '@deepseek-ai/dsh-subprocess-local'
37
+ - name: '@deepseek-ai/dsh-lsp'
38
+ - name: '@deepseek-ai/dsh-lsp-stdio'
39
+ config:
40
+ servers:
41
+ typescript:
42
+ command: typescript-language-server
43
+ args: ['--stdio']
44
+ extensionToLanguage:
45
+ '.ts': typescript
46
+ - name: '@deepseek-ai/dsh-tool-lsp'
47
+ ```
48
+
49
+ | Field | Default | Meaning |
24
50
  |---|---|---|
25
- | `command` | (required) | Executable to spawn — absolute, or resolved on the child PATH at load. Launch uses no shell. |
26
- | `args` | `[]` | Arguments passed to the executable. |
27
- | `env` | `{}` | Extra env merged on top of the credential-scrubbed ambient env (vars matching `KEY`/`PASSWORD`/`SECRET`/`TOKEN` are not forwarded); an explicit `DSH_*` entry merges after the seam's scrub of ambient ones. |
28
- | `extensionToLanguage` | (required) | Lowercase leading-dot extension LSP language id (e.g. `{ '.ts': 'typescript' }`). |
29
- | `initializationOptions` | `null` | Static `initialize` options forwarded to the server. |
30
- | `configuration` | `null` | Static answer to every `workspace/configuration` item. |
31
- | `maxMessageBytes` | `16000000` | Largest single framed message accepted from the server. |
32
- | `maxStderrBytes` | `1000000` | Largest stderr tail retained for diagnostics. |
33
- | `maxDocumentBytes` | `4000000` | Largest source file this host will open. |
34
- | `shutdownTimeoutMs` | `5000` | Graceful `shutdown`/`exit` budget before escalation. |
35
- | `killGraceMs` | `2000` | Grace for request cancellation and for SIGTERM→SIGKILL escalation. |
51
+ | `command` | required | Executable to spawn — absolute, or resolved on the child PATH at load; launched without a shell |
52
+ | `extensionToLanguage` | required | Lowercase leading-dot extension LSP language id (e.g. `{ '.ts': 'typescript' }`) |
53
+ | `args` | `[]` | Arguments passed to the executable |
54
+ | `env` | `{}` | Extra env merged over the credential-scrubbed ambient env; variables matching `KEY`/`PASSWORD`/`SECRET`/`TOKEN` and all `DSH_*` names are not forwarded |
55
+ | `initializationOptions` | `null` | Static `initialize` options forwarded to the server |
56
+ | `configuration` | `null` | Static answer to every `workspace/configuration` item |
57
+ | `maxMessageBytes` | `16000000` | Largest single framed message accepted from the server |
58
+ | `maxStderrBytes` | `1000000` | Largest stderr tail retained for diagnostics |
59
+ | `maxDocumentBytes` | `4000000` | Largest source file this host opens |
60
+ | `shutdownTimeoutMs` | `5000` | Graceful `shutdown`/`exit` budget before escalation |
61
+ | `killGraceMs` | `2000` | Request-cancel and SIGTERM→SIGKILL escalation grace |
62
+
63
+ `servers` must contain at least one entry with non-empty ids; timer budgets must be positive integers within Node's timer range, and byte caps must be positive. The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-lsp-stdio) is the exhaustive source for every accepted field.
64
+
65
+ ### What a query does
66
+
67
+ On the first query for a workspace, the provider launches one server process for that workspace and keeps it pooled. Each query reads the current source through `ctx.fs`, opens it in the server (`textDocument/didOpen`), runs the requested operation, and closes it — so the server always sees current text and no document state persists between calls. Queries to one server and workspace run one at a time; different workspaces run in parallel. If the pooled process fails before or during a read-only query, the provider retries that query once on a fresh process.
68
+
69
+ ### Observable success and failures
70
+
71
+ A successful navigation returns normalized locations, and hover returns normalized text or a no-hover notice; empty results are successful no-result responses. The query fails when the server does not support the operation or the transient open/close synchronization (`LSP_UNSUPPORTED_OPERATION`), when the source is missing, non-regular, non-UTF-8, oversized, or outside the canonical workspace (rejected before the server starts), or when the server returns a malformed payload (`LSP_MALFORMED_RESPONSE`). A hard-killed harness leaves servers running until they exit on their own — graceful shutdown happens only through service disposal.
72
+
73
+ ### Security boundary
74
+
75
+ This provider trusts its configured server and adds no sandbox confinement; the server receives the filesystem and process authority of the mounted execution world. It rejects query sources that are missing, non-regular, non-UTF-8, oversized, or canonically outside the workspace before server startup. Result locations may point outside the workspace, but an external path can never become a query source. Mount filesystem and subprocess providers for the same execution world — a split-world composition is invalid.
76
+
77
+ -----
78
+
79
+ <a id="understand-the-implementation"></a>
80
+ ## Understand the implementation
81
+
82
+ <details>
83
+ <summary>Implementation internals — click to expand</summary>
36
84
 
37
- `servers` must contain at least one entry, and every id must be non-empty. Timer budgets must be positive integers no greater than Node's `2_147_483_647` ms timer limit. All executables resolve at load after credential scrubbing; a bad later entry prevents every provider from registering. Processes launch lazily on the first matching query.
85
+ This section explains the design decisions behind the provider and where the code realizes them; observable behavior is covered in [Use this package](#use-this-package).
38
86
 
39
- ## Protocol behavior
87
+ ### Design philosophy
40
88
 
41
- Initialization advertises `general.positionEncodings: ['utf-16']`, `workspace: { workspaceFolders: true, configuration: true }`, `textDocument.hover.contentFormat: ['markdown', 'plaintext']`, and `linkSupport: true` for definition and implementation, with no dynamic registration. The server's returned capabilities are authoritative: an unsupported operation, or synchronization without transient open/close, fails the query. An omitted server `positionEncoding` defaults to `utf-16`; any other value is a protocol error. The client answers `workspace/configuration` from static config, accepts lifecycle bookkeeping requests, and rejects `workspace/applyEdit` — it never applies edits or runs commands. Navigation maps `Location` directly and `LocationLink` from `targetUri` + `targetSelectionRange`; hover normalization takes valid `MarkupContent.value`, preserves string `MarkedString`s, renders language-tagged values as fenced code, and joins arrays with one blank line. Missing results, malformed ranges or positions, and malformed hover encodings fail as structured `LSP_MALFORMED_RESPONSE` errors.
89
+ - **Generic host, not a catalog.** Deployments configure commands and mappings explicitly; presets belong in `cordis.yml` overlays, not in this package.
90
+ - **Compatibility-first transient open.** Every query runs `didOpen` (version 1, full text) → request → `didClose`, so the server always sees current bytes and the first version needs no `didChange`, content cache, or document LRU.
91
+ - **Read before spawn.** The source is resolved, contained, and byte-bounded inside the workspace queue before any process is created, so a queued query sees current bytes when its turn starts and an invalid source cannot leave an idle process pooled.
92
+ - **One pooled process per canonical workspace.** Instances are single-flighted per `(server id, canonical workspace target)`; a transport failure retries the read-only query once on a fresh process after awaiting disposal.
93
+ - **Per-workspace serialization.** One abortable queue per workspace serializes source-read/open/query/close lifecycles; distinct workspaces run in parallel, and a cancellation that fails to stop a server terminates only that instance.
94
+ - **Bounded teardown.** Graceful `shutdown`/`exit` escalates through tree termination (process-group signaling on POSIX, `taskkill /T /F` on Windows); quiescence is confirmed by awaiting process-tree exit, not by the kill outcome.
95
+ - **Execution-world pairing.** Servers launch through `ctx.subprocess` with `processId: null` (another machine or PID namespace must not monitor the harness), sources read through `ctx.fs`, and no `fs/observed` event is emitted — only the LSP result is model-visible.
42
96
 
43
- ## Security boundary
97
+ ### Source map
44
98
 
45
- The provider trusts its configured server and claims no sandbox confinement. It delegates canonical identity, containment, regular-file streaming, UTF-8 validation, and file-URI encoding to `ctx.fs`; it rejects missing, non-regular, non-UTF-8, oversized, or canonically out-of-workspace query sources before server startup. Containment is evaluated before the stream opens and does not promise stable-handle identity across concurrent path replacement. Result locations may be external, but an external path cannot become a query source. A deployment must mount filesystem and subprocess providers for the same execution world; split-world composition is invalid.
99
+ | File | Role |
100
+ |---|---|
101
+ | [`src/index.ts`](src/index.ts) | Plugin entry: config schema, executable resolution, provider registration, process pooling |
102
+ | [`src/host.ts`](src/host.ts) | Workspace canonicalization and bounded source reads through `ctx.fs` |
103
+ | [`src/instance.ts`](src/instance.ts) | One server process: initialize handshake, serialized transient-open queries, bounded teardown |
104
+ | [`src/connection.ts`](src/connection.ts) | JSON-RPC endpoint: id correlation, outbound requests, inbound server requests, stderr cap |
105
+ | [`src/framing.ts`](src/framing.ts) | `Content-Length` framing and a bounded decoder |
106
+ | [`src/protocol.ts`](src/protocol.ts) | Wire-type subset: capabilities, locations, hover, text-document synchronization |
107
+ | [`src/translate.ts`](src/translate.ts) | Capability checks, UTF-16 negotiation, `Location`/`LocationLink`/hover normalization |
108
+ | [`src/abort.ts`](src/abort.ts) | Cancellation helpers fusing caller and disposal signals |
109
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; pools and queues are private state) |
46
110
 
111
+ ### Protocol behavior
112
+
113
+ Initialization advertises UTF-16 positions, workspace folders and configuration, markdown/plaintext hover, and link support for definition and implementation, with no dynamic registration; the server's returned capabilities are authoritative. An omitted server `positionEncoding` defaults to `utf-16`; any other value fails the query. The client answers `workspace/configuration` from static config, accepts lifecycle bookkeeping requests, and rejects `workspace/applyEdit` — it never applies edits or runs commands. Navigation maps `Location` directly and `LocationLink` from `targetUri` plus `targetSelectionRange`; hover normalization accepts `MarkupContent` and `MarkedString` shapes, preserves string values, renders language-tagged values as fenced code, and joins arrays with one blank line. Missing results, malformed ranges or positions, and malformed hover encodings fail as structured `LSP_MALFORMED_RESPONSE` errors.
114
+
115
+ </details>
116
+
117
+ -----
118
+
119
+ <a id="further-exploration"></a>
120
+ ## Further Exploration
121
+
122
+ Read these pages when the package-level contract is not enough. They move from the shared navigation model to the seam, the tool, and the decision evidence.
123
+
124
+ - [LSP navigation subsystem](../../../docs/subsystems/lsp.md) — operations, coordinates, requests and results, and `LspError` codes.
125
+ - [LSP capability seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md) — design rationale, alternatives, and deliberately deferred API.
126
+ - [dsh-lsp](../lsp/README.md) — the seam this provider registers against.
127
+ - [dsh-tool-lsp](../tool-lsp/README.md) — the model-facing tool over the seam.
128
+ - [lsp group map](../README.md) — the three-package family and its related documentation.
129
+
130
+ -----
131
+
132
+ <a id="model-experience"></a>
47
133
  ## Model Experience
48
134
 
49
- Indirectly, through `dsh-tool-lsp`, which surfaces this provider's normalized results; this host contributes no prompt or schema itself.
135
+ Indirectly, through `dsh-tool-lsp`, which surfaces this provider's normalized results while this host contributes no prompt or schema itself.
50
136
 
51
137
  #### KV Cache effect
52
138
 
@@ -54,7 +140,22 @@ No direct invalidation; `dsh-tool-lsp` owns request-prefix changes.
54
140
 
55
141
  ## Known Limitations and Deferred Work
56
142
 
57
- - **No confinement policy** — this package trusts the configured server and does not sandbox its process; a restricted deployment must supply appropriate process/filesystem providers or a same-world sandbox wrapper.
58
- - **Transient-open compatibility floor** — servers whose synchronization omits open/close (or advertise `None`) are unsupported even if closed-document queries would work; the pinned TypeScript e2e establishes one compatibility floor, not a cross-language claim.
59
- - **Per-server/workspace serialization latency** — parallel agents sharing one server and workspace queue behind one process; long-lived workspace processes consume memory until disposal.
143
+ <a id="known-limitations-and-deferred-work"></a>
144
+
145
+
146
+ These limits define when the provider is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
147
+
148
+ - **No confinement policy** — this package trusts the configured server and does not sandbox its process; a restricted deployment must supply appropriate process and filesystem providers or a same-world sandbox wrapper.
149
+ - **Transient-open compatibility floor** — servers whose synchronization omits open/close (or advertises `None`) are unsupported even if closed-document queries would work; the pinned TypeScript e2e establishes one compatibility floor, not a cross-language claim.
150
+ - **Per-server and per-workspace serialization latency** — parallel agents sharing one server and workspace queue behind one process; long-lived workspace processes consume memory until disposal.
60
151
  - **A hard-killed harness orphans language servers** — `initialize.processId: null` removes server-side client-PID monitoring, so servers are cleaned only by graceful service disposal; a SIGKILL'd harness leaves them running until they exit on their own.
152
+
153
+ <a id="dev-note"></a>
154
+ ### Dev Note
155
+
156
+ <details>
157
+ <summary>Working context for maintainers — click to expand</summary>
158
+
159
+ None.
160
+
161
+ </details>
package/README.zh.md CHANGED
@@ -1,60 +1,161 @@
1
+ ---
2
+ description: "ctx.lsp 的 stdio 语言服务器提供方:配置好的服务器命令、扩展名映射与有边界的临时打开查询,供组合本地代码导航的用户与维护者阅读。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-lsp-stdio
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- `ctx.lsp` 的**通用 stdio 语言服务器后端**。一个插件实例接受一张命名服务器表,并逐配置项注册一个隔离的提供方。它通过 `ctx.fs` 读取,并通过 `ctx.subprocess` 启动,因此服务器与源文件始终位于已挂载的执行世界中。这是通用主机,而不是语言服务器目录或安装器:部署需要显式配置命令与映射,预设应放在 `cordis.yml` overlay 中。
10
+ ## 概述
11
+
12
+ `dsh-lsp-stdio` 把配置好的本地语言服务器命令变成 `ctx.lsp` 上的提供方:给它一张服务器命令与扩展名到语言的映射表,agent 就能针对这些语言的文件获得由真实语言服务器服务的语义代码导航——定义、引用、实现与悬停。一个插件实例针对每个配置的服务器注册一个隔离的提供方;每个提供方按工作区惰性启动一个服务器进程,并在查询时临时打开文档,因此查询之间不会累积任何文档状态。服务器与源文件始终位于已挂载的文件系统与子进程执行世界中。它是通用主机,而不是语言服务器目录或安装器——部署需要显式配置命令。本包信任所配置的服务器,自身不提供任何沙箱。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
6
22
 
7
- Namespace 插件(`name`/`inject`/`Config`/`apply`,无默认导出)。
23
+ -----
8
24
 
9
- ## 功能
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
10
27
 
11
- - 在注册前解析每项服务器局部设置;无效映射或注册冲突会回滚较早配置项,因此加载失败不会留下提供方路由。
12
- - 每个 `(server id, canonical workspace target)` 惰性 single-flight 一个服务器进程。服务器仍存活时返回的错误不会触发重试;如果选中的池化传输在只读查询之前或期间发生故障,提供方会等待其 dispose(资源释放)完成,并在新进程上重试该查询一次。
13
- - 每次查询都使用兼容性优先的**临时打开**序列:通过 `ctx.fs` 流式读取源文件,同时解析并限制其字节数;随后执行 `textDocument/didOpen`(版本 1、完整文本)、所请求操作,再执行位于 `finally` 中的 `textDocument/didClose`。写入 `didOpen` 失败或取消时,会在池复用该实例前将其终止。文档在每次调用后关闭,因此第一版不需要 `didChange`、内容 cache 或文档 LRU。
14
- - 通过一条逐 Workspace、可中止的队列,串行执行每个源读取/打开/查询/关闭生命周期,因此排队调用只会在轮到自身时读取当前源;不同 Workspace 并行运行。提供方 dispose 会中止文件系统与协议工作,等待尚未进入队列的 Workspace 查找完成,随后排空每条队列与每个服务器。
15
- - 协议 shutdown 失败后,经由子进程 seam 终止服务器后代树(POSIX 进程组信号;Windows `taskkill /T /F`)。树终止的投递结果与所有进程组信号一样被就地吸收,不向外抛出(投递与服务器退出存在竞态);服务器是否完全停稳,由句柄的进程树存活等待确认,而非由这次终止自身的结果确认。
16
- - 通过 `ctx.subprocess` 解析服务器可执行文件、cwd、进程和协议流;`initialize.processId` 为 `null`,因为另一台机器或 PID namespace 不得监视 harness 进程。
17
- - 使用 `ctx.fs` 提供的规范化包含关系、文件 URI 与流式文本验证,但不发出 `fs/observed`:只有 LSP 结果对模型可见,因此查询不满足先读后写策略。
28
+ 当部署拥有本地语言服务器——例如 `typescript-language-server`——并希望 harness 通过它们导航代码时,挂载此提供方。它需要描述同一执行世界的文件系统与子进程提供方,以及 `dsh-lsp` seam;若要向模型开放,还需要 `dsh-tool-lsp`。
18
29
 
19
- ## 配置
30
+ ### 最小配置
20
31
 
21
- `servers` 记录的 key 是在 `ctx.lsp` 上保留的稳定提供方 id;每个值具有以下形状:
32
+ `servers` 记录把每个稳定的提供方 id 映射到一条服务器命令。提供方会在清理 credential 后于加载时解析每个可执行文件,因此一个坏配置项会阻止所有提供方注册;进程在第一次匹配查询时惰性启动。
22
33
 
23
- | 服务器 key | 默认值 | 含义 |
34
+ ```yaml
35
+ - name: '@deepseek-ai/dsh-fs-local'
36
+ - name: '@deepseek-ai/dsh-subprocess-local'
37
+ - name: '@deepseek-ai/dsh-lsp'
38
+ - name: '@deepseek-ai/dsh-lsp-stdio'
39
+ config:
40
+ servers:
41
+ typescript:
42
+ command: typescript-language-server
43
+ args: ['--stdio']
44
+ extensionToLanguage:
45
+ '.ts': typescript
46
+ - name: '@deepseek-ai/dsh-tool-lsp'
47
+ ```
48
+
49
+ | 字段 | 默认值 | 含义 |
24
50
  |---|---|---|
25
- | `command` | (必填) | 要 spawn 的可执行文件:绝对路径,或在加载时从子进程 PATH 解析。不使用 shell 启动。 |
26
- | `args` | `[]` | 传给可执行文件的参数。 |
27
- | `env` | `{}` | 合并到已清理 credential 的环境之上的额外 env(匹配 `KEY`/`PASSWORD`/`SECRET`/`TOKEN` 的变量不会转发);显式 `DSH_*` 条目在 seam 清除环境中同名值之后合并。 |
28
- | `extensionToLanguage` | (必填) | 小写、以点开头的扩展名 LSP language id(例如 `{ '.ts': 'typescript' }`)。 |
29
- | `initializationOptions` | `null` | 转发给服务器的静态 `initialize` 选项。 |
30
- | `configuration` | `null` | 每个 `workspace/configuration` 配置项的静态答案。 |
31
- | `maxMessageBytes` | `16000000` | 从服务器接受的单条 framed 消息最大大小。 |
32
- | `maxStderrBytes` | `1000000` | 为诊断保留的 stderr 尾部最大大小。 |
33
- | `maxDocumentBytes` | `4000000` | 该主机可打开的源文件大小上限。 |
34
- | `shutdownTimeoutMs` | `5000` | 升级前用于优雅 `shutdown`/`exit` 的预算。 |
35
- | `killGraceMs` | `2000` | 请求取消及 SIGTERM→SIGKILL 升级的宽限期。 |
51
+ | `command` | 必填 | 要 spawn 的可执行文件——绝对路径,或在加载时从子进程 PATH 解析;不使用 shell 启动 |
52
+ | `extensionToLanguage` | 必填 | 小写、以点开头的扩展名 → LSP language id(例如 `{ '.ts': 'typescript' }`) |
53
+ | `args` | `[]` | 传给可执行文件的参数 |
54
+ | `env` | `{}` | 合并到已清理 credential 的环境之上的额外 env;匹配 `KEY`/`PASSWORD`/`SECRET`/`TOKEN` 的变量以及所有 `DSH_*` 名称不会被转发 |
55
+ | `initializationOptions` | `null` | 转发给服务器的静态 `initialize` 选项 |
56
+ | `configuration` | `null` | 每个 `workspace/configuration` 配置项的静态答案 |
57
+ | `maxMessageBytes` | `16000000` | 从服务器接受的单条 framed 消息最大大小 |
58
+ | `maxStderrBytes` | `1000000` | 为诊断保留的 stderr 尾部最大大小 |
59
+ | `maxDocumentBytes` | `4000000` | 该主机可打开的源文件大小上限 |
60
+ | `shutdownTimeoutMs` | `5000` | 升级前用于优雅 `shutdown`/`exit` 的预算 |
61
+ | `killGraceMs` | `2000` | 请求取消及 SIGTERM→SIGKILL 升级的宽限期 |
62
+
63
+ `servers` 必须至少包含一个配置项,每个 id 都必须非空;定时器预算必须是 Node 定时器范围内的正整数,字节上限必须为正。生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-lsp-stdio)是每个受支持字段的穷尽式真源。
64
+
65
+ ### 查询做什么
66
+
67
+ 首次查询某个工作区时,提供方会为该工作区启动一个服务器进程并放入池中。每次查询通过 `ctx.fs` 读取当前源文件,在服务器中打开它(`textDocument/didOpen`),执行所请求的操作,然后关闭——因此服务器始终看到当前文本,调用之间不会残留任何文档状态。同一服务器与工作区的查询一次只执行一个;不同工作区并行运行。如果池化进程在只读查询之前或期间发生故障,提供方会在新进程上重试该查询一次。
68
+
69
+ ### 可观察的成功与失败
70
+
71
+ 成功的导航返回规范化位置,悬停返回规范化文本或无可悬停提示;空结果是成功的无结果响应。当服务器不支持该操作或临时打开/关闭同步(`LSP_UNSUPPORTED_OPERATION`)、源文件缺失、非普通文件、非 UTF-8、过大或位于规范工作区之外(在服务器启动前被拒绝),或服务器返回格式错误的载荷(`LSP_MALFORMED_RESPONSE`)时,查询会失败。被强制杀死的 harness 会让服务器继续运行直到自行退出——优雅关闭只发生在服务释放时。
72
+
73
+ ### 安全边界
74
+
75
+ 本提供方信任所配置的服务器,不提供任何沙箱隔离;服务器获得的是已挂载执行世界的文件系统与进程权限。它会在服务器启动前拒绝缺失、非普通文件、非 UTF-8、过大或规范化后位于工作区之外的查询源。结果位置可以指向工作区外部,但外部路径永远不能成为查询源。为同一执行世界挂载文件系统与子进程提供方——分裂世界组合无效。
76
+
77
+ -----
78
+
79
+ <a id="understand-the-implementation"></a>
80
+ ## 理解实现
81
+
82
+ <details>
83
+ <summary>实现细节——点击展开</summary>
36
84
 
37
- `servers` 必须至少包含一个配置项,每个 id 都必须非空。定时器预算必须是正整数,且不超过 Node 的 `2_147_483_647` ms 定时器上限。所有可执行文件都会在清理 credential 后于加载时解析;后面的坏配置项会阻止所有提供方注册。进程在第一次匹配查询时惰性启动。
85
+ 本节解释提供方背后的设计决策并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
38
86
 
39
- ## 协议行为
87
+ ### 设计理念
40
88
 
41
- 初始化会声明 `general.positionEncodings: ['utf-16']`、`workspace: { workspaceFolders: true, configuration: true }`、`textDocument.hover.contentFormat: ['markdown', 'plaintext']`,以及定义与实现使用的 `linkSupport: true`,且不进行动态注册。服务器返回的能力具有最终决定权:不受支持的操作,或缺少临时打开/关闭的同步方式,会使查询失败。服务器省略 `positionEncoding` 时默认为 `utf-16`;其他值都属于协议错误。客户端通过静态配置回答 `workspace/configuration`,接受生命周期记账请求,并拒绝 `workspace/applyEdit`:它绝不应用编辑或运行命令。导航直接映射 `Location`,并从 `LocationLink` 的 `targetUri` + `targetSelectionRange` 映射;hover 规范化会取得有效的 `MarkupContent.value`,保留 string `MarkedString`,把带 language tag 的值渲染为围栏代码,并用一个空行连接数组。缺失结果、格式错误的范围或位置,以及格式错误的 hover 编码,都会以结构化 `LSP_MALFORMED_RESPONSE` 错误的形式失败。
89
+ - **通用主机,不是目录。** 部署显式配置命令与映射;预设应放在 `cordis.yml` overlay 中,而不是本包内。
90
+ - **兼容性优先的临时打开。** 每次查询都执行 `didOpen`(版本 1、完整文本)→ 请求 → `didClose`,因此服务器始终看到当前字节,第一版不需要 `didChange`、内容 cache 或文档 LRU。
91
+ - **先读后启动。** 源文件在工作区队列内先完成解析、包含关系检查与字节限制,然后才创建任何进程,因此排队查询只会在轮到自身时读取当前字节,无效源文件也不会留下空闲的池化进程。
92
+ - **每个规范工作区一个池化进程。** 实例按 `(server id, canonical workspace target)` 进行 single-flight;传输故障会在等待释放完成后于新进程上重试一次该只读查询。
93
+ - **逐工作区串行化。** 每个工作区一条可中止队列,串行执行源读取/打开/查询/关闭生命周期;不同工作区并行运行,无法停止服务器的取消只会终止该实例。
94
+ - **有边界的释放。** 优雅 `shutdown`/`exit` 升级为进程树终止(POSIX 进程组信号,Windows `taskkill /T /F`);是否完全停稳由等待进程树退出确认,而非由终止操作自身的结果确认。
95
+ - **执行世界配对。** 服务器通过 `ctx.subprocess` 启动,`processId: null`(另一台机器或 PID namespace 不得监视 harness);源文件通过 `ctx.fs` 读取;不发出 `fs/observed` 事件——只有 LSP 结果对模型可见。
42
96
 
43
- ## 安全边界
97
+ ### 源码地图
44
98
 
45
- 提供方信任其配置的服务器,不提供任何沙箱隔离。它把规范化身份、包含关系、普通文件流式读取、UTF-8 验证和文件 URI 编码委托给 `ctx.fs`;并在服务器启动前拒绝缺失、非普通文件、非 UTF-8、过大,或规范化后位于 Workspace 外部的查询源。包含关系在打开流之前评估,不承诺在并发路径替换期间保持稳定句柄身份。结果位置可以在外部,但外部路径不能成为查询源。部署必须挂载描述同一执行世界的文件系统与进程管理提供方;分裂世界组合无效。
99
+ | 文件 | 职责 |
100
+ |---|---|
101
+ | [`src/index.ts`](src/index.ts) | 插件入口:config schema、可执行文件解析、提供方注册、进程池 |
102
+ | [`src/host.ts`](src/host.ts) | 通过 `ctx.fs` 完成工作区规范化与有边界的源读取 |
103
+ | [`src/instance.ts`](src/instance.ts) | 单个服务器进程:initialize 握手、串行化临时打开查询、有边界的释放 |
104
+ | [`src/connection.ts`](src/connection.ts) | JSON-RPC 端点:id 关联、出站请求、入站服务器请求、stderr 上限 |
105
+ | [`src/framing.ts`](src/framing.ts) | `Content-Length` 分帧与有边界的解码器 |
106
+ | [`src/protocol.ts`](src/protocol.ts) | 协议类型子集:能力、位置、悬停、文本文档同步 |
107
+ | [`src/translate.ts`](src/translate.ts) | 能力检查、UTF-16 协商、`Location`/`LocationLink`/hover 规范化 |
108
+ | [`src/abort.ts`](src/abort.ts) | 融合调用方与释放信号的取消辅助 |
109
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;进程池与队列是私有状态) |
46
110
 
111
+ ### 协议行为
112
+
113
+ 初始化会声明 UTF-16 位置、工作区文件夹与配置、markdown/plaintext hover,以及定义与实现使用的 link 支持,且不进行动态注册;服务器返回的能力具有最终决定权。服务器省略 `positionEncoding` 时默认为 `utf-16`;其他任何值都会使查询失败。客户端通过静态配置回答 `workspace/configuration`,接受生命周期记账请求,并拒绝 `workspace/applyEdit`——它绝不应用编辑或运行命令。导航直接映射 `Location`,并从 `LocationLink` 的 `targetUri` + `targetSelectionRange` 映射;hover 规范化接受 `MarkupContent` 与 `MarkedString` 形状,保留字符串值,把带 language tag 的值渲染为围栏代码,并用一个空行连接数组。缺失结果、格式错误的范围或位置,以及格式错误的 hover 编码,都会以结构化 `LSP_MALFORMED_RESPONSE` 错误失败。
114
+
115
+ </details>
116
+
117
+ -----
118
+
119
+ <a id="further-exploration"></a>
120
+ ## 进一步探索
121
+
122
+ 当包级约定不够用时阅读以下页面。它们从共享的导航模型逐步进入 seam、工具与决策证据。
123
+
124
+ - [LSP 导航子系统](../../../docs/subsystems/lsp.zh.md)——操作、坐标、请求与结果,以及 `LspError` code。
125
+ - [LSP 能力 seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.zh.md)——设计原理、备选方案与刻意推迟的 API。
126
+ - [dsh-lsp](../lsp/README.zh.md)——本提供方注册到的 seam。
127
+ - [dsh-tool-lsp](../tool-lsp/README.zh.md)——基于该 seam 的面向模型工具。
128
+ - [lsp 组地图](../README.zh.md)——三个包的家族及其相关文档。
129
+
130
+ -----
131
+
132
+ <a id="model-experience"></a>
47
133
  ## 模型体验
48
134
 
49
- 通过 `dsh-tool-lsp` 间接影响;该工具呈现此提供方的规范化结果,该主机自身不贡献提示词或 schema。
135
+ 通过 `dsh-tool-lsp` 间接影响;该工具呈现此提供方的规范化结果,本主机自身不贡献提示词或 schema。
50
136
 
51
137
  #### KV Cache 影响
52
138
 
53
139
  不会直接失效;请求前缀变更由 `dsh-tool-lsp` 负责。
54
140
 
55
- ## 已知限制与暂缓事项
141
+ ## 已知限制与延期工作
142
+
143
+ <a id="known-limitations-and-deferred-work"></a>
144
+
145
+
146
+ 这些限制说明本提供方何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
147
+
148
+ - **不提供隔离策略**——本包信任所配置的服务器,不对其进程实施沙箱;受限部署必须提供适当的进程与文件系统提供方,或使用同一执行世界的沙箱包装层。
149
+ - **临时打开兼容性下限**——同步能力省略打开/关闭(或声明 `None`)的服务器不受支持,即使关闭文档查询能够工作;固定的 TypeScript e2e 只建立一项兼容性下限,不代表跨语言承诺。
150
+ - **逐服务器与逐工作区串行化延迟**——共享同一个服务器与工作区的并行 agent 会在一个进程后排队;长生命周期工作区进程会占用内存直到释放。
151
+ - **被强制杀死的 harness 会遗留语言服务器**——`initialize.processId: null` 取消了服务器侧的客户端 PID 监视,因此服务器只能由服务的优雅释放清理;被 SIGKILL 的 harness 会让它们继续运行,直到自行退出。
152
+
153
+ <a id="dev-note"></a>
154
+ ### 开发备注
155
+
156
+ <details>
157
+ <summary>维护者的工作上下文——点击展开</summary>
158
+
159
+ 无。
56
160
 
57
- - **不提供隔离策略**:本包(package)信任所配置的服务器,不对其进程实施沙箱;受限部署必须提供适当的进程/文件系统提供方,或使用同一执行世界的沙箱包装层。
58
- - **临时打开兼容性下限**:同步能力省略打开/关闭(或声明 `None`)的服务器不受支持,即使关闭文档查询能够工作;固定的 TypeScript e2e 只建立一项兼容性下限,不代表跨语言承诺。
59
- - **逐服务器/Workspace 串行化延迟**:共享同一个服务器与 Workspace 的并行 agent(智能体)会在一个进程后排队;长生命周期 Workspace 进程会占用内存直到 dispose。
60
- - **被强制杀死的 harness 会遗留语言服务器**:`initialize.processId: null` 取消了服务器侧的客户端 PID 监视,因此服务器只能由服务的优雅 dispose 清理;被 SIGKILL 的 harness 会让它们继续运行,直到自行退出。
161
+ </details>
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import z from "@deepseek-ai/schemastery";
2
2
  import { LspError, LspProviderId } from "@deepseek-ai/dsh-lsp";
3
3
  import { MAX_TIMER_DELAY_MS, deadline, timeoutOf } from "@deepseek-ai/dsh-timeout";
4
4
  import { Buffer as Buffer$1 } from "node:buffer";
5
- import { assertNever } from "@deepseek-ai/dsh-llm";
5
+ import { assertNever } from "@deepseek-ai/dsh-util-values";
6
6
  //#region lib/types/abort.js
7
7
  /**
8
8
  * Shared cancellation helpers for the local LSP provider's host-I/O, queue, and protocol phases.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-lsp-stdio",
3
3
  "description": "Generic stdio language-server provider for the DeepSeek Harness LSP capability seam (ctx.lsp) — spawns configured servers, translates JSON-RPC, and serves transient-open goToDefinition/findReferences/goToImplementation/hover queries in the host filesystem namespace",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,30 +32,31 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-brand": "^0.1.1-rc.2",
36
- "@deepseek-ai/dsh-fs": "^0.1.1-rc.2",
37
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
38
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
39
- "@deepseek-ai/dsh-lsp": "^0.1.1-rc.2",
40
- "@deepseek-ai/dsh-subprocess": "^0.1.1-rc.2",
41
- "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
42
- "@deepseek-ai/cordis": "^4.0.1"
35
+ "@deepseek-ai/cordis": "^4.0.2",
36
+ "@deepseek-ai/dsh-fs": "^0.1.2-alpha.2",
37
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
38
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
39
+ "@deepseek-ai/dsh-lsp": "^0.1.2-alpha.2",
40
+ "@deepseek-ai/dsh-brand": "^0.1.2-alpha.2",
41
+ "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.2",
42
+ "@deepseek-ai/dsh-subprocess": "^0.1.2-alpha.2"
43
43
  },
44
44
  "dependencies": {
45
- "@deepseek-ai/schemastery": "^3.18.1"
45
+ "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.2",
46
+ "@deepseek-ai/schemastery": "^3.18.2"
46
47
  },
47
48
  "devDependencies": {
48
49
  "typescript": "^6.0.3",
49
50
  "typescript-language-server": "^5.0.0",
50
- "@deepseek-ai/dsh-brand": "^0.1.1-rc.2",
51
- "@deepseek-ai/dsh-fs": "^0.1.1-rc.2",
52
- "@deepseek-ai/dsh-fs-local": "^0.1.1-rc.2",
53
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
54
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
55
- "@deepseek-ai/dsh-lsp": "^0.1.1-rc.2",
56
- "@deepseek-ai/dsh-subprocess-local": "^0.1.1-rc.2",
57
- "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
58
- "@deepseek-ai/cordis": "^4.0.1",
59
- "@deepseek-ai/dsh-subprocess": "^0.1.1-rc.2"
51
+ "@deepseek-ai/cordis": "^4.0.2",
52
+ "@deepseek-ai/dsh-brand": "^0.1.2-alpha.2",
53
+ "@deepseek-ai/dsh-fs": "^0.1.2-alpha.2",
54
+ "@deepseek-ai/dsh-fs-local": "^0.1.2-alpha.2",
55
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
56
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
57
+ "@deepseek-ai/dsh-lsp": "^0.1.2-alpha.2",
58
+ "@deepseek-ai/dsh-subprocess": "^0.1.2-alpha.2",
59
+ "@deepseek-ai/dsh-subprocess-local": "^0.1.2-alpha.2",
60
+ "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.2"
60
61
  }
61
62
  }