@sumicom/quicksave 0.6.0 → 0.6.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.
Files changed (122) hide show
  1. package/dist/ai/asyncQueue.d.ts +7 -6
  2. package/dist/ai/asyncQueue.d.ts.map +1 -1
  3. package/dist/ai/asyncQueue.js +31 -21
  4. package/dist/ai/asyncQueue.js.map +1 -1
  5. package/dist/ai/cardBuilder.d.ts +22 -2
  6. package/dist/ai/cardBuilder.d.ts.map +1 -1
  7. package/dist/ai/cardBuilder.js +275 -30
  8. package/dist/ai/cardBuilder.js.map +1 -1
  9. package/dist/ai/claudeCliProvider.d.ts +21 -0
  10. package/dist/ai/claudeCliProvider.d.ts.map +1 -0
  11. package/dist/ai/claudeCliProvider.js +527 -0
  12. package/dist/ai/claudeCliProvider.js.map +1 -0
  13. package/dist/ai/claudeCodeProvider.d.ts +18 -0
  14. package/dist/ai/claudeCodeProvider.d.ts.map +1 -0
  15. package/dist/ai/claudeCodeProvider.js +30 -0
  16. package/dist/ai/claudeCodeProvider.js.map +1 -0
  17. package/dist/ai/claudeSdkProvider.d.ts +20 -0
  18. package/dist/ai/claudeSdkProvider.d.ts.map +1 -0
  19. package/dist/ai/claudeSdkProvider.js +428 -0
  20. package/dist/ai/claudeSdkProvider.js.map +1 -0
  21. package/dist/ai/codexMcpProvider.d.ts +15 -0
  22. package/dist/ai/codexMcpProvider.d.ts.map +1 -0
  23. package/dist/ai/codexMcpProvider.js +442 -0
  24. package/dist/ai/codexMcpProvider.js.map +1 -0
  25. package/dist/ai/codexSdkProvider.d.ts +35 -0
  26. package/dist/ai/codexSdkProvider.d.ts.map +1 -0
  27. package/dist/ai/codexSdkProvider.js +422 -0
  28. package/dist/ai/codexSdkProvider.js.map +1 -0
  29. package/dist/ai/commitSummary.d.ts +4 -0
  30. package/dist/ai/commitSummary.d.ts.map +1 -1
  31. package/dist/ai/commitSummary.js +80 -40
  32. package/dist/ai/commitSummary.js.map +1 -1
  33. package/dist/ai/debugLogger.d.ts +24 -0
  34. package/dist/ai/debugLogger.d.ts.map +1 -0
  35. package/dist/ai/debugLogger.js +55 -0
  36. package/dist/ai/debugLogger.js.map +1 -0
  37. package/dist/ai/provider.d.ts +57 -0
  38. package/dist/ai/provider.d.ts.map +1 -0
  39. package/dist/ai/provider.js +2 -0
  40. package/dist/ai/provider.js.map +1 -0
  41. package/dist/ai/sandboxMcp.d.ts +12 -0
  42. package/dist/ai/sandboxMcp.d.ts.map +1 -0
  43. package/dist/ai/sandboxMcp.js +12 -0
  44. package/dist/ai/sandboxMcp.js.map +1 -0
  45. package/dist/ai/sandboxMcpStdio.d.ts +16 -0
  46. package/dist/ai/sandboxMcpStdio.d.ts.map +1 -0
  47. package/dist/ai/sandboxMcpStdio.js +127 -0
  48. package/dist/ai/sandboxMcpStdio.js.map +1 -0
  49. package/dist/ai/sessionManager.d.ts +105 -0
  50. package/dist/ai/sessionManager.d.ts.map +1 -0
  51. package/dist/ai/sessionManager.js +716 -0
  52. package/dist/ai/sessionManager.js.map +1 -0
  53. package/dist/ai/sessionRegistry.d.ts +28 -0
  54. package/dist/ai/sessionRegistry.d.ts.map +1 -0
  55. package/dist/ai/sessionRegistry.js +148 -0
  56. package/dist/ai/sessionRegistry.js.map +1 -0
  57. package/dist/config.d.ts +1 -0
  58. package/dist/config.d.ts.map +1 -1
  59. package/dist/config.js +12 -0
  60. package/dist/config.js.map +1 -1
  61. package/dist/connection/connection.d.ts +1 -0
  62. package/dist/connection/connection.d.ts.map +1 -1
  63. package/dist/connection/connection.js +19 -1
  64. package/dist/connection/connection.js.map +1 -1
  65. package/dist/git/operations.d.ts +26 -1
  66. package/dist/git/operations.d.ts.map +1 -1
  67. package/dist/git/operations.js +83 -2
  68. package/dist/git/operations.js.map +1 -1
  69. package/dist/handlers/messageHandler.d.ts +24 -3
  70. package/dist/handlers/messageHandler.d.ts.map +1 -1
  71. package/dist/handlers/messageHandler.js +378 -24
  72. package/dist/handlers/messageHandler.js.map +1 -1
  73. package/dist/index.js +23 -40
  74. package/dist/index.js.map +1 -1
  75. package/dist/service/debugHttpServer.d.ts +27 -0
  76. package/dist/service/debugHttpServer.d.ts.map +1 -0
  77. package/dist/service/debugHttpServer.js +189 -0
  78. package/dist/service/debugHttpServer.js.map +1 -0
  79. package/dist/service/ensureDaemon.d.ts.map +1 -1
  80. package/dist/service/ensureDaemon.js +15 -2
  81. package/dist/service/ensureDaemon.js.map +1 -1
  82. package/dist/service/ipcClient.js +1 -1
  83. package/dist/service/run.d.ts.map +1 -1
  84. package/dist/service/run.js +42 -15
  85. package/dist/service/run.js.map +1 -1
  86. package/dist/service/singleton.d.ts +4 -0
  87. package/dist/service/singleton.d.ts.map +1 -1
  88. package/dist/service/singleton.js +28 -2
  89. package/dist/service/singleton.js.map +1 -1
  90. package/package.json +7 -3
  91. package/dist/ai/asyncQueue.test.d.ts +0 -2
  92. package/dist/ai/asyncQueue.test.d.ts.map +0 -1
  93. package/dist/ai/asyncQueue.test.js +0 -56
  94. package/dist/ai/asyncQueue.test.js.map +0 -1
  95. package/dist/ai/claudeCodeService.d.ts +0 -122
  96. package/dist/ai/claudeCodeService.d.ts.map +0 -1
  97. package/dist/ai/claudeCodeService.js +0 -869
  98. package/dist/ai/claudeCodeService.js.map +0 -1
  99. package/dist/ai/sessionStore.test.d.ts +0 -2
  100. package/dist/ai/sessionStore.test.d.ts.map +0 -1
  101. package/dist/ai/sessionStore.test.js +0 -99
  102. package/dist/ai/sessionStore.test.js.map +0 -1
  103. package/dist/connection/pubsub.test.d.ts +0 -2
  104. package/dist/connection/pubsub.test.d.ts.map +0 -1
  105. package/dist/connection/pubsub.test.js +0 -96
  106. package/dist/connection/pubsub.test.js.map +0 -1
  107. package/dist/git/operations.test.d.ts +0 -2
  108. package/dist/git/operations.test.d.ts.map +0 -1
  109. package/dist/git/operations.test.js +0 -300
  110. package/dist/git/operations.test.js.map +0 -1
  111. package/dist/handlers/messageHandler.test.d.ts +0 -2
  112. package/dist/handlers/messageHandler.test.d.ts.map +0 -1
  113. package/dist/handlers/messageHandler.test.js +0 -361
  114. package/dist/handlers/messageHandler.test.js.map +0 -1
  115. package/dist/service/ipc.test.d.ts +0 -2
  116. package/dist/service/ipc.test.d.ts.map +0 -1
  117. package/dist/service/ipc.test.js +0 -140
  118. package/dist/service/ipc.test.js.map +0 -1
  119. package/dist/service/singleton.test.d.ts +0 -2
  120. package/dist/service/singleton.test.d.ts.map +0 -1
  121. package/dist/service/singleton.test.js +0 -39
  122. package/dist/service/singleton.test.js.map +0 -1
