@deepseek-ai/dsh-lsp-stdio 0.1.2-alpha.5 → 0.1.3-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: 8cb7fbcc761b72dac7c291040c390de7ebf49b99
6
- README.zh.md: 4186e8c1b66b0f2e66ace8f82c76fce58cf00a49
5
+ README.md: 61137e544ea7c9ba0d2458fc99a43eaa1c03aafb
6
+ README.zh.md: 39c327346c90d7efeee895035e35a76b17354cf5
package/README.md CHANGED
@@ -91,7 +91,7 @@ This section explains the design decisions behind the provider and where the cod
91
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
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
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.
94
+ - **Bounded teardown.** Graceful `shutdown`/`exit` escalates through the subprocess provider's managed-range termination procedure; quiescence is confirmed by awaiting that whole range, not by the termination request's outcome.
95
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.
96
96
 
97
97
  ### Source map
@@ -119,10 +119,9 @@ Initialization advertises UTF-16 positions, workspace folders and configuration,
119
119
  <a id="further-exploration"></a>
120
120
  ## Further Exploration
121
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.
122
+ Read these pages when the package-level contract is not enough. They move from the shared navigation model to the seam and the tool.
123
123
 
124
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
125
  - [dsh-lsp](../lsp/README.md) — the seam this provider registers against.
127
126
  - [dsh-tool-lsp](../tool-lsp/README.md) — the model-facing tool over the seam.
128
127
  - [lsp group map](../README.md) — the three-package family and its related documentation.
package/README.zh.md CHANGED
@@ -91,7 +91,7 @@ kind: "package-reference"
91
91
  - **先读后启动。** 源文件在工作区队列内先完成解析、包含关系检查与字节限制,然后才创建任何进程,因此排队查询只会在轮到自身时读取当前字节,无效源文件也不会留下空闲的池化进程。
92
92
  - **每个规范工作区一个池化进程。** 实例按 `(server id, canonical workspace target)` 进行 single-flight;传输故障会在等待释放完成后于新进程上重试一次该只读查询。
93
93
  - **逐工作区串行化。** 每个工作区一条可中止队列,串行执行源读取/打开/查询/关闭生命周期;不同工作区并行运行,无法停止服务器的取消只会终止该实例。
94
- - **有边界的释放。** 优雅 `shutdown`/`exit` 升级为进程树终止(POSIX 进程组信号,Windows `taskkill /T /F`);是否完全停稳由等待进程树退出确认,而非由终止操作自身的结果确认。
94
+ - **有边界的释放。** 优雅 `shutdown`/`exit` 会升级到 subprocess 提供方的 managed-range 终止流程;是否完全停稳由等待整个 range 确认,而非由终止请求自身的结果确认。
95
95
  - **执行世界配对。** 服务器通过 `ctx.subprocess` 启动,`processId: null`(另一台机器或 PID namespace 不得监视 harness);源文件通过 `ctx.fs` 读取;不发出 `fs/observed` 事件——只有 LSP 结果对模型可见。
96
96
 
97
97
  ### 源码地图
@@ -119,10 +119,9 @@ kind: "package-reference"
119
119
  <a id="further-exploration"></a>
120
120
  ## 进一步探索
121
121
 
122
- 当包级约定不够用时阅读以下页面。它们从共享的导航模型逐步进入 seam、工具与决策证据。
122
+ 当包级约定不够用时阅读以下页面。它们从共享的导航模型逐步进入 seam 与工具。
123
123
 
124
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
125
  - [dsh-lsp](../lsp/README.zh.md)——本提供方注册到的 seam。
127
126
  - [dsh-tool-lsp](../tool-lsp/README.zh.md)——基于该 seam 的面向模型工具。
128
127
  - [lsp 组地图](../README.zh.md)——三个包的家族及其相关文档。
package/lib/index.js CHANGED
@@ -229,8 +229,8 @@ function parseContentLength(headerText) {
229
229
  * server→client requests: it answers `workspace/configuration` from static
230
230
  * config, and rejects `workspace/applyEdit` (this host never applies edits or
231
231
  * runs commands). It caps stderr, surfaces framing/decoder failures as a
232
- * fatal close, and exposes tree-scoped termination through the handle so the
233
- * instance owns teardown; group/tree mechanics live in the subprocess
232
+ * fatal close, and exposes managed-range termination through the handle so the
233
+ * instance owns teardown; platform mechanics live in the subprocess
234
234
  * Service Provider.
235
235
  * @module @deepseek-ai/dsh-lsp-stdio/connection
236
236
  */
@@ -293,10 +293,6 @@ var LspConnection = class {
293
293
  this.onStdout(chunk);
294
294
  });
