@sumicom/quicksave 0.8.11 → 0.8.13

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 (75) hide show
  1. package/dist/ai/artifactStore.d.ts +12 -0
  2. package/dist/ai/artifactStore.d.ts.map +1 -0
  3. package/dist/ai/artifactStore.js +143 -0
  4. package/dist/ai/artifactStore.js.map +1 -0
  5. package/dist/ai/cardBuilder.d.ts +9 -1
  6. package/dist/ai/cardBuilder.d.ts.map +1 -1
  7. package/dist/ai/cardBuilder.js +227 -5
  8. package/dist/ai/cardBuilder.js.map +1 -1
  9. package/dist/ai/claudeCliProvider.d.ts +2 -1
  10. package/dist/ai/claudeCliProvider.d.ts.map +1 -1
  11. package/dist/ai/claudeCliProvider.js +34 -0
  12. package/dist/ai/claudeCliProvider.js.map +1 -1
  13. package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
  14. package/dist/ai/codexAppServer/cardAdapter.js +38 -1
  15. package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
  16. package/dist/ai/codexAppServer/provider.d.ts +49 -2
  17. package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
  18. package/dist/ai/codexAppServer/provider.js +77 -3
  19. package/dist/ai/codexAppServer/provider.js.map +1 -1
  20. package/dist/ai/codexAppServer/tokenAccounting.d.ts.map +1 -1
  21. package/dist/ai/codexAppServer/tokenAccounting.js +2 -0
  22. package/dist/ai/codexAppServer/tokenAccounting.js.map +1 -1
  23. package/dist/ai/codexMcpProvider.d.ts.map +1 -1
  24. package/dist/ai/codexMcpProvider.js +59 -3
  25. package/dist/ai/codexMcpProvider.js.map +1 -1
  26. package/dist/ai/codexQuota.d.ts +29 -0
  27. package/dist/ai/codexQuota.d.ts.map +1 -0
  28. package/dist/ai/codexQuota.js +196 -0
  29. package/dist/ai/codexQuota.js.map +1 -0
  30. package/dist/ai/enrichEntry.d.ts.map +1 -1
  31. package/dist/ai/enrichEntry.js +20 -1
  32. package/dist/ai/enrichEntry.js.map +1 -1
  33. package/dist/ai/provider.d.ts +10 -2
  34. package/dist/ai/provider.d.ts.map +1 -1
  35. package/dist/ai/provider.js.map +1 -1
  36. package/dist/ai/sandboxMcp.d.ts +1 -0
  37. package/dist/ai/sandboxMcp.d.ts.map +1 -1
  38. package/dist/ai/sandboxMcp.js +1 -0
  39. package/dist/ai/sandboxMcp.js.map +1 -1
  40. package/dist/ai/sandboxMcpStdio.js +62 -0
  41. package/dist/ai/sandboxMcpStdio.js.map +1 -1
  42. package/dist/ai/sessionManager.d.ts +9 -2
  43. package/dist/ai/sessionManager.d.ts.map +1 -1
  44. package/dist/ai/sessionManager.js +62 -20
  45. package/dist/ai/sessionManager.js.map +1 -1
  46. package/dist/ai/sessionRegistry.d.ts +2 -0
  47. package/dist/ai/sessionRegistry.d.ts.map +1 -1
  48. package/dist/ai/sessionRegistry.js +21 -0
  49. package/dist/ai/sessionRegistry.js.map +1 -1
  50. package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
  51. package/dist/handlers/legacyBusAdapter.js +3 -0
  52. package/dist/handlers/legacyBusAdapter.js.map +1 -1
  53. package/dist/handlers/messageHandler.d.ts +10 -2
  54. package/dist/handlers/messageHandler.d.ts.map +1 -1
  55. package/dist/handlers/messageHandler.js +53 -4
  56. package/dist/handlers/messageHandler.js.map +1 -1
  57. package/dist/index.js +2 -2
  58. package/dist/service/contextUsage.d.ts +9 -0
  59. package/dist/service/contextUsage.d.ts.map +1 -0
  60. package/dist/service/contextUsage.js +36 -0
  61. package/dist/service/contextUsage.js.map +1 -0
  62. package/dist/service/ipcClient.js +1 -1
  63. package/dist/service/run.d.ts.map +1 -1
  64. package/dist/service/run.js +31 -4
  65. package/dist/service/run.js.map +1 -1
  66. package/dist/service/singleton.d.ts +1 -0
  67. package/dist/service/singleton.d.ts.map +1 -1
  68. package/dist/service/singleton.js +3 -0
  69. package/dist/service/singleton.js.map +1 -1
  70. package/dist/service/types.js +1 -1
  71. package/dist/storage/eventStore.d.ts +2 -0
  72. package/dist/storage/eventStore.d.ts.map +1 -1
  73. package/dist/storage/eventStore.js +14 -0
  74. package/dist/storage/eventStore.js.map +1 -1
  75. package/package.json +3 -3
