@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 +2 -2
- package/README.md +134 -33
- package/README.zh.md +136 -35
- package/lib/index.js +1 -1
- package/package.json +21 -20
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:
|
|
6
|
-
README.zh.md:
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
-----
|
|
8
24
|
|
|
9
|
-
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
10
27
|
|
|
11
|
-
|
|
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
|
-
|
|
30
|
+
### Minimal configuration
|
|
20
31
|
|
|
21
|
-
The `servers` record
|
|
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
|
-
|
|
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` |
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
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
|
|
34
|
-
| `shutdownTimeoutMs` | `5000` | Graceful `shutdown`/`exit` budget before escalation
|
|
35
|
-
| `killGraceMs` | `2000` |
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
### Design philosophy
|
|
40
88
|
|
|
41
|
-
|
|
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
|
-
|
|
97
|
+
### Source map
|
|
44
98
|
|
|
45
|
-
|
|
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
|
|
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
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
32
|
+
`servers` 记录把每个稳定的提供方 id 映射到一条服务器命令。提供方会在清理 credential 后于加载时解析每个可执行文件,因此一个坏配置项会阻止所有提供方注册;进程在第一次匹配查询时惰性启动。
|
|
22
33
|
|
|
23
|
-
|
|
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` |
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
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
|
-
|
|
85
|
+
本节解释提供方背后的设计决策并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
38
86
|
|
|
39
|
-
|
|
87
|
+
### 设计理念
|
|
40
88
|
|
|
41
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
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-
|
|
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.
|
|
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/
|
|
36
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
37
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
38
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
39
|
-
"@deepseek-ai/dsh-lsp": "^0.1.
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
41
|
-
"@deepseek-ai/dsh-timeout": "^0.1.
|
|
42
|
-
"@deepseek-ai/
|
|
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/
|
|
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/
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-fs
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
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
|
}
|