@sayknow-cli/coding-agent 0.5.2 → 0.5.6

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +55 -16
  2. package/dist/types/cli/update-cli.d.ts +8 -1
  3. package/dist/types/config/atomic-yaml-patch.d.ts +20 -1
  4. package/dist/types/config/keybindings.d.ts +10 -0
  5. package/dist/types/config/model-registry.d.ts +1 -1
  6. package/dist/types/config/models-config-schema.d.ts +4 -0
  7. package/dist/types/config/settings-schema.d.ts +9 -0
  8. package/dist/types/dap/client.d.ts +1 -0
  9. package/dist/types/lsp/client.d.ts +5 -0
  10. package/dist/types/modes/components/welcome.d.ts +1 -0
  11. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  12. package/dist/types/modes/interactive-mode.d.ts +13 -1
  13. package/dist/types/modes/types.d.ts +7 -1
  14. package/dist/types/runtime-mcp/transports/stdio.d.ts +2 -0
  15. package/dist/types/session/agent-session.d.ts +8 -0
  16. package/dist/types/session/internal/managed-session-scope.d.ts +3 -1
  17. package/dist/types/session/internal/managed-session-storage.d.ts +2 -0
  18. package/dist/types/session/session-manager.d.ts +9 -0
  19. package/dist/types/session/session-storage.d.ts +17 -0
  20. package/dist/types/session-import/claude.d.ts +26 -0
  21. package/dist/types/session-import/codex.d.ts +22 -0
  22. package/dist/types/session-import/command.d.ts +20 -0
  23. package/dist/types/session-import/detect.d.ts +22 -0
  24. package/dist/types/session-import/index.d.ts +7 -0
  25. package/dist/types/session-import/redact.d.ts +23 -0
  26. package/dist/types/session-import/service.d.ts +28 -0
  27. package/dist/types/session-import/types.d.ts +125 -0
  28. package/dist/types/skc-runtime/launch-worktree.d.ts +17 -1
  29. package/dist/types/slash-commands/builtin-registry.d.ts +4 -0
  30. package/dist/types/slash-commands/types.d.ts +5 -0
  31. package/dist/types/tools/ask.d.ts +5 -0
  32. package/package.json +13 -7
  33. package/scripts/generate-sdk-operation-inventory.ts +5 -6
  34. package/src/cli/config-cli.ts +23 -8
  35. package/src/cli/update-cli.ts +265 -21
  36. package/src/config/atomic-yaml-patch.ts +167 -30
  37. package/src/config/keybindings.ts +10 -0
  38. package/src/config/model-profiles.ts +69 -1
  39. package/src/config/model-registry.ts +60 -38
  40. package/src/config/models-config-schema.ts +1 -1
  41. package/src/config/settings-schema.ts +9 -0
  42. package/src/config/settings.ts +13 -2
  43. package/src/dap/client.ts +78 -30
  44. package/src/internal-urls/docs-index.generated.ts +5 -4
  45. package/src/lsp/client.ts +17 -29
  46. package/src/modes/action-registry.ts +2 -0
  47. package/src/modes/components/welcome.ts +5 -0
  48. package/src/modes/controllers/goal-mode-controller.ts +7 -2
  49. package/src/modes/controllers/input-controller.ts +71 -6
  50. package/src/modes/controllers/plan-mode-controller.ts +8 -1
  51. package/src/modes/controllers/runtime-mcp-command-controller.ts +14 -0
  52. package/src/modes/controllers/selector-controller.ts +47 -19
  53. package/src/modes/interactive-mode.ts +54 -4
  54. package/src/modes/types.ts +5 -1
  55. package/src/runtime-mcp/transports/stdio.ts +96 -33
  56. package/src/sdk/broker/lifecycle.ts +6 -5
  57. package/src/sdk/protocol/operation-inventory.generated.json +33 -0
  58. package/src/sdk/session.ts +25 -18
  59. package/src/session/agent-session.ts +89 -14
  60. package/src/session/internal/managed-session-scope.ts +41 -6
  61. package/src/session/internal/managed-session-storage.ts +14 -0
  62. package/src/session/session-manager.ts +113 -11
  63. package/src/session/session-storage.ts +70 -0
  64. package/src/session-import/claude.ts +382 -0
  65. package/src/session-import/codex.ts +458 -0
  66. package/src/session-import/command.ts +61 -0
  67. package/src/session-import/detect.ts +133 -0
  68. package/src/session-import/index.ts +27 -0
  69. package/src/session-import/redact.ts +125 -0
  70. package/src/session-import/service.ts +749 -0
  71. package/src/session-import/types.ts +163 -0
  72. package/src/skc-runtime/launch-worktree.ts +277 -4
  73. package/src/slash-commands/acp-builtins.ts +5 -1
  74. package/src/slash-commands/builtin-registry.ts +100 -1
  75. package/src/slash-commands/types.ts +5 -0
  76. package/src/tools/ask.ts +5 -0
  77. package/src/export/html/template.css +0 -1060
  78. package/src/export/html/template.html +0 -47
  79. package/src/export/html/template.js +0 -2348
  80. package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
  81. package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
  82. package/vendor/insane-search/engine/tests/test_u1.py +0 -200
  83. package/vendor/insane-search/engine/tests/test_u4.py +0 -131
  84. package/vendor/insane-search/engine/tests/test_u5.py +0 -163
  85. package/vendor/insane-search/engine/tests/test_u7.py +0 -124
  86. package/vendor/insane-search/engine/tests/test_u8.py +0 -216
