@trim21/personal-pi-extensions 0.1.518 → 0.1.519

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.518",
3
+ "version": "0.1.519",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -213,10 +213,39 @@ function toInspectLocations(result: DefinitionResult): InspectLocation[] {
213
213
  return locations;
214
214
  }
215
215
 
216
+ /** stderr 尾部保留上限(字符):足够容纳启动失败的最后报错,又不撑爆通知。 */
217
+ const STDERR_TAIL_CHARS = 4_000;
218
+
219
+ /** 展开 Error 的 cause 链为一条 message;流包装错误只包一层,逐层展开即可还原根因。 */
220
+ function errorChainMessage(error: unknown): string {
221
+ if (!(error instanceof Error)) return String(error);
222
+ const parts: string[] = [];
223
+ let current: unknown = error;
224
+ while (current instanceof Error && current.message && !parts.includes(current.message)) {
225
+ parts.push(current.message);
226
+ current = current.cause;
227
+ }
228
+ if (typeof current === "string" || typeof current === "number") parts.push(String(current));
229
+ return parts.join(": ");
230
+ }
231
+
232
+ /** 拼装握手失败的完整原因:错误链 + 进程退出状态 + stderr 尾部。 */
233
+ function describeStartupFailure(
234
+ error: unknown,
235
+ exitDescription: string | undefined,
236
+ stderrTail: string,
237
+ ): string {
238
+ const parts = [errorChainMessage(error)];
239
+ if (exitDescription) parts.push(`server ${exitDescription}`);
240
+ const stderr = stderrTail.trim();
241
+ if (stderr) parts.push(`stderr: ${stderr}`);
242
+ return parts.join("; ");
243
+ }
244
+
216
245
  export class InitializeError extends Error {
217
246
  readonly serverID: string;
218
- constructor(serverID: string, cause: unknown) {
219
- super(`Failed to initialize LSP server ${serverID}`, { cause });
247
+ constructor(serverID: string, cause: unknown, detail?: string) {
248
+ super(`Failed to initialize LSP server ${serverID}${detail ? `: ${detail}` : ""}`, { cause });
220
249
  this.serverID = serverID;
221
250
  }
222
251
  }
@@ -443,11 +472,19 @@ export async function create(input: CreateInput): Promise<LspClient> {
443
472
  new StreamMessageReader(input.server.process.stdout),
444
473
  new StreamMessageWriter(input.server.process.stdin),
445
474
  );
446
- input.server.process.stderr.resume();
475
+ // stderr 平时只在尾部保留少量内容(避免子进程大量输出撑爆内存);
476
+ // 握手失败时随错误输出,服务器 panic / 参数错误等启动原因由此还原。
477
+ let stderrTail = "";
478
+ input.server.process.stderr.setEncoding("utf8");
479
+ input.server.process.stderr.on("data", (chunk: string) => {
480
+ stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_CHARS);
481
+ });
447
482
  /** 连接或服务器进程已关闭;pull 重试循环以此终止,避免无界等待。 */
448
483
  let connectionClosed = false;
449
- input.server.process.once("exit", () => {
484
+ let exitDescription: string | undefined;
485
+ input.server.process.once("exit", (code, signal) => {
450
486
  connectionClosed = true;
487
+ exitDescription = code === null ? `killed by signal ${signal}` : `exited with code ${code}`;
451
488
  });
452
489
  connection.onDispose(() => {
453
490
  connectionClosed = true;
@@ -582,7 +619,11 @@ export async function create(input: CreateInput): Promise<LspClient> {
582
619
  connection.end();
583
620
  connection.dispose();
584
621
  await stopProcess(input.server.process);
585
- throw new InitializeError(input.serverID, error);
622
+ throw new InitializeError(
623
+ input.serverID,
624
+ error,
625
+ describeStartupFailure(error, exitDescription, stderrTail),
626
+ );
586
627
  });
587
628
 
588
629
  const syncKind = getSyncKind(initialized.capabilities);