@sumicom/quicksave 0.8.10 → 0.8.12
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 +14 -1
- package/dist/ai/cardBuilder.d.ts.map +1 -1
- package/dist/ai/cardBuilder.js +184 -6
- 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 +53 -2
- package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
- package/dist/ai/codexAppServer/provider.js +81 -7
- 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 +2 -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 +7 -2
- package/dist/ai/sessionManager.d.ts.map +1 -1
- package/dist/ai/sessionManager.js +40 -9
- package/dist/ai/sessionManager.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 +48 -3
- 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 +26 -3
- 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/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,16 @@ 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;
|
|
62
|
+
/** Continue the local card id counter after persisted memory-mode history.
|
|
63
|
+
* Used on cold resume after daemon restart so new Codex/OpenCode cards
|
|
64
|
+
* append after existing cards instead of reusing `sessionId:1`, `:2`, ...
|
|
65
|
+
* and overwriting or reordering older history. */
|
|
66
|
+
seedSequenceFromCards(cards: readonly Card[]): void;
|
|
57
67
|
/** Start a new turn: reset per-turn state, keep accumulated cards. */
|
|
58
68
|
startNewTurn(): void;
|
|
59
69
|
/** Clear all accumulated cards. Call after a turn completes and JSONL is flushed. */
|
|
@@ -68,6 +78,7 @@ export declare class StreamCardBuilder {
|
|
|
68
78
|
* injects a user prompt into an already-running turn; the card must survive
|
|
69
79
|
* refresh before the turn's normal end-of-turn persist runs. */
|
|
70
80
|
persistCard(card: Card): Promise<void>;
|
|
81
|
+
flushCardHistoryWrites(): Promise<void>;
|
|
71
82
|
private persistCardBatch;
|
|
72
83
|
/** Snapshot current JSONL byte size so history reads stop before the active turn. */
|
|
73
84
|
snapshotCutoff(): Promise<void>;
|
|
@@ -97,6 +108,7 @@ export declare class StreamCardBuilder {
|
|
|
97
108
|
cancelDeferredClear(): void;
|
|
98
109
|
get jsonlCutoff(): number | null;
|
|
99
110
|
set jsonlCutoff(value: number | null);
|
|
111
|
+
private enqueueCardHistoryEntry;
|
|
100
112
|
private nextId;
|
|
101
113
|
private addEvent;
|
|
102
114
|
private updateEvent;
|
|
@@ -167,6 +179,7 @@ export declare class StreamCardBuilder {
|
|
|
167
179
|
stopReason?: unknown;
|
|
168
180
|
}): CardEvent;
|
|
169
181
|
generatedImage(prompt: string, status: GeneratedImageCard['status'], savedPath?: string): CardEvent;
|
|
182
|
+
artifact(ref: MarkdownArtifactRef, afterToolUseId?: string): CardEvent;
|
|
170
183
|
recoverySuggested(reason: string, action: 'compact', label: string): CardEvent;
|
|
171
184
|
errorMessage(text: string): CardEvent;
|
|
172
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;AAsGnC;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAW3E;AA6ED,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 } 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;
|
|
@@ -60,25 +63,132 @@ function mergeCardsById(existing, incoming) {
|
|
|
60
63
|
byId.set(card.id, card);
|
|
61
64
|
for (const card of incoming)
|
|
62
65
|
byId.set(card.id, card);
|
|
63
|
-
return Array.from(byId.values());
|
|
66
|
+
return sortCardsChronologically(Array.from(byId.values()));
|
|
67
|
+
}
|
|
68
|
+
function sortCardsChronologically(cards) {
|
|
69
|
+
return cards
|
|
70
|
+
.map((card, index) => ({ card, index }))
|
|
71
|
+
.sort((a, b) => {
|
|
72
|
+
const at = Number.isFinite(a.card.timestamp) ? a.card.timestamp : 0;
|
|
73
|
+
const bt = Number.isFinite(b.card.timestamp) ? b.card.timestamp : 0;
|
|
74
|
+
return at - bt || a.index - b.index;
|
|
75
|
+
})
|
|
76
|
+
.map(({ card }) => card);
|
|
77
|
+
}
|
|
78
|
+
function maxCardSequenceForSession(cards, sessionId) {
|
|
79
|
+
const prefix = `${sessionId}:`;
|
|
80
|
+
let max = 0;
|
|
81
|
+
for (const card of cards) {
|
|
82
|
+
if (!card.id.startsWith(prefix))
|
|
83
|
+
continue;
|
|
84
|
+
const raw = card.id.slice(prefix.length);
|
|
85
|
+
if (!/^\d+$/.test(raw))
|
|
86
|
+
continue;
|
|
87
|
+
const seq = Number(raw);
|
|
88
|
+
if (Number.isSafeInteger(seq) && seq > max)
|
|
89
|
+
max = seq;
|
|
90
|
+
}
|
|
91
|
+
return max;
|
|
64
92
|
}
|
|
65
93
|
/**
|
|
66
94
|
* Load persisted card history for a memory-mode session.
|
|
67
95
|
* Returns cards in insertion order, or empty array if none exist.
|
|
68
96
|
*/
|
|
69
97
|
export async function loadPersistedCards(sessionId) {
|
|
70
|
-
const
|
|
98
|
+
const migratedCards = await migrateLegacyCardHistory(sessionId);
|
|
99
|
+
const p = cardHistoryLogPath(sessionId);
|
|
71
100
|
if (!existsSync(p))
|
|
72
|
-
return [];
|
|
101
|
+
return migratedCards ?? [];
|
|
73
102
|
try {
|
|
74
103
|
const raw = await readFile(p, 'utf-8');
|
|
104
|
+
const replayed = replayCardHistoryLog(raw);
|
|
105
|
+
return migratedCards ? mergeCardsById(migratedCards, replayed) : replayed;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return migratedCards ?? [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function migrateLegacyCardHistory(sessionId) {
|
|
112
|
+
const legacyPath = cardHistoryPath(sessionId);
|
|
113
|
+
if (!existsSync(legacyPath))
|
|
114
|
+
return null;
|
|
115
|
+
try {
|
|
116
|
+
const raw = await readFile(legacyPath, 'utf-8');
|
|
75
117
|
const data = JSON.parse(raw);
|
|
76
|
-
|
|
118
|
+
if (!Array.isArray(data) || data.length === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const cards = sortCardsChronologically(data).map(cleanPersistedCard);
|
|
121
|
+
try {
|
|
122
|
+
await mkdir(getCardHistoryDir(), { recursive: true });
|
|
123
|
+
await appendFile(cardHistoryLogPath(sessionId), JSON.stringify({ op: 'seed', cards }) + '\n');
|
|
124
|
+
await rename(legacyPath, `${legacyPath}.migrated-${Date.now()}`);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Keep the legacy file as fallback; the next read can retry migration.
|
|
128
|
+
}
|
|
129
|
+
return cards;
|
|
77
130
|
}
|
|
78
131
|
catch {
|
|
79
|
-
return
|
|
132
|
+
return null;
|
|
80
133
|
}
|
|
81
134
|
}
|
|
135
|
+
function replayCardHistoryLog(raw) {
|
|
136
|
+
const cardsById = new Map();
|
|
137
|
+
for (const line of raw.split('\n')) {
|
|
138
|
+
if (!line.trim())
|
|
139
|
+
continue;
|
|
140
|
+
let entry;
|
|
141
|
+
try {
|
|
142
|
+
entry = JSON.parse(line);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
switch (entry.op) {
|
|
148
|
+
case 'seed':
|
|
149
|
+
for (const card of entry.cards ?? []) {
|
|
150
|
+
if (!card?.id || cardsById.has(card.id))
|
|
151
|
+
continue;
|
|
152
|
+
cardsById.set(card.id, cleanPersistedCard(card));
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
case 'upsert':
|
|
156
|
+
if (entry.card?.id)
|
|
157
|
+
cardsById.set(entry.card.id, cleanPersistedCard(entry.card));
|
|
158
|
+
break;
|
|
159
|
+
case 'patch': {
|
|
160
|
+
const existing = cardsById.get(entry.cardId);
|
|
161
|
+
if (!existing)
|
|
162
|
+
break;
|
|
163
|
+
const bag = existing;
|
|
164
|
+
for (const [key, value] of Object.entries(entry.patch ?? {})) {
|
|
165
|
+
if (value === null)
|
|
166
|
+
delete bag[key];
|
|
167
|
+
else
|
|
168
|
+
bag[key] = value;
|
|
169
|
+
}
|
|
170
|
+
cardsById.set(entry.cardId, cleanPersistedCard(existing));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case 'append_text': {
|
|
174
|
+
const existing = cardsById.get(entry.cardId);
|
|
175
|
+
if (existing && 'text' in existing) {
|
|
176
|
+
existing.text += entry.text;
|
|
177
|
+
cardsById.set(entry.cardId, cleanPersistedCard(existing));
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'remove':
|
|
182
|
+
cardsById.delete(entry.cardId);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return sortCardsChronologically(Array.from(cardsById.values()));
|
|
187
|
+
}
|
|
188
|
+
async function appendCardHistoryEntry(sessionId, entry) {
|
|
189
|
+
await mkdir(getCardHistoryDir(), { recursive: true });
|
|
190
|
+
await appendFile(cardHistoryLogPath(sessionId), JSON.stringify(entry) + '\n');
|
|
191
|
+
}
|
|
82
192
|
// ── Direct JSONL file reading (replaces SDK getSessionMessages/listSubagents) ──
|
|
83
193
|
export function encodeCwdPath(cwd) {
|
|
84
194
|
return cwd.replace(/\//g, '-');
|
|
@@ -298,6 +408,8 @@ export class StreamCardBuilder {
|
|
|
298
408
|
cards = new Map();
|
|
299
409
|
/** tool_use_id → CardId, for pairing tool_result to ToolCallCard */
|
|
300
410
|
toolUseIdToCardId = new Map();
|
|
411
|
+
/** artifactId → CardId, for de-duping generated display artifact cards. */
|
|
412
|
+
artifactIdToCardId = new Map();
|
|
301
413
|
/** agentId (task_id) → CardId, for matching subagent updates */
|
|
302
414
|
agentIdToCardId = new Map();
|
|
303
415
|
/** Cards created for subagent permissions — removed after resolution. */
|
|
@@ -317,6 +429,8 @@ export class StreamCardBuilder {
|
|
|
317
429
|
/** Token identifying an in-flight deferred clear. A later call (new turn, cancel)
|
|
318
430
|
* replaces this token, causing the pending polling task to bail out. */
|
|
319
431
|
_pendingClearToken = null;
|
|
432
|
+
persistMemoryCards = false;
|
|
433
|
+
cardHistoryWriteQueue = Promise.resolve();
|
|
320
434
|
constructor(sessionId, cwd) {
|
|
321
435
|
this.sessionId = sessionId;
|
|
322
436
|
this.cwd = cwd;
|
|
@@ -324,6 +438,16 @@ export class StreamCardBuilder {
|
|
|
324
438
|
updateSessionId(sessionId) {
|
|
325
439
|
this.sessionId = sessionId;
|
|
326
440
|
}
|
|
441
|
+
enableMemoryPersistence(enabled = true) {
|
|
442
|
+
this.persistMemoryCards = enabled;
|
|
443
|
+
}
|
|
444
|
+
/** Continue the local card id counter after persisted memory-mode history.
|
|
445
|
+
* Used on cold resume after daemon restart so new Codex/OpenCode cards
|
|
446
|
+
* append after existing cards instead of reusing `sessionId:1`, `:2`, ...
|
|
447
|
+
* and overwriting or reordering older history. */
|
|
448
|
+
seedSequenceFromCards(cards) {
|
|
449
|
+
this.seq = Math.max(this.seq, maxCardSequenceForSession(cards, this.sessionId));
|
|
450
|
+
}
|
|
327
451
|
/** Start a new turn: reset per-turn state, keep accumulated cards. */
|
|
328
452
|
startNewTurn() {
|
|
329
453
|
this.currentTextCardId = null;
|
|
@@ -332,6 +456,7 @@ export class StreamCardBuilder {
|
|
|
332
456
|
clearCards() {
|
|
333
457
|
this.cards.clear();
|
|
334
458
|
this.toolUseIdToCardId.clear();
|
|
459
|
+
this.artifactIdToCardId.clear();
|
|
335
460
|
this.agentIdToCardId.clear();
|
|
336
461
|
this.ephemeralCards.clear();
|
|
337
462
|
this.agentToolUseStash.clear();
|
|
@@ -348,14 +473,34 @@ export class StreamCardBuilder {
|
|
|
348
473
|
const cards = this.getCards();
|
|
349
474
|
if (cards.length === 0)
|
|
350
475
|
return;
|
|
476
|
+
if (this.persistMemoryCards) {
|
|
477
|
+
for (const card of cards) {
|
|
478
|
+
this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
|
|
479
|
+
}
|
|
480
|
+
await this.flushCardHistoryWrites();
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
351
483
|
await this.persistCardBatch(cards);
|
|
352
484
|
}
|
|
353
485
|
/** Persist a single card immediately. Used when a memory-mode provider
|
|
354
486
|
* injects a user prompt into an already-running turn; the card must survive
|
|
355
487
|
* refresh before the turn's normal end-of-turn persist runs. */
|
|
356
488
|
async persistCard(card) {
|
|
489
|
+
if (this.persistMemoryCards) {
|
|
490
|
+
this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
|
|
491
|
+
await this.flushCardHistoryWrites();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
357
494
|
await this.persistCardBatch([card]);
|
|
358
495
|
}
|
|
496
|
+
async flushCardHistoryWrites() {
|
|
497
|
+
try {
|
|
498
|
+
await this.cardHistoryWriteQueue;
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
// Individual write failures are intentionally swallowed in the queue.
|
|
502
|
+
}
|
|
503
|
+
}
|
|
359
504
|
async persistCardBatch(cards) {
|
|
360
505
|
if (cards.length === 0)
|
|
361
506
|
return;
|
|
@@ -444,6 +589,7 @@ export class StreamCardBuilder {
|
|
|
444
589
|
this._jsonlCutoff = lastSize >= 0 ? lastSize : null;
|
|
445
590
|
this.cards.clear();
|
|
446
591
|
this.toolUseIdToCardId.clear();
|
|
592
|
+
this.artifactIdToCardId.clear();
|
|
447
593
|
this.agentIdToCardId.clear();
|
|
448
594
|
this.ephemeralCards.clear();
|
|
449
595
|
this.agentToolUseStash.clear();
|
|
@@ -462,11 +608,23 @@ export class StreamCardBuilder {
|
|
|
462
608
|
set jsonlCutoff(value) {
|
|
463
609
|
this._jsonlCutoff = value;
|
|
464
610
|
}
|
|
611
|
+
enqueueCardHistoryEntry(entry) {
|
|
612
|
+
if (!this.persistMemoryCards || this.sessionId === 'pending')
|
|
613
|
+
return;
|
|
614
|
+
const sessionId = this.sessionId;
|
|
615
|
+
this.cardHistoryWriteQueue = this.cardHistoryWriteQueue
|
|
616
|
+
.catch(() => undefined)
|
|
617
|
+
.then(() => appendCardHistoryEntry(sessionId, entry))
|
|
618
|
+
.catch((err) => {
|
|
619
|
+
console.warn(`[card-history] append failed for session=${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
465
622
|
nextId() {
|
|
466
623
|
return `${this.sessionId}:${++this.seq}`;
|
|
467
624
|
}
|
|
468
625
|
addEvent(card, afterCardId) {
|
|
469
626
|
this.cards.set(card.id, card);
|
|
627
|
+
this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
|
|
470
628
|
return { type: 'add', sessionId: this.sessionId, card, afterCardId };
|
|
471
629
|
}
|
|
472
630
|
updateEvent(cardId, patch) {
|
|
@@ -483,6 +641,7 @@ export class StreamCardBuilder {
|
|
|
483
641
|
else
|
|
484
642
|
bag[key] = value;
|
|
485
643
|
}
|
|
644
|
+
this.enqueueCardHistoryEntry({ op: 'patch', cardId, patch });
|
|
486
645
|
}
|
|
487
646
|
return { type: 'update', sessionId: this.sessionId, cardId, patch };
|
|
488
647
|
}
|
|
@@ -490,11 +649,13 @@ export class StreamCardBuilder {
|
|
|
490
649
|
const existing = this.cards.get(cardId);
|
|
491
650
|
if (existing && 'text' in existing) {
|
|
492
651
|
existing.text += text;
|
|
652
|
+
this.enqueueCardHistoryEntry({ op: 'append_text', cardId, text });
|
|
493
653
|
}
|
|
494
654
|
return { type: 'append_text', sessionId: this.sessionId, cardId, text };
|
|
495
655
|
}
|
|
496
656
|
removeEvent(cardId) {
|
|
497
657
|
this.cards.delete(cardId);
|
|
658
|
+
this.enqueueCardHistoryEntry({ op: 'remove', cardId });
|
|
498
659
|
return { type: 'remove', sessionId: this.sessionId, cardId };
|
|
499
660
|
}
|
|
500
661
|
// ── Public: produce CardEvents from SDK data ────────────────────────────
|
|
@@ -748,6 +909,23 @@ export class StreamCardBuilder {
|
|
|
748
909
|
};
|
|
749
910
|
return this.addEvent(card);
|
|
750
911
|
}
|
|
912
|
+
artifact(ref, afterToolUseId) {
|
|
913
|
+
this.currentTextCardId = null;
|
|
914
|
+
const existingCardId = this.artifactIdToCardId.get(ref.artifactId);
|
|
915
|
+
if (existingCardId) {
|
|
916
|
+
return this.updateEvent(existingCardId, { artifact: ref });
|
|
917
|
+
}
|
|
918
|
+
const id = `${this.sessionId}:artifact:${ref.artifactId}`;
|
|
919
|
+
this.artifactIdToCardId.set(ref.artifactId, id);
|
|
920
|
+
const card = {
|
|
921
|
+
type: 'artifact',
|
|
922
|
+
id,
|
|
923
|
+
timestamp: Date.now(),
|
|
924
|
+
artifact: ref,
|
|
925
|
+
};
|
|
926
|
+
const afterCardId = afterToolUseId ? this.toolUseIdToCardId.get(afterToolUseId) : undefined;
|
|
927
|
+
return this.addEvent(card, afterCardId);
|
|
928
|
+
}
|
|
751
929
|
recoverySuggested(reason, action, label) {
|
|
752
930
|
const card = {
|
|
753
931
|
type: 'recovery_suggested',
|