295
295
  }
296
- /** The child's pid, or `-1` when the spawn produced no pid (so signalling is a no-op). */
297
- get pid() {
298
- return this.handle.pid;
299
- }
300
296
  /** The retained stderr tail, for diagnostics on a failed server. */
301
297
  get stderrTail() {
302
298
  /* v8 ignore next -- the collect disposition always exposes a stderr reader; defensive. */
@@ -372,16 +368,16 @@ var LspConnection = class {
372
368
  peekNextId() {
373
369
  return this.nextId;
374
370
  }
375
- /** Terminate the server's process tree (the seam's SIGTERM→grace→SIGKILL escalation; idempotent). */
371
+ /** Terminate the server's provider-managed range (idempotent). */
376
372
  terminate() {
377
373
  this.handle.terminate();
378
374
  }
379
375
  /**
380
- * Wait until the owned process tree has exited.
376
+ * Wait until the owned managed range is empty.
381
377
  * @param signal - optional bound for the wait.
382
- * @returns `true` when the tree exited, or `false` when the signal aborted first.
378
+ * @returns `true` when the range is empty, or `false` when the signal aborted first.
383
379
  */
384
- async waitForProcessTreeExit(signal) {
380
+ async waitForManagedRangeExit(signal) {
385
381
  return await this.handle.waitForExit(signal);
386
382
  }
387
383
  onStdout(chunk) {
@@ -747,7 +743,7 @@ var LspInstance = class {
747
743
  */
748
744
  query(request, source, signal) {
749
745
  const run = abortable(this.queue, signal).then(() => this.runQuery(request, source, signal)).catch(async (error) => {
750
- if (this.isTransportFailure(error)) await this.startTeardown();
746
+ if (this.isTransportFailure(error)) await this.awaitTeardownAttempt();
751
747
  throw error;
752
748
  });
753
749
  this.queue = this.queue.then(() => run).then(() => void 0, () => void 0);
@@ -775,7 +771,7 @@ var LspInstance = class {
775
771
  try {
776
772
  await abortable(this.ready, signal);
777
773
  } catch (error) {
778
- if (!this.dead) await this.startTeardown();
774
+ if (!this.dead) await this.awaitTeardownAttempt();
779
775
  throw error;
780
776
  }
781
777
  const capabilities = this.capabilities;
@@ -796,7 +792,7 @@ var LspInstance = class {
796
792
  text: source.text
797
793
  } }), signal);
798
794
  } catch (error) {
799
- await this.startTeardown();
795
+ await this.awaitTeardownAttempt();
800
796
  throw error;
801
797
  }
802
798
  opened = true;
@@ -805,10 +801,8 @@ var LspInstance = class {
805
801
  } finally {
806
802
  if (opened && !this.dead) try {
807
803
  await this.connection.notify("textDocument/didClose", { textDocument: { uri } });
808
- } catch {
809
- try {
810
- await this.startTeardown();
811
- } catch {}
804
+ } catch (_closeFailure) {
805
+ await this.awaitTeardownAttempt();
812
806
  }
813
807
  }
814
808
  }
@@ -848,7 +842,7 @@ var LspInstance = class {
848
842
  grace.signal.addEventListener("abort", () => {
849
843
  resolve(false);
850
844
  }, { once: true });
851
- })])) await this.startTeardown();
845
+ })])) await this.awaitTeardownAttempt();
852
846
  } finally {
853
847
  grace[Symbol.dispose]();
854
848
  }
@@ -890,6 +884,12 @@ var LspInstance = class {
890
884
  this.teardownPromise ??= this.tearDown();
891
885
  return this.teardownPromise;
892
886
  }
