@yeaft/webchat-agent 1.0.188 → 1.0.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/cli.js +12 -0
  2. package/index.js +10 -2
  3. package/local-run.js +218 -0
  4. package/local-runtime/server/.env.example +54 -0
  5. package/local-runtime/server/api.js +111 -0
  6. package/local-runtime/server/auth/aad.js +235 -0
  7. package/local-runtime/server/auth/login.js +156 -0
  8. package/local-runtime/server/auth/oauth-flow.js +277 -0
  9. package/local-runtime/server/auth/password-reset.js +134 -0
  10. package/local-runtime/server/auth/providers/alipay.js +125 -0
  11. package/local-runtime/server/auth/providers/github.js +82 -0
  12. package/local-runtime/server/auth/providers/google.js +60 -0
  13. package/local-runtime/server/auth/providers/microsoft.js +71 -0
  14. package/local-runtime/server/auth/providers/types.js +57 -0
  15. package/local-runtime/server/auth/providers/wechat.js +68 -0
  16. package/local-runtime/server/auth/register.js +91 -0
  17. package/local-runtime/server/auth/session-store.js +57 -0
  18. package/local-runtime/server/auth/token.js +85 -0
  19. package/local-runtime/server/auth/totp-auth.js +133 -0
  20. package/local-runtime/server/auth/utils.js +42 -0
  21. package/local-runtime/server/auth.js +8 -0
  22. package/local-runtime/server/check-node-version.js +74 -0
  23. package/local-runtime/server/config.js +298 -0
  24. package/local-runtime/server/context.js +140 -0
  25. package/local-runtime/server/create-user.js +59 -0
  26. package/local-runtime/server/database.js +12 -0
  27. package/local-runtime/server/db/connection.js +963 -0
  28. package/local-runtime/server/db/expert-db.js +171 -0
  29. package/local-runtime/server/db/identity-db.js +92 -0
  30. package/local-runtime/server/db/invitation-db.js +38 -0
  31. package/local-runtime/server/db/message-db.js +257 -0
  32. package/local-runtime/server/db/session-db.js +118 -0
  33. package/local-runtime/server/db/user-db.js +165 -0
  34. package/local-runtime/server/db/user-stats-db.js +185 -0
  35. package/local-runtime/server/db/yeaft-session-db.js +258 -0
  36. package/local-runtime/server/email.js +96 -0
  37. package/local-runtime/server/encryption.js +105 -0
  38. package/local-runtime/server/handlers/agent-conversation.js +347 -0
  39. package/local-runtime/server/handlers/agent-file-terminal.js +99 -0
  40. package/local-runtime/server/handlers/agent-output.js +854 -0
  41. package/local-runtime/server/handlers/agent-sync.js +399 -0
  42. package/local-runtime/server/handlers/agent-work-center.js +27 -0
  43. package/local-runtime/server/handlers/client-conversation.js +1182 -0
  44. package/local-runtime/server/handlers/client-misc.js +254 -0
  45. package/local-runtime/server/handlers/client-work-center.js +269 -0
  46. package/local-runtime/server/handlers/client-workbench.js +146 -0
  47. package/local-runtime/server/handlers/session-pin-router.js +61 -0
  48. package/local-runtime/server/heartbeat-policy.js +46 -0
  49. package/local-runtime/server/index.js +275 -0
  50. package/local-runtime/server/package.json +55 -0
  51. package/local-runtime/server/perf-trace.js +154 -0
  52. package/local-runtime/server/proxy.js +273 -0
  53. package/local-runtime/server/routes/admin-routes.js +207 -0
  54. package/local-runtime/server/routes/auth-routes.js +322 -0
  55. package/local-runtime/server/routes/expert-routes.js +117 -0
  56. package/local-runtime/server/routes/invitation-routes.js +60 -0
  57. package/local-runtime/server/routes/session-routes.js +112 -0
  58. package/local-runtime/server/routes/upload-routes.js +109 -0
  59. package/local-runtime/server/routes/user-routes.js +241 -0
  60. package/local-runtime/server/totp.js +74 -0
  61. package/local-runtime/server/work-item-attachment-policy.js +56 -0
  62. package/local-runtime/server/ws-agent.js +319 -0
  63. package/local-runtime/server/ws-client.js +214 -0
  64. package/local-runtime/server/ws-utils.js +394 -0
  65. package/local-runtime/server/yeaft-asset-store.js +339 -0
  66. package/local-runtime/version.json +1 -0
  67. package/local-runtime/web/app.bundle.js +7673 -0
  68. package/local-runtime/web/app.bundle.js.gz +0 -0
  69. package/local-runtime/web/assets/avatars/README.md +34 -0
  70. package/local-runtime/web/assets/avatars/ada.svg +1 -0
  71. package/local-runtime/web/assets/avatars/alan.svg +1 -0
  72. package/local-runtime/web/assets/avatars/alice.svg +1 -0
  73. package/local-runtime/web/assets/avatars/anders.svg +1 -0
  74. package/local-runtime/web/assets/avatars/bezos.svg +1 -0
  75. package/local-runtime/web/assets/avatars/borges.svg +1 -0
  76. package/local-runtime/web/assets/avatars/buffett.svg +1 -0
  77. package/local-runtime/web/assets/avatars/clausewitz.svg +1 -0
  78. package/local-runtime/web/assets/avatars/dalio.svg +1 -0
  79. package/local-runtime/web/assets/avatars/dieter.svg +1 -0
  80. package/local-runtime/web/assets/avatars/drucker.svg +1 -0
  81. package/local-runtime/web/assets/avatars/einstein.svg +1 -0
  82. package/local-runtime/web/assets/avatars/grace.svg +1 -0
  83. package/local-runtime/web/assets/avatars/harari.svg +1 -0
  84. package/local-runtime/web/assets/avatars/jung.svg +1 -0
  85. package/local-runtime/web/assets/avatars/kahneman.svg +1 -0
  86. package/local-runtime/web/assets/avatars/ken.svg +1 -0
  87. package/local-runtime/web/assets/avatars/kongzi.svg +1 -0
  88. package/local-runtime/web/assets/avatars/kubrick.svg +1 -0
  89. package/local-runtime/web/assets/avatars/linus.svg +1 -0
  90. package/local-runtime/web/assets/avatars/luxun.svg +1 -0
  91. package/local-runtime/web/assets/avatars/margaret.svg +1 -0
  92. package/local-runtime/web/assets/avatars/martin.svg +1 -0
  93. package/local-runtime/web/assets/avatars/miyazaki.svg +1 -0
  94. package/local-runtime/web/assets/avatars/munger.svg +1 -0
  95. package/local-runtime/web/assets/avatars/nietzsche.svg +1 -0
  96. package/local-runtime/web/assets/avatars/norman.svg +1 -0
  97. package/local-runtime/web/assets/avatars/shannon.svg +1 -0
  98. package/local-runtime/web/assets/avatars/simaqian.svg +1 -0
  99. package/local-runtime/web/assets/avatars/socrates.svg +1 -0
  100. package/local-runtime/web/assets/avatars/steve.svg +1 -0
  101. package/local-runtime/web/assets/avatars/sudongpo.svg +1 -0
  102. package/local-runtime/web/assets/avatars/sunzi.svg +1 -0
  103. package/local-runtime/web/docx-preview.min.js +2 -0
  104. package/local-runtime/web/docx-preview.min.js.gz +0 -0
  105. package/local-runtime/web/html-to-image.min.js +2 -0
  106. package/local-runtime/web/html-to-image.min.js.gz +0 -0
  107. package/local-runtime/web/index.html +21 -0
  108. package/local-runtime/web/jszip.min.js +13 -0
  109. package/local-runtime/web/jszip.min.js.gz +0 -0
  110. package/local-runtime/web/mermaid.min.js +2843 -0
  111. package/local-runtime/web/mermaid.min.js.gz +0 -0
  112. package/local-runtime/web/msal-browser.min.js +69 -0
  113. package/local-runtime/web/msal-browser.min.js.gz +0 -0
  114. package/local-runtime/web/style.bundle.css +10 -0
  115. package/local-runtime/web/style.bundle.css.gz +0 -0
  116. package/local-runtime/web/vendor.bundle.js +1522 -0
  117. package/local-runtime/web/vendor.bundle.js.gz +0 -0
  118. package/local-runtime/web/xlsx.min.js +24 -0
  119. package/local-runtime/web/xlsx.min.js.gz +0 -0
  120. package/package.json +13 -3
  121. package/scripts/prepare-local-runtime.js +25 -0