@@ -1,16 +1,17 @@
1
1
  /**
2
- * Push-based async iterable.
3
- * Consumers `for await` over it; producers call `push()` to enqueue values
4
- * and `end()` to signal completion.
2
+ * A simple async iterable queue for multi-turn prompt delivery.
3
+ *
4
+ * Push values from the producer side; consumers iterate with `for await`.
5
+ * Calling `end()` signals the consumer that no more values will arrive.
5
6
  */
6
7
  export declare class AsyncQueue<T> implements AsyncIterable<T> {
7
8
  private queue;
8
- private resolve;
9
+ private waiting;
9
10
  private done;
10
- /** Enqueue a value. No-op if end() was already called. */
11
+ /** Enqueue a value. If a consumer is already waiting, deliver immediately. */
11
12
  push(value: T): void;
12
13
  /** Signal that no more values will be pushed. */
13
14
  end(): void;
14
- [Symbol.asyncIterator](): AsyncGenerator<T, void>;
15
+ [Symbol.asyncIterator](): AsyncIterator<T>;
15
16
  }
16
17
  //# sourceMappingURL=asyncQueue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncQueue.d.ts","sourceRoot":"","sources":["../../src/ai/asyncQueue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,UAAU,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,IAAI,CAAS;IAErB,0DAA0D;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IASpB,iDAAiD;IACjD,GAAG,IAAI,IAAI;IAQJ,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC;CASzD"}