887
+ /** Await teardown while leaving its memoized failure for provider-level finalization. */
888
+ async awaitTeardownAttempt() {
889
+ try {
890
+ await this.startTeardown();
891
+ } catch (_teardownFailure) {}
892
+ }
893
893
  async tearDown() {
894
894
  const shutdownDeadline = deadline(void 0, this.spec.shutdownTimeoutMs, "LSP_SHUTDOWN");
895
895
  try {
@@ -906,14 +906,13 @@ var LspInstance = class {
906
906
  await abortable(this.connection.closed, signal);
907
907
  }
908
908
  /**
909
- * Terminate the tree (the seam escalates SIGTERM→`killGraceMs`→SIGKILL),
910
- * then await leader and helper exit. The awaits are unbounded on purpose:
911
- * the seam's escalation already committed to SIGKILL, so quiescence — not
912
- * another timer — is the postcondition disposal owes its callers.
909
+ * Terminate the provider-managed range, then await the direct server result
910
+ * and whole-range quiescence. The awaits are unbounded on purpose because
911
+ * quiescence, not another timer, is the postcondition disposal owes callers.
913
912
  */
914
913
  async forceTerminate() {
915
914
  this.connection.terminate();
916
- await Promise.all([this.connection.closed, this.connection.waitForProcessTreeExit()]);
915
+ await Promise.all([this.connection.closed, this.connection.waitForManagedRangeExit()]);
917
916
  }
918
917
  };
919
918
  /** Server→client request methods this host acknowledges with an empty result (no dynamic registration). */
@@ -1109,20 +1108,23 @@ var LocalLspProvider = class {
1109
1108
  const source = await readHostSource(this.fs, request.filePath, workspace, this.config.maxDocumentBytes, querySignal);
1110
1109
  this.assertActive(querySignal);
1111
1110
  let instance = this.instanceFor(workspaceKey, workspace);
1112
- try {
1113
- return await instance.query(request, source, querySignal);
1114
- } catch (error) {
1115
- if (!instance.isTransportFailure(error)) throw error;
1116
- await instance.dispose();
1117
- this.evictIfCurrent(workspaceKey, instance);
1118
- this.assertActive(querySignal);
1119
- instance = this.instanceFor(workspaceKey, workspace);
1120
- return await instance.query(request, source, querySignal);
1121
- } finally {
1111
+ let canRetryTransport = true;
1112
+ for (;;) {
1113
+ const [queryOutcome] = await Promise.allSettled([instance.query(request, source, querySignal)]);
1114
+ let teardownOutcome;
1122
1115
  if (instance.dead) {
1123
- await instance.dispose();
1116
+ [teardownOutcome] = await Promise.allSettled([instance.dispose()]);
1124
1117
  this.evictIfCurrent(workspaceKey, instance);
1125
1118
  }
1119
+ if (teardownOutcome?.status === "rejected") {
1120
+ if (queryOutcome.status === "rejected") throw new AggregateError([queryOutcome.reason, teardownOutcome.reason], "LSP operation and teardown failed");
1121
+ throw teardownOutcome.reason;
1122
+ }
1123
+ if (queryOutcome.status === "fulfilled") return queryOutcome.value;
1124
+ if (!canRetryTransport || !instance.isTransportFailure(queryOutcome.reason)) throw queryOutcome.reason;
1125
+ canRetryTransport = false;
1126
+ this.assertActive(querySignal);
1127
+ instance = this.instanceFor(workspaceKey, workspace);
1126
1128
  }
1127
1129
  });
1128
1130
  }
@@ -4,8 +4,8 @@
4
4
  * server→client requests: it answers `workspace/configuration` from static
5
5
  * config, and rejects `workspace/applyEdit` (this host never applies edits or
6
6
  * runs commands). It caps stderr, surfaces framing/decoder failures as a
7
- * fatal close, and exposes tree-scoped termination through the handle so the
8
- * instance owns teardown; group/tree mechanics live in the subprocess
7
+ * fatal close, and exposes managed-range termination through the handle so the
8
+ * instance owns teardown; platform mechanics live in the subprocess
9
9
  * Service Provider.
10
10
  * @module @deepseek-ai/dsh-lsp-stdio/connection
11
11
  */