package/src/dap/client.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import * as fs from "node:fs/promises";
3
- import { logger } from "@sayknow-cli/utils";
3
+ import { isKnownSinkPeerClosedError, logger } from "@sayknow-cli/utils";
4
4
  import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
5
5
  import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
6
6
  import { type OwnedProcess, spawnOwnedProcess } from "../runtime/process-lifecycle";
@@ -62,9 +62,9 @@ function parseMessage(
62
62
 
63
63
  async function writeMessage(sink: DapWriteSink, message: DapRequestMessage | DapResponseMessage): Promise<void> {
64
64
  const content = JSON.stringify(message);
65
- sink.write(`Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n`);
66
- sink.write(content);
67
- await sink.flush();
65
+ const frame = `Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n${content}`;
66
+ await Promise.resolve(sink.write(frame));
67
+ await Promise.resolve(sink.flush());
68
68
  }
69
69
 
70
70
  function toErrorMessage(value: unknown): string {
@@ -98,6 +98,9 @@ export class DapClient {
98
98
  #messageBuffer = Buffer.alloc(0);
99
99
  #isReading = false;
100
100
  #disposed = false;
101
+ #terminalError: Error | undefined;
102
+ #disposePromise: Promise<void> | undefined;
103
+ #writeQueue: Promise<void> = Promise.resolve();
101
104
  #lastActivity = Date.now();
102
105
  #capabilities?: DapCapabilities;
103
106
  #eventHandlers = new Map<string, Set<DapEventHandler>>();
@@ -119,6 +122,18 @@ export class DapClient {
119
122
  this.#socket = options?.socket;
120
123
  }
121
124
 
125
+ #queueWriteMessage(message: DapRequestMessage | DapResponseMessage): Promise<void> {
126
+ const write = this.#writeQueue
127
+ .catch(() => {})
128
+ .then(() => {
129
+ if (this.#terminalError) throw this.#terminalError;
130
+ if (this.#disposed) throw new Error(`DAP adapter ${this.adapter.name} is not running`);
131
+ return writeMessage(this.#writeSink, message);
132
+ });
133
+ this.#writeQueue = write.catch(() => {});
134
+ return write;
135
+ }
136
+
122
137
  static async spawn({ adapter, cwd }: DapSpawnOptions): Promise<DapClient> {
123
138
  if (adapter.connectMode === "socket") {
124
139
  return DapClient.#spawnSocket({ adapter, cwd });
@@ -142,7 +157,7 @@ export class DapClient {
142
157
  proc.exited.then(() => {
143
158
  client.#handleProcessExit();
144
159
  });
145
- void client.#startMessageReader();
160
+ void client.startMessageReader();
146
161
  return client;
147
162
  }
148
163
 
@@ -199,7 +214,7 @@ export class DapClient {
199
214
  transport = await connectSocket({ unix: socketPath }, 10_000);
200
215
  const client = new DapClient(adapter, cwd, owner, transport);
201
216
  proc.exited.then(() => client.#handleProcessExit());
202
- void client.#startMessageReader();
217
+ void client.startMessageReader();
203
218
  return client;
204
219
  } catch (err) {
205
220
  transport?.socket.end();
@@ -268,7 +283,7 @@ export class DapClient {
268
283
  const { readable, writeSink, socket } = wrapBunSocket(rawSocket);
269
284
  const client = new DapClient(adapter, cwd, owner, { readable, writeSink, socket });
270
285
  proc.exited.then(() => client.#handleProcessExit());
271
- void client.#startMessageReader();
286
+ void client.startMessageReader();
272
287
  return client;
273
288
  }
274
289
 
@@ -364,6 +379,7 @@ export class DapClient {
364
379
  signal?: AbortSignal,
365
380
  timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,
366
381
  ): Promise<TBody> {
382
+ if (this.#terminalError) throw this.#terminalError;
367
383
  if (signal?.aborted) {
368
384
  throw signal.reason instanceof Error ? signal.reason : new ToolAbortError();
369
385
  }
@@ -412,10 +428,11 @@ export class DapClient {
412
428
  });
413
429
  this.#lastActivity = Date.now();
414
430
  try {
415
- await writeMessage(this.#writeSink, request);
431
+ await this.#queueWriteMessage(request);
416
432
  } catch (error) {
417
433
  this.#pendingRequests.delete(requestSeq);
418
434
  cleanup();
435
+ if (isKnownSinkPeerClosedError(error)) throw this.#terminalizeTransport(error);
419
436
  throw error;
420
437
  }
421
438
  return promise;
@@ -431,37 +448,30 @@ export class DapClient {
431
448
  ...(message ? { message } : {}),
432
449
  ...(body !== undefined ? { body } : {}),
433
450
  };
434
- await writeMessage(this.#writeSink, response);
451
+ await this.#queueWriteMessage(response);
435
452
  }
436
453
 
437
454
  async dispose(): Promise<void> {
438
- if (this.#disposed) return;
439
- this.#disposed = true;
440
- this.#rejectPendingRequests(new Error(`DAP adapter ${this.adapter.name} disposed`));
441
- try {
442
- this.#socket?.end();
443
- } catch {
444
- /* socket may already be closed */
445
- }
446
- try {
447
- await this.#owner.dispose();
448
- await this.#owner.awaitExit({ timeoutMs: 1_000 });
449
- } catch (error) {
450
- logger.debug("Failed to dispose DAP adapter", {
451
- adapter: this.adapter.name,
452
- error: toErrorMessage(error),
453
- });
455
+ if (!this.#disposed) {
456
+ const error = new Error(`DAP adapter ${this.adapter.name} disposed`);
457
+ this.#disposed = true;
458
+ this.#terminalError = error;
459
+ this.#rejectPendingRequests(error);
454
460
  }
461
+ await this.#beginOwnerDisposal();
455
462
  }
456
463
 
457
- async #startMessageReader(): Promise<void> {
464
+ async startMessageReader(): Promise<void> {
458
465
  if (this.#isReading) return;
459
466
  this.#isReading = true;
460
467
  const reader = this.#readable.getReader();
461
468
  try {
462
469
  while (true) {
463
470
  const { done, value } = await reader.read();
464
- if (done) break;
471
+ if (done) {
472
+ this.#terminalizeTransport(new Error("DAP readable closed"));
473
+ break;
474
+ }
465
475
  const currentBuffer = Buffer.concat([this.#messageBuffer, value]);
466
476
  this.#messageBuffer = currentBuffer;
467
477
  let workingBuffer = currentBuffer;
@@ -482,7 +492,7 @@ export class DapClient {
482
492
  this.#messageBuffer = workingBuffer;
483
493
  }
484
494
  } catch (error) {
485
- this.#rejectPendingRequests(new Error(`DAP connection closed: ${toErrorMessage(error)}`));
495
+ this.#terminalizeTransport(error);
486
496
  } finally {
487
497
  reader.releaseLock();
488
498
  this.#isReading = false;
@@ -523,9 +533,9 @@ export class DapClient {
523
533
  try {
524
534
  const handler = this.#reverseRequestHandlers.get(message.command);
525
535
  if (handler) {
536
+ let body: unknown;
526
537
  try {
527
- const body = await handler(message.arguments);
528
- await this.sendResponse(message, true, body);
538
+ body = await handler(message.arguments);
529
539
  } catch (error) {
530
540
  const errorMessage = toErrorMessage(error);
531
541
  await this.sendResponse(
@@ -539,7 +549,9 @@ export class DapClient {
539
549
  },
540
550
  errorMessage,
541
551
  );
552
+ return;
542
553
  }
554
+ await this.sendResponse(message, true, body);
543
555
  return;
544
556
  }
545
557
  const errorMessage = `Unsupported DAP request: ${message.command}`;
@@ -555,6 +567,10 @@ export class DapClient {
555
567
  errorMessage,
556
568
  );
557
569
  } catch (error) {
570
+ if (isKnownSinkPeerClosedError(error)) {
571
+ this.#terminalizeTransport(error);
572
+ return;
573
+ }
558
574
  logger.warn("Failed to answer DAP adapter request", {
559
575
  adapter: this.adapter.name,
560
576
  command: message.command,
@@ -586,9 +602,41 @@ export class DapClient {
586
602
  ? `DAP adapter exited (code ${exitCode}): ${stderr}${diagnosticSuffix}`
587
603
  : `DAP adapter exited unexpectedly (code ${exitCode})${diagnosticSuffix}`,
588
604
  );
605
+ this.#terminalError = error;
589
606
  this.#rejectPendingRequests(error);
590
607
  }
591
608
 
609
+ #terminalizeTransport(cause: unknown): Error {
610
+ if (this.#terminalError) return this.#terminalError;
611
+ const error = new Error(`DAP adapter ${this.adapter.name} transport closed`, { cause });
612
+ this.#terminalError = error;
613
+ this.#disposed = true;
614
+ this.#rejectPendingRequests(error);
615
+ void this.#beginOwnerDisposal();
616
+ return error;
617
+ }
618
+
619
+ #beginOwnerDisposal(): Promise<void> {
620
+ if (this.#disposePromise) return this.#disposePromise;
621
+ this.#disposePromise = (async () => {
622
+ try {
623
+ this.#socket?.end();
624
+ } catch {
625
+ /* socket may already be closed */
626
+ }
627
+ try {
628
+ await this.#owner.dispose();
629
+ await this.#owner.awaitExit({ timeoutMs: 1_000 });
630
+ } catch (disposeError) {
631
+ logger.debug("Failed to dispose DAP adapter", {
632
+ adapter: this.adapter.name,
633
+ error: toErrorMessage(disposeError),
634
+ });
635
+ }
636
+ })();
637
+ return this.#disposePromise;
638
+ }
639
+
592
640
  #rejectPendingRequests(error: Error): void {
593
641
  for (const pending of this.#pendingRequests.values()) {
594
642
  pending.reject(error);