1
+ {"version":3,"file":"asyncQueue.d.ts","sourceRoot":"","sources":["../../src/ai/asyncQueue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,UAAU,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,OAAO,CAAqD;IACpE,OAAO,CAAC,IAAI,CAAS;IAErB,8EAA8E;IAC9E,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAWpB,iDAAiD;IACjD,GAAG,IAAI,IAAI;IASX,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAe3C"}
@@ -1,39 +1,49 @@
1
1
  /**
2
- * Push-based async iterable.
3
- * Consumers `for await` over it; producers call `push()` to enqueue values
4
- * and `end()` to signal completion.
2
+ * A simple async iterable queue for multi-turn prompt delivery.
3
+ *
4
+ * Push values from the producer side; consumers iterate with `for await`.
5
+ * Calling `end()` signals the consumer that no more values will arrive.
5
6
  */
6
7
  export class AsyncQueue {
7
8
  queue = [];
8
- resolve = null;
9
+ waiting = null;
9
10
  done = false;
10
- /** Enqueue a value. No-op if end() was already called. */
11
+ /** Enqueue a value. If a consumer is already waiting, deliver immediately. */
11
12
  push(value) {
12
13
  if (this.done)
13
14
  return;
14
- this.queue.push(value);
15
- if (this.resolve) {
16
- this.resolve();
17
- this.resolve = null;
15
+ if (this.waiting) {
16
+ const resolve = this.waiting;
17
+ this.waiting = null;
18
+ resolve({ value, done: false });
19
+ }
20
+ else {
21
+ this.queue.push(value);
18
22
  }
19
23
  }
20
24
  /** Signal that no more values will be pushed. */
21
25
  end() {
22
26
  this.done = true;
23
- if (this.resolve) {
24
- this.resolve();
25
- this.resolve = null;
27
+ if (this.waiting) {
28
+ const resolve = this.waiting;
29
+ this.waiting = null;
30
+ resolve({ value: undefined, done: true });
26
31
  }
27
32
  }
28
- async *[Symbol.asyncIterator]() {
29
- while (true) {
30
- while (this.queue.length > 0) {
31
- yield this.queue.shift();
32
- }
33
- if (this.done)
34
- return;
35
- await new Promise((r) => { this.resolve = r; });
36
- }
33
+ [Symbol.asyncIterator]() {
34
+ return {
35
+ next: () => {
36
+ if (this.queue.length > 0) {
37
+ return Promise.resolve({ value: this.queue.shift(), done: false });
38
+ }
39
+ if (this.done) {
40
+ return Promise.resolve({ value: undefined, done: true });
41
+ }
42
+ return new Promise((resolve) => {
43
+ this.waiting = resolve;
44
+ });
45
+ },
46
+ };
37
47
  }
38
48
  }
39
49
  //# sourceMappingURL=asyncQueue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncQueue.js","sourceRoot":"","sources":["../../src/ai/asyncQueue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACb,KAAK,GAAQ,EAAE,CAAC;IAChB,OAAO,GAAwB,IAAI,CAAC;IACpC,IAAI,GAAG,KAAK,CAAC;IAErB,0DAA0D;IAC1D,IAAI,CAAC,KAAQ;QACX,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACtB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,GAAG;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;QAC3B,OAAO,IAAI,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,CAAC;YAC5B,CAAC;YACD,IAAI,IAAI,CAAC,IAAI;gBAAE,OAAO;YACtB,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"asyncQueue.js","sourceRoot":"","sources":["../../src/ai/asyncQueue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,UAAU;IACb,KAAK,GAAQ,EAAE,CAAC;IAChB,OAAO,GAAgD,IAAI,CAAC;IAC5D,IAAI,GAAG,KAAK,CAAC;IAErB,8EAA8E;IAC9E,IAAI,CAAC,KAAQ;QACX,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACtB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,GAAG;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,OAAO,CAAC,EAAE,KAAK,EAAE,SAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,CAAC,MAAM,CAAC,aAAa,CAAC;QACpB,OAAO;YACL,IAAI,EAAE,GAA+B,EAAE;gBACrC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBACtE,CAAC;gBACD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBACd,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClE,CAAC;gBACD,OAAO,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,EAAE;oBAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;gBACzB,CAAC,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
@@ -1,7 +1,13 @@
1
1
  import type { Card, CardId, CardEvent, CardHistoryResponse, PendingInputAttachment } from '@sumicom/quicksave-shared';
2
+ /**
3
+ * Load persisted card history for a memory-mode session.
4
+ * Returns cards in insertion order, or empty array if none exist.
5
+ */
6
+ export declare function loadPersistedCards(sessionId: string): Promise<Card[]>;
2
7
  export declare class StreamCardBuilder {
3
8
  private sessionId;
4
9
  private streamId;
10
+ private cwd;
5
11
  private seq;
6
12
  private cards;
7
13
  /** tool_use_id → CardId, for pairing tool_result to ToolCallCard */
@@ -12,10 +18,24 @@ export declare class StreamCardBuilder {
12
18
  private ephemeralCards;
13
19
  /** Current streaming assistant_text card (for append_text events) */
14
20
  private currentTextCardId;
15
- constructor(sessionId: string, streamId: string);
21
+ /** JSONL file byte offset at the start of the current turn — history reads stop here. */
22
+ private _jsonlCutoff;
23
+ constructor(sessionId: string, streamId: string, cwd: string);
16
24
  updateSessionId(sessionId: string): void;
17
25
  /** Start a new turn: update streamId and reset per-turn state, keep accumulated cards. */
18
26
  startNewTurn(streamId: string): void;
27
+ /** Clear all accumulated cards. Call after a turn completes and JSONL is flushed. */
28
+ clearCards(): void;
29
+ /**
30
+ * Append current in-memory cards to the persisted card history file.
31
+ * Used by memory-mode providers (Codex) to survive reconnects.
32
+ * Call before clearCards() at the end of each turn.
33
+ */
34
+ persistCards(): Promise<void>;
35
+ /** Snapshot current JSONL byte size so history reads stop before the active turn. */
36
+ snapshotCutoff(): Promise<void>;
37
+ get jsonlCutoff(): number | null;
38
+ set jsonlCutoff(value: number | null);
19
39
  private nextId;
20
40
  private addEvent;
21
41
  private updateEvent;
@@ -60,5 +80,5 @@ export declare class StreamCardBuilder {
60
80
  systemMessage(text: string, subtype?: 'compacted' | 'cost' | 'error' | 'info' | 'warning'): CardEvent;
61
81
  errorMessage(text: string): CardEvent;
62
82
  }
63
- export declare function buildCardsFromHistory(sessionId: string, cwd: string, offset?: number, limit?: number): Promise<CardHistoryResponse>;
83
+ export declare function buildCardsFromHistory(sessionId: string, cwd: string, offset?: number, limit?: number, cutoffBytes?: number): Promise<CardHistoryResponse>;
64
84
  //# sourceMappingURL=cardBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cardBuilder.d.ts","sourceRoot":"","sources":["../../src/ai/cardBuilder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cardBuilder.d.ts","sourceRoot":"","sources":["../../src/ai/cardBuilder.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,143 @@
1
- import { getSessionMessages, listSubagents, } from '@anthropic-ai/claude-agent-sdk';
2
- import { readFile } from 'fs/promises';
1
+ import { readFile, readdir, writeFile, mkdir, stat, open } from 'fs/promises';
3
2
  import { join } from 'path';
3
+ import { createReadStream, existsSync } from 'fs';
4
+ import { createInterface } from 'readline';
5
+ import { homedir } from 'os';
4
6
  const TOOL_RESULT_TRUNCATE_LENGTH = 500;
7
+ // ── Card history persistence (for memory-mode providers like Codex) ──
8
+ function cardHistoryDir() {
9
+ return join(homedir(), '.quicksave', 'state', 'card-history');
10
+ }
11
+ function cardHistoryPath(sessionId) {
12
+ return join(cardHistoryDir(), `${sessionId}.json`);
13
+ }
14
+ /**
15
+ * Load persisted card history for a memory-mode session.
16
+ * Returns cards in insertion order, or empty array if none exist.
17
+ */
18
+ export async function loadPersistedCards(sessionId) {
19
+ const p = cardHistoryPath(sessionId);
20
+ if (!existsSync(p))
21
+ return [];
22
+ try {
23
+ const raw = await readFile(p, 'utf-8');
24
+ const data = JSON.parse(raw);
25
+ return Array.isArray(data) ? data : [];
26
+ }
27
+ catch {
28
+ return [];
29
+ }
30
+ }
31
+ // ── Direct JSONL file reading (replaces SDK getSessionMessages/listSubagents) ──
32
+ function encodeCwdPath(cwd) {
33
+ return cwd.replace(/\//g, '-');
34
+ }
35
+ function claudeProjectDir(cwd) {
36
+ return join(homedir(), '.claude', 'projects', encodeCwdPath(cwd));
37
+ }
38
+ function jsonlPath(sessionId, cwd) {
39
+ return join(claudeProjectDir(cwd), sessionId + '.jsonl');
40
+ }
41
+ function parseJSONLContent(content) {
42
+ const msgs = [];
43
+ for (const line of content.split('\n')) {
44
+ if (!line.trim())
45
+ continue;
46
+ try {
47
+ const m = JSON.parse(line);
48
+ if (m.type === 'user' || m.type === 'assistant' || m.type === 'system')
49
+ msgs.push(m);
50
+ }
51
+ catch { /* skip malformed lines */ }
52
+ }
53
+ return msgs;
54
+ }
55
+ /** Count valid non-sidechain messages by streaming the file (constant memory). */
56
+ async function countMessagesInJSONL(filePath) {
57
+ return new Promise((resolve, reject) => {
58
+ let count = 0;
59
+ const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
60
+ rl.on('line', (line) => {
61
+ if (!line || line[0] !== '{')
62
+ return;
63
+ try {
64
+ const m = JSON.parse(line);
65
+ if ((m.type === 'user' || m.type === 'assistant' || m.type === 'system') && !m.isSidechain)
66
+ count++;
67
+ }
68
+ catch { /* skip */ }
69
+ });
70
+ rl.on('close', () => resolve(count));
71
+ rl.on('error', reject);
72
+ });
73
+ }
74
+ /** Read the tail of a file, dropping the first partial line at the cut boundary. */
75
+ async function readTailContent(filePath, bytes) {
76
+ const { size } = await stat(filePath);
77
+ if (size <= bytes)
78
+ return await readFile(filePath, 'utf-8');
79
+ const fh = await open(filePath, 'r');
80
+ try {
81
+ const start = size - bytes;
82
+ const buf = Buffer.alloc(bytes);
83
+ const { bytesRead } = await fh.read(buf, 0, bytes, start);
84
+ let content = buf.toString('utf-8', 0, bytesRead);
85
+ // Drop leading partial line (may also be mid-UTF-8)
86
+ const firstNl = content.indexOf('\n');
87
+ if (firstNl >= 0)
88
+ content = content.slice(firstNl + 1);
89
+ return content;
90
+ }
91
+ finally {
92
+ await fh.close();
93
+ }
94
+ }
95
+ /**
96
+ * Read messages from a session JSONL file.
97
+ *
98
+ * Modes:
99
+ * - `headBytes` — read only the first N bytes (for active-turn cutoff).
100
+ * - `tailBytes` — read only the last N bytes (for large-file pagination).
101
+ * - neither — read the entire file.
102
+ */
103
+ async function readMessagesFromJSONL(sessionId, cwd, opts) {
104
+ const p = jsonlPath(sessionId, cwd);
105
+ if (!existsSync(p))
106
+ return [];
107
+ if (opts?.headBytes != null) {
108
+ const fh = await open(p, 'r');
109
+ try {
110
+ const buf = Buffer.alloc(opts.headBytes);
111
+ const { bytesRead } = await fh.read(buf, 0, opts.headBytes, 0);
112
+ let raw = buf.toString('utf-8', 0, bytesRead);
113
+ const lastNewline = raw.lastIndexOf('\n');
114
+ if (lastNewline >= 0 && lastNewline < raw.length - 1) {
115
+ raw = raw.slice(0, lastNewline + 1);
116
+ }
117
+ return parseJSONLContent(raw);
118
+ }
119
+ finally {
120
+ await fh.close();
121
+ }
122
+ }
123
+ if (opts?.tailBytes != null) {
124
+ const content = await readTailContent(p, opts.tailBytes);
125
+ return parseJSONLContent(content);
126
+ }
127
+ const content = await readFile(p, 'utf-8');
128
+ return parseJSONLContent(content);
129
+ }
130
+ async function listSubagentIdsFromDisk(sessionId, cwd) {
131
+ const d = join(claudeProjectDir(cwd), sessionId, 'subagents');
132
+ if (!existsSync(d))
133
+ return [];
134
+ try {
135
+ return (await readdir(d)).filter(f => f.endsWith('.meta.json')).map(f => f.replace('.meta.json', ''));
136
+ }
137
+ catch {
138
+ return [];
139
+ }
140
+ }
5
141
  /** Extract readable text from tool_result content (string or array of blocks). */
6
142
  function extractToolResultText(content) {
7
143
  if (typeof content === 'string')
@@ -20,6 +156,7 @@ function extractToolResultText(content) {
20
156
  export class StreamCardBuilder {
21
157
  sessionId;
22
158
  streamId;
159
+ cwd;
23
160
  seq = 0;
24
161
  cards = new Map();
25
162
  /** tool_use_id → CardId, for pairing tool_result to ToolCallCard */
@@ -30,9 +167,12 @@ export class StreamCardBuilder {
30
167
  ephemeralCards = new Set();
31
168
  /** Current streaming assistant_text card (for append_text events) */
32
169
  currentTextCardId = null;
33
- constructor(sessionId, streamId) {
170
+ /** JSONL file byte offset at the start of the current turn — history reads stop here. */
171
+ _jsonlCutoff = null;
172
+ constructor(sessionId, streamId, cwd) {
34
173
  this.sessionId = sessionId;
35
174
  this.streamId = streamId;
175
+ this.cwd = cwd;
36
176
  }
37
177
  updateSessionId(sessionId) {
38
178
  this.sessionId = sessionId;
@@ -42,6 +182,65 @@ export class StreamCardBuilder {
42
182
  this.streamId = streamId;
43
183
  this.currentTextCardId = null;
44
184
  }
185
+ /** Clear all accumulated cards. Call after a turn completes and JSONL is flushed. */
186
+ clearCards() {
187
+ this.cards.clear();
188
+ this.toolUseIdToCardId.clear();
189
+ this.agentIdToCardId.clear();
190
+ this.ephemeralCards.clear();
191
+ this.currentTextCardId = null;
192
+ }
193
+ /**
194
+ * Append current in-memory cards to the persisted card history file.
195
+ * Used by memory-mode providers (Codex) to survive reconnects.
196
+ * Call before clearCards() at the end of each turn.
197
+ */
198
+ async persistCards() {
199
+ const cards = this.getCards();
200
+ if (cards.length === 0)
201
+ return;
202
+ // Strip transient fields before persisting
203
+ const cleaned = cards.map(c => {
204
+ const { pendingInput, ...rest } = c;
205
+ if (rest.type === 'assistant_text') {
206
+ return { ...rest, streaming: false };
207
+ }
208
+ return rest;
209
+ });
210
+ const dir = cardHistoryDir();
211
+ const p = cardHistoryPath(this.sessionId);
212
+ // Append to existing history
213
+ let existing = [];
214
+ try {
215
+ if (existsSync(p)) {
216
+ const raw = await readFile(p, 'utf-8');
217
+ const parsed = JSON.parse(raw);
218
+ if (Array.isArray(parsed))
219
+ existing = parsed;
220
+ }
221
+ }
222
+ catch { /* start fresh */ }
223
+ const merged = [...existing, ...cleaned];
224
+ await mkdir(dir, { recursive: true });
225
+ await writeFile(p, JSON.stringify(merged) + '\n');
226
+ }
227
+ /** Snapshot current JSONL byte size so history reads stop before the active turn. */
228
+ async snapshotCutoff() {
229
+ const p = jsonlPath(this.sessionId, this.cwd);
230
+ try {
231
+ const { size } = await stat(p);
232
+ this._jsonlCutoff = size;
233
+ }
234
+ catch {
235
+ this._jsonlCutoff = null;
236
+ }
237
+ }
238
+ get jsonlCutoff() {
239
+ return this._jsonlCutoff;
240
+ }
241
+ set jsonlCutoff(value) {
242
+ this._jsonlCutoff = value;
243
+ }
45
244
  nextId() {
46
245
  return `${this.sessionId}:${this.streamId}:${++this.seq}`;
47
246
  }
@@ -240,13 +439,53 @@ export class StreamCardBuilder {
240
439
  // ============================================================================
241
440
  // buildCardsFromHistory — convert JSONL history into Card[]
242
441
  // ============================================================================
243
- export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit = 50) {
244
- const allMessages = (await getSessionMessages(sessionId, { dir: cwd, includeSystemMessages: true }))
245
- .filter((m) => !m.isSidechain);
246
- const total = allMessages.length;
247
- const tailStart = Math.max(0, total - offset - limit);
248
- const tailEnd = Math.max(0, total - offset);
249
- const sliced = allMessages.slice(tailStart, tailEnd);
442
+ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit = 50, cutoffBytes) {
443
+ const p = jsonlPath(sessionId, cwd);
444
+ if (!existsSync(p))
445
+ return { cards: [], total: 0, hasMore: false };
446
+ let allMessages;
447
+ let total;
448
+ if (cutoffBytes != null) {
449
+ // Active turn: read head of file up to the snapshot byte offset.
450
+ allMessages = (await readMessagesFromJSONL(sessionId, cwd, { headBytes: cutoffBytes }))
451
+ .filter((m) => !m.isSidechain);
452
+ total = allMessages.length;
453
+ }
454
+ else {
455
+ const { size: fileSize } = await stat(p);
456
+ // Estimate: (offset + limit) messages × 3 margin × ~4 KB each
457
+ const tailBytes = (offset + limit) * 3 * 4096;
458
+ if (tailBytes < fileSize) {
459
+ // Large file: streaming count for stable total + tail read for cards
460
+ const [counted, tailMsgs] = await Promise.all([
461
+ countMessagesInJSONL(p),
462
+ readMessagesFromJSONL(sessionId, cwd, { tailBytes }),
463
+ ]);
464
+ const filtered = tailMsgs.filter((m) => !m.isSidechain);
465
+ total = counted;
466
+ if (filtered.length >= offset + limit) {
467
+ allMessages = filtered;
468
+ }
469
+ else {
470
+ // Tail wasn't enough — fall back to full read
471
+ allMessages = (await readMessagesFromJSONL(sessionId, cwd))
472
+ .filter((m) => !m.isSidechain);
473
+ total = allMessages.length;
474
+ }
475
+ }
476
+ else {
477
+ // Small file: read whole thing
478
+ allMessages = (await readMessagesFromJSONL(sessionId, cwd))
479
+ .filter((m) => !m.isSidechain);
480
+ total = allMessages.length;
481
+ }
482
+ }
483
+ // allMessages may be a tail subset — map page positions to local indices.
484
+ // Page wants global positions [total-offset-limit, total-offset].
485
+ // allMessages holds the last `allMessages.length` messages of the file.
486
+ const pageStart = Math.max(0, allMessages.length - offset - limit);
487
+ const pageEnd = Math.max(0, allMessages.length - offset);
488
+ const sliced = allMessages.slice(pageStart, pageEnd);
250
489
  // ── Pass 1: Build indexes from ALL messages ────────────────────────────
251
490
  // toolUseId → toolName (for tool_result lookup across pages)
252
491
  const agentToolUseIds = new Set();
@@ -286,24 +525,11 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
286
525
  }
287
526
  const toolUseIdToAgentId = new Map();
288
527
  try {
289
- const agentIds = await listSubagents(sessionId, { dir: cwd });
290
- // Read each subagent's meta.json to get description, then match to toolUseId
291
- // SDK stores subagent meta at ~/.claude/projects/<cwd-hash>/<sessionId>/subagents/<agentId>.meta.json
292
- const cwdHash = cwd.replace(/\//g, '-').replace(/^-/, '');
293
- const subagentsDir = join(process.env.HOME ?? '', '.claude', 'projects', cwdHash, sessionId, 'subagents');
528
+ const agentIds = await listSubagentIdsFromDisk(sessionId, cwd);
529
+ const subagentsDir = join(claudeProjectDir(cwd), sessionId, 'subagents');
294
530
  for (const agentId of agentIds) {
295
531
  try {
296
- const possiblePaths = [
297
- join(subagentsDir, `${agentId}.meta.json`),
298
- ];
299
- let meta = null;
300
- for (const p of possiblePaths) {
301
- try {
302
- meta = JSON.parse(await readFile(p, 'utf-8'));
303
- break;
304
- }
305
- catch { /* try next */ }
306
- }
532
+ const meta = JSON.parse(await readFile(join(subagentsDir, `${agentId}.meta.json`), 'utf-8'));
307
533
  if (meta?.description && descToToolUseId.has(meta.description)) {
308
534
  toolUseIdToAgentId.set(descToToolUseId.get(meta.description), agentId);
309
535
  }
@@ -324,7 +550,7 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
324
550
  }
325
551
  }
326
552
  // ── Pass 2: Build Cards from sliced messages ───────────────────────────
327
- let seq = tailStart;
553
+ let seq = Math.max(0, total - offset - limit);
328
554
  const nextId = () => `${sessionId}:h:${++seq}`;
329
555
  const cards = [];
330
556
  for (const msg of sliced) {
@@ -445,6 +671,18 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
445
671
  const toolInput = typeof block.input === 'object' && block.input !== null
446
672
  ? block.input
447
673
  : {};
674
+ const result = block.id ? toolResults.get(block.id) : undefined;
675
+ let pairedResult;
676
+ if (result) {
677
+ const truncated = result.content.length > TOOL_RESULT_TRUNCATE_LENGTH;
678
+ pairedResult = {
679
+ content: truncated
680
+ ? result.content.slice(0, TOOL_RESULT_TRUNCATE_LENGTH) + ' [truncated]'
681
+ : result.content,
682
+ isError: result.isError,
683
+ truncated,
684
+ };
685
+ }
448
686
  cards.push({
449
687
  type: 'tool_call',
450
688
  id: nextId(),
@@ -452,6 +690,7 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
452
690
  toolName: block.name ?? block.type,
453
691
  toolInput,
454
692
  toolUseId: block.id ?? nextId(),
693
+ result: pairedResult,
455
694
  });
456
695
  break;
457
696
  }
@@ -487,9 +726,15 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
487
726
  case 'container_upload':
488
727
  cards.push({ type: 'system', id: nextId(), timestamp: Date.now(), text: 'Container upload', subtype: 'info' });
489
728
  break;
490
- // Unknown block types — skip silently
491
- default:
729
+ // Unknown block types — surface as info card so they're visible
730
+ default: {
731
+ const preview = block.text ?? block.content ?? '';
732
+ const previewStr = typeof preview === 'string' ? preview.slice(0, 200) : JSON.stringify(preview).slice(0, 200);
733
+ if (block.type && previewStr) {
734
+ cards.push({ type: 'system', id: nextId(), timestamp: Date.now(), text: `[${block.type}] ${previewStr}`, subtype: 'info' });
735
+ }
492
736
  break;
737
+ }
493
738
  }
494
739
  }
495
740
  }
@@ -497,7 +742,7 @@ export async function buildCardsFromHistory(sessionId, cwd, offset = 0, limit =
497
742
  return {
498
743
  cards,
499
744
  total,
500
- hasMore: tailStart > 0,
745
+ hasMore: total - offset - limit > 0,
501
746
  };
502
747
  }
503
748
  //# sourceMappingURL=cardBuilder.js.map