@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.
- package/dist/ai/artifactStore.d.ts +12 -0
- package/dist/ai/artifactStore.d.ts.map +1 -0
- package/dist/ai/artifactStore.js +143 -0
- package/dist/ai/artifactStore.js.map +1 -0
- package/dist/ai/cardBuilder.d.ts +9 -1
- package/dist/ai/cardBuilder.d.ts.map +1 -1
- package/dist/ai/cardBuilder.js +227 -5
- package/dist/ai/cardBuilder.js.map +1 -1
- package/dist/ai/claudeCliProvider.d.ts +2 -1
- package/dist/ai/claudeCliProvider.d.ts.map +1 -1
- package/dist/ai/claudeCliProvider.js +34 -0
- package/dist/ai/claudeCliProvider.js.map +1 -1
- package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
- package/dist/ai/codexAppServer/cardAdapter.js +38 -1
- package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
- package/dist/ai/codexAppServer/provider.d.ts +49 -2
- package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
- package/dist/ai/codexAppServer/provider.js +77 -3
- package/dist/ai/codexAppServer/provider.js.map +1 -1
- package/dist/ai/codexAppServer/tokenAccounting.d.ts.map +1 -1
- package/dist/ai/codexAppServer/tokenAccounting.js +2 -0
- package/dist/ai/codexAppServer/tokenAccounting.js.map +1 -1
- package/dist/ai/codexMcpProvider.d.ts.map +1 -1
- package/dist/ai/codexMcpProvider.js +59 -3
- package/dist/ai/codexMcpProvider.js.map +1 -1
- package/dist/ai/codexQuota.d.ts +29 -0
- package/dist/ai/codexQuota.d.ts.map +1 -0
- package/dist/ai/codexQuota.js +196 -0
- package/dist/ai/codexQuota.js.map +1 -0
- package/dist/ai/enrichEntry.d.ts.map +1 -1
- package/dist/ai/enrichEntry.js +20 -1
- package/dist/ai/enrichEntry.js.map +1 -1
- package/dist/ai/provider.d.ts +10 -2
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/provider.js.map +1 -1
- package/dist/ai/sandboxMcp.d.ts +1 -0
- package/dist/ai/sandboxMcp.d.ts.map +1 -1
- package/dist/ai/sandboxMcp.js +1 -0
- package/dist/ai/sandboxMcp.js.map +1 -1
- package/dist/ai/sandboxMcpStdio.js +62 -0
- package/dist/ai/sandboxMcpStdio.js.map +1 -1
- package/dist/ai/sessionManager.d.ts +9 -2
- package/dist/ai/sessionManager.d.ts.map +1 -1
- package/dist/ai/sessionManager.js +62 -20
- package/dist/ai/sessionManager.js.map +1 -1
- package/dist/ai/sessionRegistry.d.ts +2 -0
- package/dist/ai/sessionRegistry.d.ts.map +1 -1
- package/dist/ai/sessionRegistry.js +21 -0
- package/dist/ai/sessionRegistry.js.map +1 -1
- package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
- package/dist/handlers/legacyBusAdapter.js +3 -0
- package/dist/handlers/legacyBusAdapter.js.map +1 -1
- package/dist/handlers/messageHandler.d.ts +10 -2
- package/dist/handlers/messageHandler.d.ts.map +1 -1
- package/dist/handlers/messageHandler.js +53 -4
- package/dist/handlers/messageHandler.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/service/contextUsage.d.ts +9 -0
- package/dist/service/contextUsage.d.ts.map +1 -0
- package/dist/service/contextUsage.js +36 -0
- package/dist/service/contextUsage.js.map +1 -0
- package/dist/service/ipcClient.js +1 -1
- package/dist/service/run.d.ts.map +1 -1
- package/dist/service/run.js +31 -4
- package/dist/service/run.js.map +1 -1
- package/dist/service/singleton.d.ts +1 -0
- package/dist/service/singleton.d.ts.map +1 -1
- package/dist/service/singleton.js +3 -0
- package/dist/service/singleton.js.map +1 -1
- package/dist/service/types.js +1 -1
- package/dist/storage/eventStore.d.ts +2 -0
- package/dist/storage/eventStore.d.ts.map +1 -1
- package/dist/storage/eventStore.js +14 -0
- package/dist/storage/eventStore.js.map +1 -1
- 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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"}
|
package/dist/ai/cardBuilder.d.ts
CHANGED
|
@@ -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":"AAEA,OAAO,KAAK,EACV,UAAU,EAEV,IAAI,EACJ,MAAM,EACN,SAAS,EAKT,mBAAmB,EAInB,sBAAsB,EACtB,kBAAkB,EAClB,UAAU,
|
|
1
|
+
{"version":3,"file":"cardBuilder.d.ts","sourceRoot":"","sources":["../../src/ai/cardBuilder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EAEV,IAAI,EACJ,MAAM,EACN,SAAS,EAKT,mBAAmB,EAInB,sBAAsB,EACtB,kBAAkB,EAClB,UAAU,EACV,mBAAmB,EACpB,MAAM,2BAA2B,CAAC;AAsKnC;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAY3E;AA4FD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhE;AAqJD;;0BAE0B;AAC1B,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,EACrD,EAAE,EAAE,MAAM,GACT,UAAU,CAWZ;AAED;;2EAE2E;AAC3E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,EACD,EAAE,EAAE,MAAM,GACT,UAAU,CA0BZ;AAMD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,KAAK,CAA2B;IACxC,oEAAoE;IACpE,OAAO,CAAC,iBAAiB,CAA6B;IACtD,2EAA2E;IAC3E,OAAO,CAAC,kBAAkB,CAA6B;IACvD,gEAAgE;IAChE,OAAO,CAAC,eAAe,CAA6B;IACpD,yEAAyE;IACzE,OAAO,CAAC,cAAc,CAAqB;IAC3C;4EACwE;IACxE,OAAO,CAAC,iBAAiB,CAIpB;IACL;kFAC8E;IAC9E,OAAO,CAAC,oBAAoB,CAAuB;IACnD,uFAAuF;IACvF,OAAO,CAAC,2BAA2B,CAA6B;IAChE,qEAAqE;IACrE,OAAO,CAAC,iBAAiB,CAAuB;IAChD,yFAAyF;IACzF,OAAO,CAAC,YAAY,CAAuB;IAC3C;4EACwE;IACxE,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,qBAAqB,CAAoC;gBAErD,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAK1C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIxC,uBAAuB,CAAC,OAAO,UAAO,GAAG,IAAI;IAI7C;;;sDAGkD;IAClD,qBAAqB,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,IAAI;IAInD,sEAAsE;IACtE,YAAY,IAAI,IAAI;IAIpB,qFAAqF;IACrF,UAAU,IAAI,IAAI;IAYlB;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAenC;;oEAEgE;IAC1D,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAStC,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;YAQ/B,gBAAgB;IAoB9B,qFAAqF;IAC/E,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAUrC;;;;;;;;;;;;;;;OAeG;IACG,qBAAqB,CAAC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA4C7G;+DAC2D;IAC3D,mBAAmB,IAAI,IAAI;IAI3B,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IAED,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAEnC;IAED,OAAO,CAAC,uBAAuB;IAa/B,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;IAQnB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,SAAS,UAAU,EAAE,GAAG,SAAS;IAuBzE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;IAMtC;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;IAUtC,8DAA8D;IAC9D,qBAAqB,IAAI,SAAS,GAAG,IAAI;IAOzC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS;IA6B3F;;;OAGG;IACH,sBAAsB,CACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,sBAAsB;IACpC,uFAAuF;IACvF,SAAS,UAAQ,GAChB,SAAS;IAqBZ,uEAAuE;IACvE,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,sBAAsB,GAAG,SAAS,GAAG,IAAI;IAMhG;0FACsF;IACtF,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI;IAkBxF,8CAA8C;IAC9C,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAO1D,qEAAqE;IACrE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIvC,gFAAgF;IAChF,QAAQ,IAAI,IAAI,EAAE;IAIlB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI;IAyBlF,mEAAmE;IACnE,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI;IAMpF,iFAAiF;IACjF,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE;QAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,IAAI;IAIR,aAAa,CACX,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3E,SAAS;IA4BZ,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAUhI,WAAW,CACT,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,EAC1C,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,GAAG,IAAI;IAQnB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS;IAKrG;sCACkC;IAClC,YAAY,CAAC,GAAG,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS;IAI9E,kFAAkF;IAClF,eAAe,CAAC,GAAG,EAAE;QACnB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,GAAG,SAAS;IAIb,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS;IAYnG,QAAQ,CAAC,GAAG,EAAE,mBAAmB,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS;IAkBtE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS;IAY9E,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;CAGtC;AAsDD,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,SAAI,EACV,KAAK,SAAK,EACV,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAmjB9B"}
|
package/dist/ai/cardBuilder.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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',
|