@@ -0,0 +1,12 @@
1
+ import type { Artifact, ArtifactMetadata, MarkdownArtifactRef } from '@sumicom/quicksave-shared';
2
+ export interface PublishMarkdownArtifactArgs {
3
+ sessionId: string;
4
+ cwd: string;
5
+ sourcePath: string;
6
+ title?: string;
7
+ }
8
+ export declare function publishMarkdownArtifact(args: PublishMarkdownArtifactArgs): Promise<MarkdownArtifactRef>;
9
+ export declare function loadArtifactBySession(sessionId: string, artifactId: string): Promise<Artifact | null>;
10
+ export declare function listSessionArtifacts(cwd: string, sessionId: string): Promise<ArtifactMetadata[]>;
11
+ export declare function removeSessionArtifacts(cwd: string, sessionId: string): Promise<void>;
12
+ //# sourceMappingURL=artifactStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactStore.d.ts","sourceRoot":"","sources":["../../src/ai/artifactStore.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAuCjG,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,2BAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuC7G;AAED,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA4B3G;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiBtG;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM1F"}
@@ -0,0 +1,143 @@
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
3
+ // ============================================================================
4
+ // Persistent artifact store on the agent.
5
+ //
6
+ // Generated reports live under:
7
+ //
8
+ // <state>/artifacts/<encoded-project-path>/<sessionId>/<artifactId>/
9
+ // artifact.md
10
+ // meta.json
11
+ //
12
+ // Tool results and cards carry only ArtifactMetadata. The PWA fetches content
13
+ // on demand through `artifact:fetch`, keeping large reports out of model
14
+ // context and out of card snapshots.
15
+ // ============================================================================
16
+ import { copyFile, mkdir, readFile, readdir, realpath, rm, stat, writeFile } from 'fs/promises';
17
+ import { basename, extname, isAbsolute, join, resolve, sep } from 'path';
18
+ import { randomUUID } from 'crypto';
19
+ import { getArtifactsDir } from '../service/singleton.js';
20
+ const ARTIFACT_FILENAME = 'artifact.md';
21
+ const META_FILENAME = 'meta.json';
22
+ const MARKDOWN_ARTIFACT_MAX_BYTES = 10 * 1024 * 1024;
23
+ const MARKDOWN_ARTIFACT_EXTENSIONS = ['.md', '.markdown'];
24
+ function encodeProjectPath(cwd) {
25
+ return cwd.replace(/[\\/]/g, '-').replace(/:/g, '');
26
+ }
27
+ function dirFor(cwd, sessionId) {
28
+ return join(getArtifactsDir(), encodeProjectPath(cwd), sessionId);
29
+ }
30
+ function artifactDirFor(cwd, sessionId, artifactId) {
31
+ return join(dirFor(cwd, sessionId), artifactId);
32
+ }
33
+ function storedMarkdownPath(cwd, sessionId, artifactId) {
34
+ return join(artifactDirFor(cwd, sessionId, artifactId), ARTIFACT_FILENAME);
35
+ }
36
+ function metaPathFor(cwd, sessionId, artifactId) {
37
+ return join(artifactDirFor(cwd, sessionId, artifactId), META_FILENAME);
38
+ }
39
+ function isPathInside(child, parent) {
40
+ return child === parent || child.startsWith(parent.endsWith(sep) ? parent : parent + sep);
41
+ }
42
+ function assertMarkdownPath(filePath) {
43
+ const ext = extname(filePath).toLowerCase();
44
+ if (!MARKDOWN_ARTIFACT_EXTENSIONS.includes(ext)) {
45
+ throw new Error('Only .md and .markdown artifacts are supported');
46
+ }
47
+ }
48
+ export async function publishMarkdownArtifact(args) {
49
+ const cwdReal = await realpath(args.cwd);
50
+ const requestedSource = isAbsolute(args.sourcePath)
51
+ ? args.sourcePath
52
+ : resolve(cwdReal, args.sourcePath);
53
+ assertMarkdownPath(requestedSource);
54
+ const sourceReal = await realpath(requestedSource);
55
+ if (!isPathInside(sourceReal, cwdReal)) {
56
+ throw new Error('Artifact source must be inside the project directory');
57
+ }
58
+ const st = await stat(sourceReal);
59
+ if (!st.isFile()) {
60
+ throw new Error('Artifact source must be a regular file');
61
+ }
62
+ if (st.size > MARKDOWN_ARTIFACT_MAX_BYTES) {
63
+ throw new Error(`Markdown artifact exceeds ${MARKDOWN_ARTIFACT_MAX_BYTES} byte limit`);
64
+ }
65
+ const artifactId = randomUUID();
66
+ const artifactDir = artifactDirFor(cwdReal, args.sessionId, artifactId);
67
+ await mkdir(artifactDir, { recursive: true });
68
+ await copyFile(sourceReal, storedMarkdownPath(cwdReal, args.sessionId, artifactId));
69
+ const meta = {
70
+ refKind: 'artifact',
71
+ artifactId,
72
+ sessionId: args.sessionId,
73
+ cwd: cwdReal,
74
+ kind: 'markdown',
75
+ title: args.title?.trim() || basename(sourceReal),
76
+ mimeType: 'text/markdown',
77
+ size: st.size,
78
+ createdAt: Date.now(),
79
+ sourcePath: sourceReal,
80
+ };
81
+ await writeFile(metaPathFor(cwdReal, args.sessionId, artifactId), JSON.stringify(meta, null, 2));
82
+ return meta;
83
+ }
84
+ export async function loadArtifactBySession(sessionId, artifactId) {
85
+ const base = getArtifactsDir();
86
+ let projectDirs;
87
+ try {
88
+ projectDirs = await readdir(base);
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ for (const projectDir of projectDirs) {
94
+ const artifactDir = join(base, projectDir, sessionId, artifactId);
95
+ try {
96
+ const [metaRaw, bytes] = await Promise.all([
97
+ readFile(join(artifactDir, META_FILENAME), 'utf8'),
98
+ readFile(join(artifactDir, ARTIFACT_FILENAME)),
99
+ ]);
100
+ const meta = JSON.parse(metaRaw);
101
+ if (meta.sessionId !== sessionId || meta.artifactId !== artifactId)
102
+ continue;
103
+ return {
104
+ ...meta,
105
+ contentBase64: bytes.toString('base64'),
106
+ };
107
+ }
108
+ catch {
109
+ // Keep scanning: sessions can share ids across different project dirs in
110
+ // malformed or hand-edited state, and missing files are local-only.
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ export async function listSessionArtifacts(cwd, sessionId) {
116
+ let artifactIds;
117
+ try {
118
+ artifactIds = await readdir(dirFor(cwd, sessionId));
119
+ }
120
+ catch {
121
+ return [];
122
+ }
123
+ const metas = [];
124
+ for (const artifactId of artifactIds) {
125
+ try {
126
+ const raw = await readFile(metaPathFor(cwd, sessionId, artifactId), 'utf8');
127
+ metas.push(JSON.parse(raw));
128
+ }
129
+ catch {
130
+ // skip unreadable metas
131
+ }
132
+ }
133
+ return metas.sort((a, b) => a.createdAt - b.createdAt);
134
+ }
135
+ export async function removeSessionArtifacts(cwd, sessionId) {
136
+ try {
137
+ await rm(dirFor(cwd, sessionId), { recursive: true, force: true });
138
+ }
139
+ catch {
140
+ // best effort
141
+ }
142
+ }
143
+ //# sourceMappingURL=artifactStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactStore.js","sourceRoot":"","sources":["../../src/ai/artifactStore.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { Attachment, Card, CardId, CardEvent, CardHistoryResponse, PendingInputAttachment, GeneratedImageCard, SystemCard } from '@sumicom/quicksave-shared';
1
+ import type { Attachment, Card, CardId, CardEvent, CardHistoryResponse, PendingInputAttachment, GeneratedImageCard, SystemCard, MarkdownArtifactRef } from '@sumicom/quicksave-shared';
2
2
  /**
3
3
  * Load persisted card history for a memory-mode session.
4
4
  * Returns cards in insertion order, or empty array if none exist.
@@ -33,6 +33,8 @@ export declare class StreamCardBuilder {
33
33
  private cards;
34
34
  /** tool_use_id → CardId, for pairing tool_result to ToolCallCard */
35
35
  private toolUseIdToCardId;
36
+ /** artifactId → CardId, for de-duping generated display artifact cards. */
37
+ private artifactIdToCardId;
36
38
  /** agentId (task_id) → CardId, for matching subagent updates */
37
39
  private agentIdToCardId;
38
40
  /** Cards created for subagent permissions — removed after resolution. */
@@ -52,8 +54,11 @@ export declare class StreamCardBuilder {
52
54
  /** Token identifying an in-flight deferred clear. A later call (new turn, cancel)
53
55
  * replaces this token, causing the pending polling task to bail out. */
54
56
  private _pendingClearToken;
57
+ private persistMemoryCards;
58
+ private cardHistoryWriteQueue;
55
59
  constructor(sessionId: string, cwd: string);
56
60
  updateSessionId(sessionId: string): void;
61
+ enableMemoryPersistence(enabled?: boolean): void;
57
62
  /** Continue the local card id counter after persisted memory-mode history.
58
63
  * Used on cold resume after daemon restart so new Codex/OpenCode cards
59
64
  * append after existing cards instead of reusing `sessionId:1`, `:2`, ...
@@ -73,6 +78,7 @@ export declare class StreamCardBuilder {
73
78
  * injects a user prompt into an already-running turn; the card must survive
74
79
  * refresh before the turn's normal end-of-turn persist runs. */
75
80
  persistCard(card: Card): Promise<void>;
81
+ flushCardHistoryWrites(): Promise<void>;
76
82
  private persistCardBatch;
77
83
  /** Snapshot current JSONL byte size so history reads stop before the active turn. */
78
84
  snapshotCutoff(): Promise<void>;
@@ -102,6 +108,7 @@ export declare class StreamCardBuilder {
102
108
  cancelDeferredClear(): void;
103
109
  get jsonlCutoff(): number | null;
104
110
  set jsonlCutoff(value: number | null);
111
+ private enqueueCardHistoryEntry;
105
112
  private nextId;
106
113
  private addEvent;
107
114
  private updateEvent;
@@ -172,6 +179,7 @@ export declare class StreamCardBuilder {
172
179
  stopReason?: unknown;
173
180
  }): CardEvent;
174
181
  generatedImage(prompt: string, status: GeneratedImageCard['status'], savedPath?: string): CardEvent;
182
+ artifact(ref: MarkdownArtifactRef, afterToolUseId?: string): CardEvent;
175
183
  recoverySuggested(reason: string, action: 'compact', label: string): CardEvent;
176
184
  errorMessage(text: string): CardEvent;
177
185
  }
@@ -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,4 +1,4 @@
1
- import { readFile, readdir, writeFile, mkdir, stat, open } from 'fs/promises';
1
+ import { appendFile, readFile, readdir, writeFile, mkdir, stat, open, rename, utimes } from 'fs/promises';
2
2
  import { join } from 'path';
3
3
  import { createReadStream, existsSync } from 'fs';
4
4
  import { createInterface } from 'readline';
@@ -46,6 +46,9 @@ const TOOL_RESULT_TRUNCATE_LENGTH = 500;
46
46
  function cardHistoryPath(sessionId) {
47
47
  return join(getCardHistoryDir(), `${sessionId}.json`);
48
48
  }
49
+ function cardHistoryLogPath(sessionId) {
50
+ return join(getCardHistoryDir(), `${sessionId}.jsonl`);
51
+ }
49
52
  function cleanPersistedCard(card) {
50
53
  const { pendingInput, ...rest } = card;
51
54
  void pendingInput;
@@ -87,23 +90,181 @@ function maxCardSequenceForSession(cards, sessionId) {
87
90
  }
88
91
  return max;
89
92
  }
93
+ async function statOrNull(path) {
94
+ try {
95
+ return await stat(path);
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ function statTimeMs(value) {
102
+ if (typeof value === 'bigint')
103
+ return Number(value);
104
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
105
+ }
106
+ function isSeedOnlyCardHistoryLog(raw) {
107
+ let sawSeed = false;
108
+ for (const line of raw.split('\n')) {
109
+ if (!line.trim())
110
+ continue;
111
+ let entry;
112
+ try {
113
+ entry = JSON.parse(line);
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ if (entry.op !== 'seed')
119
+ return false;
120
+ sawSeed = true;
121
+ }
122
+ return sawSeed;
123
+ }
124
+ async function repairMigratedCardHistoryLogMtime(sessionId, raw) {
125
+ if (!isSeedOnlyCardHistoryLog(raw))
126
+ return;
127
+ const logPath = cardHistoryLogPath(sessionId);
128
+ const logStat = await statOrNull(logPath);
129
+ if (!logStat)
130
+ return;
131
+ let files;
132
+ try {
133
+ files = await readdir(getCardHistoryDir());
134
+ }
135
+ catch {
136
+ return;
137
+ }
138
+ const prefix = `${sessionId}.json.migrated-`;
139
+ const candidates = files
140
+ .filter((file) => file.startsWith(prefix))
141
+ .map((file) => ({
142
+ file,
143
+ migratedAtMs: Number(file.slice(prefix.length)),
144
+ }))
145
+ .filter((candidate) => Number.isFinite(candidate.migratedAtMs))
146
+ .sort((a, b) => b.migratedAtMs - a.migratedAtMs);
147
+ const logMtimeMs = statTimeMs(logStat.mtimeMs);
148
+ for (const candidate of candidates) {
149
+ if (Math.abs(logMtimeMs - candidate.migratedAtMs) > 5000)
150
+ continue;
151
+ const migratedStat = await statOrNull(join(getCardHistoryDir(), candidate.file));
152
+ const targetMtimeMs = statTimeMs(migratedStat?.mtimeMs);
153
+ if (targetMtimeMs <= 0 || targetMtimeMs >= logMtimeMs)
154
+ return;
155
+ const targetAtimeMs = Math.max(statTimeMs(migratedStat?.atimeMs), targetMtimeMs);
156
+ await utimes(logPath, new Date(targetAtimeMs), new Date(targetMtimeMs));
157
+ return;
158
+ }
159
+ }
90
160
  /**
91
161
  * Load persisted card history for a memory-mode session.
92
162
  * Returns cards in insertion order, or empty array if none exist.
93
163
  */
94
164
  export async function loadPersistedCards(sessionId) {
95
- const p = cardHistoryPath(sessionId);
165
+ const migratedCards = await migrateLegacyCardHistory(sessionId);
166
+ const p = cardHistoryLogPath(sessionId);
96
167
  if (!existsSync(p))
97
- return [];
168
+ return migratedCards ?? [];
98
169
  try {
99
170
  const raw = await readFile(p, 'utf-8');
171
+ await repairMigratedCardHistoryLogMtime(sessionId, raw);
172
+ const replayed = replayCardHistoryLog(raw);
173
+ return migratedCards ? mergeCardsById(migratedCards, replayed) : replayed;
174
+ }
175
+ catch {
176
+ return migratedCards ?? [];
177
+ }
178
+ }
179
+ async function migrateLegacyCardHistory(sessionId) {
180
+ const legacyPath = cardHistoryPath(sessionId);
181
+ if (!existsSync(legacyPath))
182
+ return null;
183
+ try {
184
+ const legacyStat = await statOrNull(legacyPath);
185
+ const raw = await readFile(legacyPath, 'utf-8');
100
186
  const data = JSON.parse(raw);
101
- return Array.isArray(data) ? sortCardsChronologically(data) : [];
187
+ if (!Array.isArray(data) || data.length === 0)
188
+ return null;
189
+ const cards = sortCardsChronologically(data).map(cleanPersistedCard);
190
+ try {
191
+ const logPath = cardHistoryLogPath(sessionId);
192
+ const existingLogStat = existsSync(logPath) ? await statOrNull(logPath) : null;
193
+ await mkdir(getCardHistoryDir(), { recursive: true });
194
+ await appendFile(logPath, JSON.stringify({ op: 'seed', cards }) + '\n');
195
+ const targetMtimeMs = Math.max(statTimeMs(legacyStat?.mtimeMs), statTimeMs(existingLogStat?.mtimeMs));
196
+ if (targetMtimeMs > 0) {
197
+ const targetAtimeMs = Math.max(statTimeMs(legacyStat?.atimeMs), statTimeMs(existingLogStat?.atimeMs), targetMtimeMs);
198
+ await utimes(logPath, new Date(targetAtimeMs), new Date(targetMtimeMs));
199
+ }
200
+ await rename(legacyPath, `${legacyPath}.migrated-${Date.now()}`);
201
+ }
202
+ catch {
203
+ // Keep the legacy file as fallback; the next read can retry migration.
204
+ }
205
+ return cards;
102
206
  }
103
207
  catch {
104
- return [];
208
+ return null;
105
209
  }
106
210
  }
211
+ function replayCardHistoryLog(raw) {
212
+ const cardsById = new Map();
213
+ for (const line of raw.split('\n')) {
214
+ if (!line.trim())
215
+ continue;
216
+ let entry;
217
+ try {
218
+ entry = JSON.parse(line);
219
+ }
220
+ catch {
221
+ continue;
222
+ }
223
+ switch (entry.op) {
224
+ case 'seed':
225
+ for (const card of entry.cards ?? []) {
226
+ if (!card?.id || cardsById.has(card.id))
227
+ continue;
228
+ cardsById.set(card.id, cleanPersistedCard(card));
229
+ }
230
+ break;
231
+ case 'upsert':
232
+ if (entry.card?.id)
233
+ cardsById.set(entry.card.id, cleanPersistedCard(entry.card));
234
+ break;
235
+ case 'patch': {
236
+ const existing = cardsById.get(entry.cardId);
237
+ if (!existing)
238
+ break;
239
+ const bag = existing;
240
+ for (const [key, value] of Object.entries(entry.patch ?? {})) {
241
+ if (value === null)
242
+ delete bag[key];
243
+ else
244
+ bag[key] = value;
245
+ }
246
+ cardsById.set(entry.cardId, cleanPersistedCard(existing));
247
+ break;
248
+ }
249
+ case 'append_text': {
250
+ const existing = cardsById.get(entry.cardId);
251
+ if (existing && 'text' in existing) {
252
+ existing.text += entry.text;
253
+ cardsById.set(entry.cardId, cleanPersistedCard(existing));
254
+ }
255
+ break;
256
+ }
257
+ case 'remove':
258
+ cardsById.delete(entry.cardId);
259
+ break;
260
+ }
261
+ }
262
+ return sortCardsChronologically(Array.from(cardsById.values()));
263
+ }
264
+ async function appendCardHistoryEntry(sessionId, entry) {
265
+ await mkdir(getCardHistoryDir(), { recursive: true });
266
+ await appendFile(cardHistoryLogPath(sessionId), JSON.stringify(entry) + '\n');
267
+ }
107
268
  // ── Direct JSONL file reading (replaces SDK getSessionMessages/listSubagents) ──
108
269
  export function encodeCwdPath(cwd) {
109
270
  return cwd.replace(/\//g, '-');
@@ -323,6 +484,8 @@ export class StreamCardBuilder {
323
484
  cards = new Map();
324
485
  /** tool_use_id → CardId, for pairing tool_result to ToolCallCard */
325
486
  toolUseIdToCardId = new Map();
487
+ /** artifactId → CardId, for de-duping generated display artifact cards. */
488
+ artifactIdToCardId = new Map();
326
489
  /** agentId (task_id) → CardId, for matching subagent updates */
327
490
  agentIdToCardId = new Map();
328
491
  /** Cards created for subagent permissions — removed after resolution. */
@@ -342,6 +505,8 @@ export class StreamCardBuilder {
342
505
  /** Token identifying an in-flight deferred clear. A later call (new turn, cancel)
343
506
  * replaces this token, causing the pending polling task to bail out. */
344
507
  _pendingClearToken = null;
508
+ persistMemoryCards = false;
509
+ cardHistoryWriteQueue = Promise.resolve();
345
510
  constructor(sessionId, cwd) {
346
511
  this.sessionId = sessionId;
347
512
  this.cwd = cwd;
@@ -349,6 +514,9 @@ export class StreamCardBuilder {
349
514
  updateSessionId(sessionId) {
350
515
  this.sessionId = sessionId;
351
516
  }
517
+ enableMemoryPersistence(enabled = true) {
518
+ this.persistMemoryCards = enabled;
519
+ }
352
520
  /** Continue the local card id counter after persisted memory-mode history.
353
521
  * Used on cold resume after daemon restart so new Codex/OpenCode cards
354
522
  * append after existing cards instead of reusing `sessionId:1`, `:2`, ...
@@ -364,6 +532,7 @@ export class StreamCardBuilder {
364
532
  clearCards() {
365
533
  this.cards.clear();
366
534
  this.toolUseIdToCardId.clear();
535
+ this.artifactIdToCardId.clear();
367
536
  this.agentIdToCardId.clear();
368
537
  this.ephemeralCards.clear();
369
538
  this.agentToolUseStash.clear();
@@ -380,14 +549,34 @@ export class StreamCardBuilder {
380
549
  const cards = this.getCards();
381
550
  if (cards.length === 0)
382
551
  return;
552
+ if (this.persistMemoryCards) {
553
+ for (const card of cards) {
554
+ this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
555
+ }
556
+ await this.flushCardHistoryWrites();
557
+ return;
558
+ }
383
559
  await this.persistCardBatch(cards);
384
560
  }
385
561
  /** Persist a single card immediately. Used when a memory-mode provider
386
562
  * injects a user prompt into an already-running turn; the card must survive
387
563
  * refresh before the turn's normal end-of-turn persist runs. */
388
564
  async persistCard(card) {
565
+ if (this.persistMemoryCards) {
566
+ this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
567
+ await this.flushCardHistoryWrites();
568
+ return;
569
+ }
389
570
  await this.persistCardBatch([card]);
390
571
  }
572
+ async flushCardHistoryWrites() {
573
+ try {
574
+ await this.cardHistoryWriteQueue;
575
+ }
576
+ catch {
577
+ // Individual write failures are intentionally swallowed in the queue.
578
+ }
579
+ }
391
580
  async persistCardBatch(cards) {
392
581
  if (cards.length === 0)
393
582
  return;
@@ -476,6 +665,7 @@ export class StreamCardBuilder {
476
665
  this._jsonlCutoff = lastSize >= 0 ? lastSize : null;
477
666
  this.cards.clear();
478
667
  this.toolUseIdToCardId.clear();
668
+ this.artifactIdToCardId.clear();
479
669
  this.agentIdToCardId.clear();
480
670
  this.ephemeralCards.clear();
481
671
  this.agentToolUseStash.clear();
@@ -494,11 +684,23 @@ export class StreamCardBuilder {
494
684
  set jsonlCutoff(value) {
495
685
  this._jsonlCutoff = value;
496
686
  }
687
+ enqueueCardHistoryEntry(entry) {
688
+ if (!this.persistMemoryCards || this.sessionId === 'pending')
689
+ return;
690
+ const sessionId = this.sessionId;
691
+ this.cardHistoryWriteQueue = this.cardHistoryWriteQueue
692
+ .catch(() => undefined)
693
+ .then(() => appendCardHistoryEntry(sessionId, entry))
694
+ .catch((err) => {
695
+ console.warn(`[card-history] append failed for session=${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
696
+ });
697
+ }
497
698
  nextId() {
498
699
  return `${this.sessionId}:${++this.seq}`;
499
700
  }
500
701
  addEvent(card, afterCardId) {
501
702
  this.cards.set(card.id, card);
703
+ this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
502
704
  return { type: 'add', sessionId: this.sessionId, card, afterCardId };
503
705
  }
504
706
  updateEvent(cardId, patch) {
@@ -515,6 +717,7 @@ export class StreamCardBuilder {
515
717
  else
516
718
  bag[key] = value;
517
719
  }
720
+ this.enqueueCardHistoryEntry({ op: 'patch', cardId, patch });
518
721
  }
519
722
  return { type: 'update', sessionId: this.sessionId, cardId, patch };
520
723
  }
@@ -522,11 +725,13 @@ export class StreamCardBuilder {
522
725
  const existing = this.cards.get(cardId);
523
726
  if (existing && 'text' in existing) {
524
727
  existing.text += text;
728
+ this.enqueueCardHistoryEntry({ op: 'append_text', cardId, text });
525
729
  }
526
730
  return { type: 'append_text', sessionId: this.sessionId, cardId, text };
527
731
  }
528
732
  removeEvent(cardId) {
529
733
  this.cards.delete(cardId);
734
+ this.enqueueCardHistoryEntry({ op: 'remove', cardId });
530
735
  return { type: 'remove', sessionId: this.sessionId, cardId };
531
736
  }
532
737
  // ── Public: produce CardEvents from SDK data ────────────────────────────
@@ -780,6 +985,23 @@ export class StreamCardBuilder {
780
985
  };
781
986
  return this.addEvent(card);
782
987
  }
988
+ artifact(ref, afterToolUseId) {
989
+ this.currentTextCardId = null;
990
+ const existingCardId = this.artifactIdToCardId.get(ref.artifactId);
991
+ if (existingCardId) {
992
+ return this.updateEvent(existingCardId, { artifact: ref });
993
+ }
994
+ const id = `${this.sessionId}:artifact:${ref.artifactId}`;
995
+ this.artifactIdToCardId.set(ref.artifactId, id);
996
+ const card = {
997
+ type: 'artifact',
998
+ id,
999
+ timestamp: Date.now(),
1000
+ artifact: ref,
1001
+ };
1002
+ const afterCardId = afterToolUseId ? this.toolUseIdToCardId.get(afterToolUseId) : undefined;
1003
+ return this.addEvent(card, afterCardId);
1004
+ }
783
1005
  recoverySuggested(reason, action, label) {
784
1006
  const card = {
785
1007
  type: 'recovery_suggested',