@sumicom/quicksave 0.8.11 → 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 +9 -1
- package/dist/ai/cardBuilder.d.ts.map +1 -1
- package/dist/ai/cardBuilder.js +151 -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 +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 +37 -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":"AAAA,qDAAqD;AACrD,+BAA+B;AAC/B,+EAA+E;AAC/E,0CAA0C;AAC1C,EAAE;AACF,gCAAgC;AAChC,EAAE;AACF,uEAAuE;AACvE,kBAAkB;AAClB,gBAAgB;AAChB,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,qCAAqC;AACrC,+EAA+E;AAE/E,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,iBAAiB,GAAG,aAAa,CAAC;AACxC,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,2BAA2B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACrD,MAAM,4BAA4B,GAAG,CAAC,KAAK,EAAE,WAAW,CAAU,CAAC;AAEnE,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,GAAW,EAAE,SAAiB;IAC5C,OAAO,IAAI,CAAC,eAAe,EAAE,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,cAAc,CAAC,GAAW,EAAE,SAAiB,EAAE,UAAkB;IACxE,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW,EAAE,SAAiB,EAAE,UAAkB;IAC5E,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,SAAiB,EAAE,UAAkB;IACrE,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,MAAc;IACjD,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;AAC5F,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,4BAA4B,CAAC,QAAQ,CAAC,GAA0B,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAAiC;IAC7E,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,eAAe,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;QACjD,CAAC,CAAC,IAAI,CAAC,UAAU;QACjB,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtC,kBAAkB,CAAC,eAAe,CAAC,CAAC;IAEpC,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,EAAE,CAAC,IAAI,GAAG,2BAA2B,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,6BAA6B,2BAA2B,aAAa,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;IAChC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACxE,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAEpF,MAAM,IAAI,GAAwB;QAChC,OAAO,EAAE,UAAU;QACnB,UAAU;QACV,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,OAAO;QACZ,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,UAAU,CAAC;QACjD,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,EAAE,CAAC,IAAI;QACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,UAAU,EAAE,UAAU;KACvB,CAAC;IACF,MAAM,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACjG,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,UAAkB;IAC/E,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,IAAI,WAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACzC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC;gBAClD,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;aAC/C,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAqB,CAAC;YACrD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,KAAK,UAAU;gBAAE,SAAS;YAC7E,OAAO;gBACL,GAAG,IAAI;gBACP,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;aACxC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,oEAAoE;QACtE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAW,EAAE,SAAiB;IACvE,IAAI,WAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;YAC5E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,GAAW,EAAE,SAAiB;IACzE,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC"}
|
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":"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"}
|
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;
|
|
@@ -92,18 +95,100 @@ function maxCardSequenceForSession(cards, sessionId) {
|
|
|
92
95
|
* Returns cards in insertion order, or empty array if none exist.
|
|
93
96
|
*/
|
|
94
97
|
export async function loadPersistedCards(sessionId) {
|
|
95
|
-
const
|
|
98
|
+
const migratedCards = await migrateLegacyCardHistory(sessionId);
|
|
99
|
+
const p = cardHistoryLogPath(sessionId);
|
|
96
100
|
if (!existsSync(p))
|
|
97
|
-
return [];
|
|
101
|
+
return migratedCards ?? [];
|
|
98
102
|
try {
|
|
99
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');
|
|
100
117
|
const data = JSON.parse(raw);
|
|
101
|
-
|
|
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;
|
|
102
130
|
}
|
|
103
131
|
catch {
|
|
104
|
-
return
|
|
132
|
+
return null;
|
|
105
133
|
}
|
|
106
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
|
+
}
|
|
107
192
|
// ── Direct JSONL file reading (replaces SDK getSessionMessages/listSubagents) ──
|
|
108
193
|
export function encodeCwdPath(cwd) {
|
|
109
194
|
return cwd.replace(/\//g, '-');
|
|
@@ -323,6 +408,8 @@ export class StreamCardBuilder {
|
|
|
323
408
|
cards = new Map();
|
|
324
409
|
/** tool_use_id → CardId, for pairing tool_result to ToolCallCard */
|
|
325
410
|
toolUseIdToCardId = new Map();
|
|
411
|
+
/** artifactId → CardId, for de-duping generated display artifact cards. */
|
|
412
|
+
artifactIdToCardId = new Map();
|
|
326
413
|
/** agentId (task_id) → CardId, for matching subagent updates */
|
|
327
414
|
agentIdToCardId = new Map();
|
|
328
415
|
/** Cards created for subagent permissions — removed after resolution. */
|
|
@@ -342,6 +429,8 @@ export class StreamCardBuilder {
|
|
|
342
429
|
/** Token identifying an in-flight deferred clear. A later call (new turn, cancel)
|
|
343
430
|
* replaces this token, causing the pending polling task to bail out. */
|
|
344
431
|
_pendingClearToken = null;
|
|
432
|
+
persistMemoryCards = false;
|
|
433
|
+
cardHistoryWriteQueue = Promise.resolve();
|
|
345
434
|
constructor(sessionId, cwd) {
|
|
346
435
|
this.sessionId = sessionId;
|
|
347
436
|
this.cwd = cwd;
|
|
@@ -349,6 +438,9 @@ export class StreamCardBuilder {
|
|
|
349
438
|
updateSessionId(sessionId) {
|
|
350
439
|
this.sessionId = sessionId;
|
|
351
440
|
}
|
|
441
|
+
enableMemoryPersistence(enabled = true) {
|
|
442
|
+
this.persistMemoryCards = enabled;
|
|
443
|
+
}
|
|
352
444
|
/** Continue the local card id counter after persisted memory-mode history.
|
|
353
445
|
* Used on cold resume after daemon restart so new Codex/OpenCode cards
|
|
354
446
|
* append after existing cards instead of reusing `sessionId:1`, `:2`, ...
|
|
@@ -364,6 +456,7 @@ export class StreamCardBuilder {
|
|
|
364
456
|
clearCards() {
|
|
365
457
|
this.cards.clear();
|
|
366
458
|
this.toolUseIdToCardId.clear();
|
|
459
|
+
this.artifactIdToCardId.clear();
|
|
367
460
|
this.agentIdToCardId.clear();
|
|
368
461
|
this.ephemeralCards.clear();
|
|
369
462
|
this.agentToolUseStash.clear();
|
|
@@ -380,14 +473,34 @@ export class StreamCardBuilder {
|
|
|
380
473
|
const cards = this.getCards();
|
|
381
474
|
if (cards.length === 0)
|
|
382
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
|
+
}
|
|
383
483
|
await this.persistCardBatch(cards);
|
|
384
484
|
}
|
|
385
485
|
/** Persist a single card immediately. Used when a memory-mode provider
|
|
386
486
|
* injects a user prompt into an already-running turn; the card must survive
|
|
387
487
|
* refresh before the turn's normal end-of-turn persist runs. */
|
|
388
488
|
async persistCard(card) {
|
|
489
|
+
if (this.persistMemoryCards) {
|
|
490
|
+
this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
|
|
491
|
+
await this.flushCardHistoryWrites();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
389
494
|
await this.persistCardBatch([card]);
|
|
390
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
|
+
}
|
|
391
504
|
async persistCardBatch(cards) {
|
|
392
505
|
if (cards.length === 0)
|
|
393
506
|
return;
|
|
@@ -476,6 +589,7 @@ export class StreamCardBuilder {
|
|
|
476
589
|
this._jsonlCutoff = lastSize >= 0 ? lastSize : null;
|
|
477
590
|
this.cards.clear();
|
|
478
591
|
this.toolUseIdToCardId.clear();
|
|
592
|
+
this.artifactIdToCardId.clear();
|
|
479
593
|
this.agentIdToCardId.clear();
|
|
480
594
|
this.ephemeralCards.clear();
|
|
481
595
|
this.agentToolUseStash.clear();
|
|
@@ -494,11 +608,23 @@ export class StreamCardBuilder {
|
|
|
494
608
|
set jsonlCutoff(value) {
|
|
495
609
|
this._jsonlCutoff = value;
|
|
496
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
|
+
}
|
|
497
622
|
nextId() {
|
|
498
623
|
return `${this.sessionId}:${++this.seq}`;
|
|
499
624
|
}
|
|
500
625
|
addEvent(card, afterCardId) {
|
|
501
626
|
this.cards.set(card.id, card);
|
|
627
|
+
this.enqueueCardHistoryEntry({ op: 'upsert', card: cleanPersistedCard(card) });
|
|
502
628
|
return { type: 'add', sessionId: this.sessionId, card, afterCardId };
|
|
503
629
|
}
|
|
504
630
|
updateEvent(cardId, patch) {
|
|
@@ -515,6 +641,7 @@ export class StreamCardBuilder {
|
|
|
515
641
|
else
|
|
516
642
|
bag[key] = value;
|
|
517
643
|
}
|
|
644
|
+
this.enqueueCardHistoryEntry({ op: 'patch', cardId, patch });
|
|
518
645
|
}
|
|
519
646
|
return { type: 'update', sessionId: this.sessionId, cardId, patch };
|
|
520
647
|
}
|
|
@@ -522,11 +649,13 @@ export class StreamCardBuilder {
|
|
|
522
649
|
const existing = this.cards.get(cardId);
|
|
523
650
|
if (existing && 'text' in existing) {
|
|
524
651
|
existing.text += text;
|
|
652
|
+
this.enqueueCardHistoryEntry({ op: 'append_text', cardId, text });
|
|
525
653
|
}
|
|
526
654
|
return { type: 'append_text', sessionId: this.sessionId, cardId, text };
|
|
527
655
|
}
|
|
528
656
|
removeEvent(cardId) {
|
|
529
657
|
this.cards.delete(cardId);
|
|
658
|
+
this.enqueueCardHistoryEntry({ op: 'remove', cardId });
|
|
530
659
|
return { type: 'remove', sessionId: this.sessionId, cardId };
|
|
531
660
|
}
|
|
532
661
|
// ── Public: produce CardEvents from SDK data ────────────────────────────
|
|
@@ -780,6 +909,23 @@ export class StreamCardBuilder {
|
|
|
780
909
|
};
|
|
781
910
|
return this.addEvent(card);
|
|
782
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
|
+
}
|
|
783
929
|
recoverySuggested(reason, action, label) {
|
|
784
930
|
const card = {
|
|
785
931
|
type: 'recovery_suggested',
|