@@ -62,8 +62,6 @@ export declare class LspConnection {
62
62
  * @param writer - message writer; tests inject callback failures without relying on OS pipe races.
63
63
  */
64
64
  constructor(spec: ConnectionSpec, spawner: ConnectionSpawner, onServerRequest: (method: string, params: unknown) => Promise<unknown>, writer?: ConnectionWriter);
65
- /** The child's pid, or `-1` when the spawn produced no pid (so signalling is a no-op). */
66
- get pid(): number;
67
65
  /** The retained stderr tail, for diagnostics on a failed server. */
68
66
  get stderrTail(): string;
69
67
  /** Whether the transport has failed even if the child close event has not arrived yet. */
@@ -98,14 +96,14 @@ export declare class LspConnection {
98
96
  * @returns the numeric id the next request will be assigned.
99
97
  */
100
98
  peekNextId(): number;
101
- /** Terminate the server's process tree (the seam's SIGTERM→grace→SIGKILL escalation; idempotent). */
99
+ /** Terminate the server's provider-managed range (idempotent). */
102
100
  terminate(): void;
103
101
  /**
104
- * Wait until the owned process tree has exited.
102
+ * Wait until the owned managed range is empty.
105
103
  * @param signal - optional bound for the wait.
106
- * @returns `true` when the tree exited, or `false` when the signal aborted first.
104
+ * @returns `true` when the range is empty, or `false` when the signal aborted first.
107
105
  */
108
- waitForProcessTreeExit(signal?: AbortSignal): Promise<boolean>;
106
+ waitForManagedRangeExit(signal?: AbortSignal): Promise<boolean>;
109
107
  private onStdout;
110
108
  private dispatch;
111
109
  private handleServerRequest;
@@ -75,14 +75,15 @@ export declare class LspInstance {
75
75
  dispose(): Promise<void>;
76
76
  /** Publish disposal once and make every caller await the same quiescence boundary. */
77
77
  private startTeardown;
78
+ /** Await teardown while leaving its memoized failure for provider-level finalization. */
79
+ private awaitTeardownAttempt;
78
80
  private tearDown;
79
81
  /** Best-effort LSP `shutdown`/`exit`, including process close, bounded by `signal`. */
80
82
  private gracefulShutdown;
81
83
  /**
82
- * Terminate the tree (the seam escalates SIGTERM→`killGraceMs`→SIGKILL),
83
- * then await leader and helper exit. The awaits are unbounded on purpose:
84
- * the seam's escalation already committed to SIGKILL, so quiescence — not
85
- * another timer — is the postcondition disposal owes its callers.
84
+ * Terminate the provider-managed range, then await the direct server result
85
+ * and whole-range quiescence. The awaits are unbounded on purpose because
86
+ * quiescence, not another timer, is the postcondition disposal owes callers.
86
87
  */
87
88
  private forceTerminate;
88
89
  }
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.2-alpha.5",
4
+ "version": "0.1.3-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -28,28 +28,28 @@
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
30
  "@deepseek-ai/cordis": "^4.0.2",
31
- "@deepseek-ai/dsh-brand": "^0.1.2-alpha.5",
32
- "@deepseek-ai/dsh-fs": "^0.1.2-alpha.5",
33
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
34
- "@deepseek-ai/dsh-lsp": "^0.1.2-alpha.5",
35
- "@deepseek-ai/dsh-subprocess": "^0.1.2-alpha.5",
36
- "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.5"
31
+ "@deepseek-ai/dsh-brand": "^0.1.3-alpha.2",
32
+ "@deepseek-ai/dsh-fs": "^0.1.3-alpha.2",
33
+ "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
34
+ "@deepseek-ai/dsh-lsp": "^0.1.3-alpha.2",
35
+ "@deepseek-ai/dsh-subprocess": "^0.1.3-alpha.2",
36
+ "@deepseek-ai/dsh-timeout": "^0.1.3-alpha.2"
37
37
  },
38
38
  "dependencies": {
39
- "@deepseek-ai/schemastery": "^3.18.2",
40
- "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.5"
39
+ "@deepseek-ai/dsh-util-values": "^0.1.3-alpha.2",
40
+ "@deepseek-ai/schemastery": "^3.18.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "typescript": "^6.0.3",
44
44
  "typescript-language-server": "^5.0.0",
45
45
  "@deepseek-ai/cordis": "^4.0.2",
46
- "@deepseek-ai/dsh-brand": "^0.1.2-alpha.5",
47
- "@deepseek-ai/dsh-fs": "^0.1.2-alpha.5",
48
- "@deepseek-ai/dsh-fs-local": "^0.1.2-alpha.5",
49
- "@deepseek-ai/dsh-lsp": "^0.1.2-alpha.5",
50
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
51
- "@deepseek-ai/dsh-subprocess": "^0.1.2-alpha.5",
52
- "@deepseek-ai/dsh-subprocess-local": "^0.1.2-alpha.5",
53
- "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.5"
46
+ "@deepseek-ai/dsh-brand": "^0.1.3-alpha.2",
47
+ "@deepseek-ai/dsh-fs": "^0.1.3-alpha.2",
48
+ "@deepseek-ai/dsh-fs-local": "^0.1.3-alpha.2",
49
+ "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
50
+ "@deepseek-ai/dsh-subprocess": "^0.1.3-alpha.2",
51
+ "@deepseek-ai/dsh-subprocess-local": "^0.1.3-alpha.2",
52
+ "@deepseek-ai/dsh-lsp": "^0.1.3-alpha.2",
53
+ "@deepseek-ai/dsh-timeout": "^0.1.3-alpha.2"
54
54
  }
55
55
  }