@@ -0,0 +1,339 @@
1
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import {
3
+ existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync,
4
+ } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { CONFIG } from './config.js';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const DEFAULT_ROOT = join(__dirname, '../data/yeaft-assets');
11
+ const MAX_ASSET_BYTES = 20 * 1024 * 1024;
12
+ const DEFAULT_MAX_SCOPE_BYTES = 512 * 1024 * 1024;
13
+ const DEFAULT_MAX_OWNER_BYTES = 2 * 1024 * 1024 * 1024;
14
+ const DEFAULT_MAX_GLOBAL_BYTES = 20 * 1024 * 1024 * 1024;
15
+ const DEFAULT_MAX_SCOPE_ASSETS = 1_000;
16
+ const DEFAULT_MAX_GLOBAL_ASSETS = 100_000;
17
+ const DEFAULT_RETENTION_MS = 180 * 24 * 60 * 60 * 1000;
18
+ const DEFAULT_ORPHAN_GRACE_MS = 60 * 60 * 1000;
19
+ const MIME_BY_SIGNATURE = Object.freeze({
20
+ png: 'image/png',
21
+ jpeg: 'image/jpeg',
22
+ gif: 'image/gif',
23
+ webp: 'image/webp',
24
+ });
25
+
26
+ function sha256(value) {
27
+ return createHash('sha256').update(value).digest('hex');
28
+ }
29
+
30
+ function safeFilename(value, mimeType) {
31
+ const raw = String(value || '').split(/[/\\]/).pop() || 'image';
32
+ const clean = raw.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 160) || 'image';
33
+ if (/\.[A-Za-z0-9]{2,5}$/.test(clean)) return clean;
34
+ const ext = mimeType === 'image/jpeg' ? '.jpg' : `.${mimeType.split('/')[1] || 'img'}`;
35
+ return `${clean}${ext}`;
36
+ }
37
+
38
+ export function detectImageMime(buffer) {
39
+ if (!Buffer.isBuffer(buffer) || buffer.length < 10) return null;
40
+ if (buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) return MIME_BY_SIGNATURE.png;
41
+ if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return MIME_BY_SIGNATURE.jpeg;
42
+ const head6 = buffer.subarray(0, 6).toString('ascii');
43
+ if (head6 === 'GIF87a' || head6 === 'GIF89a') return MIME_BY_SIGNATURE.gif;
44
+ if (buffer.subarray(0, 4).toString('ascii') === 'RIFF' && buffer.subarray(8, 12).toString('ascii') === 'WEBP') return MIME_BY_SIGNATURE.webp;
45
+ return null;
46
+ }
47
+
48
+ function scopeIdFor(ownerId, agentId, sessionId, secret) {
49
+ return createHmac('sha256', secret)
50
+ .update(`${ownerId}\0${agentId}\0${sessionId}`)
51
+ .digest('hex')
52
+ .slice(0, 32);
53
+ }
54
+
55
+ function tokenFor(scopeId, assetId, secret) {
56
+ return createHmac('sha256', secret).update(`${scopeId}\0${assetId}`).digest('base64url');
57
+ }
58
+
59
+ function safeTokenEqual(actual, expected) {
60
+ const a = Buffer.from(String(actual || ''));
61
+ const b = Buffer.from(String(expected || ''));
62
+ return a.length === b.length && timingSafeEqual(a, b);
63
+ }
64
+
65
+ function positiveNumber(value, fallback) {
66
+ const parsed = Number(value);
67
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
68
+ }
69
+
70
+ function writeAtomic(path, data) {
71
+ const tmp = `${path}.tmp-${process.pid}-${randomUUID()}`;
72
+ writeFileSync(tmp, data, { flag: 'wx' });
73
+ renameSync(tmp, path);
74
+ }
75
+
76
+ export function createYeaftAssetStore({
77
+ root = process.env.YEAFT_ASSET_DIR || DEFAULT_ROOT,
78
+ secret = CONFIG.jwtSecret,
79
+ maxScopeBytes = positiveNumber(process.env.YEAFT_ASSET_SCOPE_BYTES, DEFAULT_MAX_SCOPE_BYTES),
80
+ maxOwnerBytes = positiveNumber(process.env.YEAFT_ASSET_OWNER_BYTES, DEFAULT_MAX_OWNER_BYTES),
81
+ maxGlobalBytes = positiveNumber(process.env.YEAFT_ASSET_GLOBAL_BYTES, DEFAULT_MAX_GLOBAL_BYTES),
82
+ maxScopeAssets = positiveNumber(process.env.YEAFT_ASSET_SCOPE_COUNT, DEFAULT_MAX_SCOPE_ASSETS),
83
+ maxGlobalAssets = positiveNumber(process.env.YEAFT_ASSET_GLOBAL_COUNT, DEFAULT_MAX_GLOBAL_ASSETS),
84
+ retentionMs = positiveNumber(process.env.YEAFT_ASSET_RETENTION_MS, DEFAULT_RETENTION_MS),
85
+ orphanGraceMs = positiveNumber(process.env.YEAFT_ASSET_ORPHAN_GRACE_MS, DEFAULT_ORPHAN_GRACE_MS),
86
+ now = () => Date.now(),
87
+ } = {}) {
88
+ mkdirSync(root, { recursive: true });
89
+ const scopeUsage = new Map();
90
+ const scopeCounts = new Map();
91
+ const ownerUsage = new Map();
92
+ let totalUsage = 0;
93
+ let assetCount = 0;
94
+ let lastCleanupAt = 0;
95
+
96
+ const pathsFor = (scopeId, assetId) => {
97
+ if (!/^[a-f0-9]{32}$/.test(scopeId) || !/^[a-f0-9]{64}$/.test(assetId)) return null;
98
+ const dir = join(root, scopeId, assetId.slice(0, 2));
99
+ return { dir, data: join(dir, `${assetId}.bin`), meta: join(dir, `${assetId}.json`) };
100
+ };
101
+
102
+ const scopeMetadataRows = (scopeId) => {
103
+ const rows = [];
104
+ if (!/^[a-f0-9]{32}$/.test(scopeId)) return rows;
105
+ const scopeDir = join(root, scopeId);
106
+ let prefixes;
107
+ try {
108
+ if (!statSync(scopeDir).isDirectory()) return rows;
109
+ prefixes = readdirSync(scopeDir);
110
+ } catch { return rows; }
111
+ for (const prefix of prefixes) {
112
+ const prefixDir = join(scopeDir, prefix);
113
+ let names;
114
+ try { names = readdirSync(prefixDir); } catch { continue; }
115
+ for (const name of names) {
116
+ if (!name.endsWith('.json')) continue;
117
+ const metaPath = join(prefixDir, name);
118
+ try {
119
+ const metadata = JSON.parse(readFileSync(metaPath, 'utf8'));
120
+ const paths = pathsFor(metadata.scopeId, metadata.assetId);
121
+ if (metadata.scopeId !== scopeId || !paths || paths.meta !== metaPath || !existsSync(paths.data)) continue;
122
+ rows.push({ metadata, paths });
123
+ } catch { /* malformed rows are handled as orphans */ }
124
+ }
125
+ }
126
+ return rows;
127
+ };
128
+
129
+ const metadataRows = () => {
130
+ const rows = [];
131
+ if (!existsSync(root)) return rows;
132
+ for (const scopeId of readdirSync(root)) rows.push(...scopeMetadataRows(scopeId));
133
+ return rows;
134
+ };
135
+
136
+ const rebuildUsage = () => {
137
+ scopeUsage.clear();
138
+ scopeCounts.clear();
139
+ ownerUsage.clear();
140
+ totalUsage = 0;
141
+ assetCount = 0;
142
+ for (const { metadata } of metadataRows()) {
143
+ const size = Number(metadata.size) || 0;
144
+ totalUsage += size;
145
+ assetCount++;
146
+ scopeUsage.set(metadata.scopeId, (scopeUsage.get(metadata.scopeId) || 0) + size);
147
+ scopeCounts.set(metadata.scopeId, (scopeCounts.get(metadata.scopeId) || 0) + 1);
148
+ ownerUsage.set(metadata.ownerId, (ownerUsage.get(metadata.ownerId) || 0) + size);
149
+ }
150
+ };
151
+
152
+ const cleanupEmptyParents = path => {
153
+ try { rmSync(path, { recursive: false }); } catch { /* non-empty or already gone */ }
154
+ try { rmSync(dirname(path), { recursive: false }); } catch { /* non-empty */ }
155
+ };
156
+
157
+ const collectGarbage = () => {
158
+ const current = now();
159
+ const cutoff = current - retentionMs;
160
+ const known = new Set();
161
+ let removed = 0;
162
+ for (const row of metadataRows()) {
163
+ known.add(row.paths.data);
164
+ known.add(row.paths.meta);
165
+ const referencedAt = Number(row.metadata.lastReferencedAt || row.metadata.createdAt || 0);
166
+ if (referencedAt >= cutoff) continue;
167
+ rmSync(row.paths.data, { force: true });
168
+ rmSync(row.paths.meta, { force: true });
169
+ cleanupEmptyParents(row.paths.dir);
170
+ removed++;
171
+ }
172
+ if (existsSync(root)) {
173
+ const stack = [root];
174
+ while (stack.length) {
175
+ const dir = stack.pop();
176
+ let names;
177
+ try { names = readdirSync(dir); } catch { continue; }
178
+ for (const name of names) {
179
+ const path = join(dir, name);
180
+ let stat;
181
+ try { stat = statSync(path); } catch { continue; }
182
+ if (stat.isDirectory()) {
183
+ stack.push(path);
184
+ continue;
185
+ }
186
+ if (known.has(path) || current - stat.mtimeMs < orphanGraceMs) continue;
187
+ rmSync(path, { force: true });
188
+ }
189
+ }
190
+ }
191
+ lastCleanupAt = current;
192
+ rebuildUsage();
193
+ return removed;
194
+ };
195
+
196
+ collectGarbage();
197
+
198
+ return {
199
+ put({ ownerId, agentId, sessionId, assetId, data, mimeType, filename, width = null, height = null, turnId = null, vpId = null, threadId = null }) {
200
+ if (!ownerId || !agentId || !sessionId) throw new Error('Asset owner, agent, and Session are required');
201
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(String(data || ''), 'base64');
202
+ if (!buffer.length || buffer.length > MAX_ASSET_BYTES) throw new Error(`Image asset must be between 1 byte and ${MAX_ASSET_BYTES} bytes`);
203
+ const detectedMime = detectImageMime(buffer);
204
+ if (!detectedMime) throw new Error('Unsupported or invalid image asset');
205
+ if (mimeType && mimeType !== detectedMime) throw new Error('Image MIME does not match file bytes');
206
+ const computedAssetId = sha256(buffer);
207
+ if (assetId && assetId !== computedAssetId) throw new Error('Image asset id does not match file bytes');
208
+ const scopeId = scopeIdFor(ownerId, agentId, sessionId, secret);
209
+ const paths = pathsFor(scopeId, computedAssetId);
210
+ if (now() - lastCleanupAt > 60 * 60 * 1000) collectGarbage();
211
+ const duplicate = existsSync(paths.data) && existsSync(paths.meta);
212
+ if (!duplicate) {
213
+ if ((scopeCounts.get(scopeId) || 0) + 1 > maxScopeAssets) throw new Error('Session image asset count quota exceeded');
214
+ if (assetCount + 1 > maxGlobalAssets) throw new Error('Global image asset count quota exceeded');
215
+ if ((scopeUsage.get(scopeId) || 0) + buffer.length > maxScopeBytes) throw new Error('Session image asset quota exceeded');
216
+ if ((ownerUsage.get(ownerId) || 0) + buffer.length > maxOwnerBytes) throw new Error('User image asset quota exceeded');
217
+ if (totalUsage + buffer.length > maxGlobalBytes) throw new Error('Global image asset quota exceeded');
218
+ }
219
+ mkdirSync(paths.dir, { recursive: true });
220
+ if (!existsSync(paths.data)) writeFileSync(paths.data, buffer, { flag: 'wx' });
221
+ let existing = null;
222
+ if (duplicate) {
223
+ try { existing = JSON.parse(readFileSync(paths.meta, 'utf8')); } catch { /* rewrite below */ }
224
+ }
225
+ const metadata = {
226
+ assetId: computedAssetId,
227
+ scopeId,
228
+ ownerId,
229
+ agentId,
230
+ sessionId,
231
+ mimeType: detectedMime,
232
+ filename: safeFilename(filename, detectedMime),
233
+ size: buffer.length,
234
+ width: Number.isFinite(width) && width > 0 ? Math.floor(width) : null,
235
+ height: Number.isFinite(height) && height > 0 ? Math.floor(height) : null,
236
+ turnIds: [...new Set([
237
+ ...(Array.isArray(existing?.turnIds) ? existing.turnIds : []),
238
+ ...(existing?.turnId ? [existing.turnId] : []),
239
+ ...(turnId ? [turnId] : []),
240
+ ])],
241
+ vpId: vpId || existing?.vpId || null,
242
+ threadId: threadId || existing?.threadId || null,
243
+ createdAt: Number(existing?.createdAt) || now(),
244
+ lastReferencedAt: now(),
245
+ };
246
+ try {
247
+ writeAtomic(paths.meta, JSON.stringify(metadata));
248
+ } catch (err) {
249
+ if (!duplicate) rmSync(paths.data, { force: true });
250
+ throw err;
251
+ }
252
+ if (!duplicate) {
253
+ scopeUsage.set(scopeId, (scopeUsage.get(scopeId) || 0) + buffer.length);
254
+ scopeCounts.set(scopeId, (scopeCounts.get(scopeId) || 0) + 1);
255
+ ownerUsage.set(ownerId, (ownerUsage.get(ownerId) || 0) + buffer.length);
256
+ totalUsage += buffer.length;
257
+ assetCount++;
258
+ }
259
+ return this.describe({ ownerId, agentId, sessionId, assetId: computedAssetId });
260
+ },
261
+
262
+ describe({ ownerId, agentId, sessionId, assetId }) {
263
+ if (!ownerId || !agentId || !sessionId || !assetId) return null;
264
+ const scopeId = scopeIdFor(ownerId, agentId, sessionId, secret);
265
+ const paths = pathsFor(scopeId, assetId);
266
+ if (!paths || !existsSync(paths.data) || !existsSync(paths.meta)) return null;
267
+ let metadata;
268
+ try { metadata = JSON.parse(readFileSync(paths.meta, 'utf8')); } catch { return null; }
269
+ if (metadata.ownerId !== ownerId || metadata.agentId !== agentId || metadata.sessionId !== sessionId || metadata.assetId !== assetId) return null;
270
+ if (now() - Number(metadata.lastReferencedAt || 0) > Math.min(retentionMs / 2, 60 * 60 * 1000)) {
271
+ metadata.lastReferencedAt = now();
272
+ try { writeAtomic(paths.meta, JSON.stringify(metadata)); } catch { /* best-effort retention refresh */ }
273
+ }
274
+ const token = tokenFor(scopeId, assetId, secret);
275
+ return {
276
+ assetId,
277
+ mimeType: metadata.mimeType,
278
+ filename: metadata.filename,
279
+ size: metadata.size,
280
+ width: metadata.width,
281
+ height: metadata.height,
282
+ src: `/api/yeaft/assets/${scopeId}/${assetId}?token=${encodeURIComponent(token)}`,
283
+ };
284
+ },
285
+
286
+ read(scopeId, assetId, token) {
287
+ const paths = pathsFor(scopeId, assetId);
288
+ if (!paths || !safeTokenEqual(token, tokenFor(scopeId, assetId, secret))) return null;
289
+ if (!existsSync(paths.data) || !existsSync(paths.meta)) return null;
290
+ let metadata;
291
+ try { metadata = JSON.parse(readFileSync(paths.meta, 'utf8')); } catch { return null; }
292
+ if (metadata.scopeId !== scopeId || metadata.assetId !== assetId) return null;
293
+ return { metadata, buffer: readFileSync(paths.data) };
294
+ },
295
+
296
+ describeTurns({ ownerId, agentId, sessionId, turnIds }) {
297
+ const requested = new Set((Array.isArray(turnIds) ? turnIds : [])
298
+ .filter(turnId => typeof turnId === 'string' && turnId));
299
+ const result = new Map(Array.from(requested, turnId => [turnId, []]));
300
+ if (!ownerId || !agentId || !sessionId || requested.size === 0) return result;
301
+ const scopeId = scopeIdFor(ownerId, agentId, sessionId, secret);
302
+ const rows = scopeMetadataRows(scopeId)
303
+ .sort((a, b) => Number(a.metadata.createdAt) - Number(b.metadata.createdAt));
304
+ for (const row of rows) {
305
+ const associatedTurns = new Set([
306
+ ...(row.metadata.turnId ? [row.metadata.turnId] : []),
307
+ ...(Array.isArray(row.metadata.turnIds) ? row.metadata.turnIds : []),
308
+ ]);
309
+ const matches = Array.from(associatedTurns).filter(turnId => requested.has(turnId));
310
+ if (matches.length === 0) continue;
311
+ const image = this.describe({ ownerId, agentId, sessionId, assetId: row.metadata.assetId });
312
+ if (!image) continue;
313
+ for (const turnId of matches) result.get(turnId).push(image);
314
+ }
315
+ return result;
316
+ },
317
+
318
+ describeTurn({ ownerId, agentId, sessionId, turnId }) {
319
+ return this.describeTurns({ ownerId, agentId, sessionId, turnIds: [turnId] }).get(turnId) || [];
320
+ },
321
+
322
+ deleteScope({ ownerId, agentId, sessionId }) {
323
+ if (!ownerId || !agentId || !sessionId) return 0;
324
+ const scopeId = scopeIdFor(ownerId, agentId, sessionId, secret);
325
+ const removed = scopeMetadataRows(scopeId).length;
326
+ rmSync(join(root, scopeId), { recursive: true, force: true });
327
+ rebuildUsage();
328
+ return removed;
329
+ },
330
+
331
+ collectGarbage,
332
+
333
+ usage() {
334
+ return { bytes: totalUsage, assets: assetCount };
335
+ },
336
+ };
337
+ }
338
+
339
+ export const yeaftAssetStore = createYeaftAssetStore();
@@ -0,0 +1 @@
1
+ {"version":"1.0.190"}