@yeaft/webchat-agent 1.0.570 → 1.0.572
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/index.js +2 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +67 -61
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/conversation/history-index-state.js +3 -0
- package/yeaft/conversation/history-index-worker.js +2 -2
- package/yeaft/conversation/history-index.js +7 -4
- package/yeaft/conversation/persist.js +61 -23
- package/yeaft/conversation/search.js +2 -1
- package/yeaft/web-bridge.js +13 -12
- package/yeaft/work-center/attachment-platform.js +16 -0
- package/yeaft/work-center/attachments.js +267 -78
|
Binary file
|
package/package.json
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
import { join, relative } from 'node:path';
|
|
15
15
|
import { writeAtomic } from '../storage/atomic.js';
|
|
16
16
|
|
|
17
|
+
// v3 indexes the visible transcript, including rows replaced only in model context.
|
|
18
|
+
export const HISTORY_INDEX_SCHEMA_VERSION = 3;
|
|
19
|
+
|
|
17
20
|
const STATE_VERSION = 1;
|
|
18
21
|
const INDEX_DIR = 'conversation-index';
|
|
19
22
|
const STATE_FILE = 'mutation-state.json';
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
findLiteralSearch,
|
|
10
10
|
normalizeLiteralSearch,
|
|
11
11
|
} from './visible-entry.js';
|
|
12
|
-
import { fingerprintConversationSources } from './history-index-state.js';
|
|
12
|
+
import { fingerprintConversationSources, HISTORY_INDEX_SCHEMA_VERSION } from './history-index-state.js';
|
|
13
13
|
import { extractRecallTerms, scoreRecallTurn, normalizeRecallLimit, RECALL_LIMITS } from './recall-relevance.js';
|
|
14
14
|
|
|
15
|
-
const INDEX_SCHEMA_VERSION =
|
|
15
|
+
const INDEX_SCHEMA_VERSION = HISTORY_INDEX_SCHEMA_VERSION;
|
|
16
16
|
const SHORT_BLOOM_BYTES = 256;
|
|
17
17
|
const BUILD_YIELD_INTERVAL = 64;
|
|
18
18
|
const QUERY_BATCH_ROWS = 128;
|
|
@@ -4,6 +4,7 @@ import { dirname } from 'node:path';
|
|
|
4
4
|
import { writeAtomic } from '../storage/atomic.js';
|
|
5
5
|
import { extractRecallTerms, scoreRecallTurn, normalizeRecallLimit } from './recall-relevance.js';
|
|
6
6
|
import {
|
|
7
|
+
HISTORY_INDEX_SCHEMA_VERSION,
|
|
7
8
|
conversationIndexDatabasePath,
|
|
8
9
|
conversationIndexManifestPath,
|
|
9
10
|
flushConversationIndexMutations,
|
|
@@ -47,7 +48,9 @@ function readManifest(ownerRoot, sessionId) {
|
|
|
47
48
|
if (!existsSync(path)) return null;
|
|
48
49
|
try {
|
|
49
50
|
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
50
|
-
|
|
51
|
+
// Retain the previous generation across projection/schema upgrades, but
|
|
52
|
+
// never activate its database until it has been rebuilt.
|
|
53
|
+
if (!Number.isInteger(value?.indexSchemaVersion)) return null;
|
|
51
54
|
const generation = Number(value?.generation);
|
|
52
55
|
if (!Number.isInteger(generation) || generation < 1) return null;
|
|
53
56
|
const databasePath = conversationIndexDatabasePath(ownerRoot, sessionId, generation);
|
|
@@ -232,7 +235,7 @@ class SessionHistoryIndex {
|
|
|
232
235
|
const manifest = readManifest(this.ownerRoot, this.sessionId);
|
|
233
236
|
if (!manifest?.databasePath || !existsSync(manifest.databasePath)) return { needs: true, manifest };
|
|
234
237
|
const revision = readConversationMutationRevision(this.ownerRoot, 'session', this.sessionId);
|
|
235
|
-
if (Number(manifest.sourceRevision) !== revision) return { needs: true, manifest };
|
|
238
|
+
if (manifest.indexSchemaVersion !== HISTORY_INDEX_SCHEMA_VERSION || Number(manifest.sourceRevision) !== revision) return { needs: true, manifest };
|
|
236
239
|
if (this.active?.databasePath === manifest.databasePath) return { needs: false, manifest };
|
|
237
240
|
const source = await spawnOneShot('fingerprint', {
|
|
238
241
|
ownerRoot: this.ownerRoot,
|
|
@@ -297,7 +300,7 @@ class SessionHistoryIndex {
|
|
|
297
300
|
|
|
298
301
|
const manifest = readManifest(this.ownerRoot, this.sessionId);
|
|
299
302
|
if (manifest?.databasePath && existsSync(manifest.databasePath)) {
|
|
300
|
-
if (allowStale) {
|
|
303
|
+
if (allowStale && manifest.indexSchemaVersion === HISTORY_INDEX_SCHEMA_VERSION) {
|
|
301
304
|
await this.#activate(manifest);
|
|
302
305
|
return this.active;
|
|
303
306
|
}
|
|
@@ -350,7 +353,7 @@ class SessionHistoryIndex {
|
|
|
350
353
|
}
|
|
351
354
|
const manifest = {
|
|
352
355
|
version: 1,
|
|
353
|
-
indexSchemaVersion:
|
|
356
|
+
indexSchemaVersion: HISTORY_INDEX_SCHEMA_VERSION,
|
|
354
357
|
sessionId: this.sessionId,
|
|
355
358
|
generation,
|
|
356
359
|
databasePath,
|
|
@@ -74,6 +74,9 @@ const DELTA_TOOL_PAIR_EXTENSION_CAP = 500;
|
|
|
74
74
|
|
|
75
75
|
const SEGMENT_INDEX_FILE = 'index.json';
|
|
76
76
|
const SEGMENT_LINEAGE_FILE = 'lineage.json';
|
|
77
|
+
// Browser cursors also identify the visible projection. Version 1 (unversioned)
|
|
78
|
+
// omitted folded rows; its cached head cannot be repaired by an append delta.
|
|
79
|
+
const VISIBLE_HISTORY_PROJECTION_VERSION = 2;
|
|
77
80
|
const SEGMENT_DIR = 'segments';
|
|
78
81
|
const SEGMENT_TARGET_BYTES = 1024 * 1024;
|
|
79
82
|
const SEGMENT_FIRST_NAME = '000001.jsonl';
|
|
@@ -490,7 +493,7 @@ export function projectVisibleSessionMessages(messages) {
|
|
|
490
493
|
const { providerState, thinkingBlocks, ...row } = sourceRow;
|
|
491
494
|
if (!isVisibleConversationRow(row)) continue;
|
|
492
495
|
if (row.role !== 'assistant' || !Array.isArray(row.toolCalls) || row.toolCalls.length === 0) {
|
|
493
|
-
if (row.role === 'assistant' && !row.content && !row.attachments && !row.images
|
|
496
|
+
if (row.role === 'assistant' && !row.content && !row.attachments && !row.images && !row.imageAssetIds?.length
|
|
494
497
|
&& !row.todos && !row.askUserResults) continue;
|
|
495
498
|
const responseKind = row.role === 'assistant' ? projectedResponseKind(row) : null;
|
|
496
499
|
visible.push(responseKind && row.responseKind !== responseKind
|
|
@@ -520,7 +523,7 @@ export function projectVisibleSessionMessages(messages) {
|
|
|
520
523
|
...(visibleToolCalls.length > 0 ? { toolCalls: visibleToolCalls } : {}),
|
|
521
524
|
...(askUserResults.length > 0 ? { askUserResults } : {}),
|
|
522
525
|
};
|
|
523
|
-
if (!projected.content && !projected.attachments && !projected.images
|
|
526
|
+
if (!projected.content && !projected.attachments && !projected.images && !projected.imageAssetIds?.length
|
|
524
527
|
&& !projected.todos && !projected.toolCalls && !projected.askUserResults) continue;
|
|
525
528
|
visible.push(projected);
|
|
526
529
|
}
|
|
@@ -1031,7 +1034,12 @@ class SegmentStore {
|
|
|
1031
1034
|
return rows;
|
|
1032
1035
|
}
|
|
1033
1036
|
|
|
1034
|
-
|
|
1037
|
+
/**
|
|
1038
|
+
* Context applies fold replacements; transcript preserves durable source rows.
|
|
1039
|
+
* Transcript is NOT a public projection: callers must still filter control
|
|
1040
|
+
* rows and sensitive fields before exposing it to users.
|
|
1041
|
+
*/
|
|
1042
|
+
readAll({ beforeSeq = Infinity, afterSeq = -Infinity, desc = false, includeCold = false, projection = 'context' } = {}) {
|
|
1035
1043
|
if (!this.hasData()) return [];
|
|
1036
1044
|
const idx = this.loadIndex();
|
|
1037
1045
|
const segments = (idx.segments || [])
|
|
@@ -1041,7 +1049,7 @@ class SegmentStore {
|
|
|
1041
1049
|
const out = [];
|
|
1042
1050
|
for (const seg of segments) {
|
|
1043
1051
|
const rows = this.#readSegment(seg.file, { beforeSeq, afterSeq, desc, includeCold });
|
|
1044
|
-
out.push(...applyFoldedMessageTombstones(rows, idx.foldedMessageIds));
|
|
1052
|
+
out.push(...(projection === 'transcript' ? rows : applyFoldedMessageTombstones(rows, idx.foldedMessageIds)));
|
|
1045
1053
|
}
|
|
1046
1054
|
return desc
|
|
1047
1055
|
? out.sort((a, b) => parseSeqFromId(b.id) - parseSeqFromId(a.id))
|
|
@@ -1062,7 +1070,7 @@ class SegmentStore {
|
|
|
1062
1070
|
.sort(compareMessagesBySeq);
|
|
1063
1071
|
}
|
|
1064
1072
|
|
|
1065
|
-
*scan({ beforeSeq = Infinity, afterSeq = -Infinity, desc = false, includeCold = false, scanStats = null } = {}) {
|
|
1073
|
+
*scan({ beforeSeq = Infinity, afterSeq = -Infinity, desc = false, includeCold = false, scanStats = null, projection = 'context' } = {}) {
|
|
1066
1074
|
if (!this.hasData()) return;
|
|
1067
1075
|
const idx = this.loadIndex();
|
|
1068
1076
|
const segments = (idx.segments || [])
|
|
@@ -1074,7 +1082,7 @@ class SegmentStore {
|
|
|
1074
1082
|
addScanMetric(scanStats, 'segments');
|
|
1075
1083
|
addScanMetric(scanStats, 'bytes', Number(seg.bytes) || 0);
|
|
1076
1084
|
addScanMetric(scanStats, 'rows', rows.length);
|
|
1077
|
-
yield* applyFoldedMessageTombstones(rows, idx.foldedMessageIds);
|
|
1085
|
+
yield* projection === 'transcript' ? rows : applyFoldedMessageTombstones(rows, idx.foldedMessageIds);
|
|
1078
1086
|
}
|
|
1079
1087
|
}
|
|
1080
1088
|
|
|
@@ -1650,8 +1658,9 @@ export class ConversationStore {
|
|
|
1650
1658
|
* Atomically publish a logical range replacement for tool folding.
|
|
1651
1659
|
*
|
|
1652
1660
|
* The original rows stay append-only on disk. A single reflection row owns
|
|
1653
|
-
* their ids as tombstones, so readers
|
|
1654
|
-
*
|
|
1661
|
+
* their ids as context-only tombstones, so model readers observe the complete
|
|
1662
|
+
* old arc or its reflection — never a half-rewritten tool pair. User-visible
|
|
1663
|
+
* history always projects the original transcript, not these replacements.
|
|
1655
1664
|
*
|
|
1656
1665
|
* @param {object[]} messages — persisted rows being folded
|
|
1657
1666
|
* @param {object} reflection — synthetic `_reflection` user row
|
|
@@ -1792,15 +1801,20 @@ export class ConversationStore {
|
|
|
1792
1801
|
// ─── Read API ───────────────────────────────────────────
|
|
1793
1802
|
|
|
1794
1803
|
/**
|
|
1795
|
-
* Return the
|
|
1796
|
-
* changes on clear/recreate; `revision`
|
|
1797
|
-
*
|
|
1798
|
-
* trusting an `afterSeq` delta cursor.
|
|
1804
|
+
* Return the cache identity of one Session's user-visible history. `streamId`
|
|
1805
|
+
* includes the projection version and changes on clear/recreate; `revision`
|
|
1806
|
+
* changes on append/update/fold. Browser caches must match both before
|
|
1807
|
+
* trusting an `afterSeq` delta cursor, including across projection upgrades.
|
|
1808
|
+
* The durable stream identity and model context are not modified.
|
|
1799
1809
|
*/
|
|
1800
1810
|
getSessionHistoryMetadata(sessionId) {
|
|
1801
1811
|
if (!sessionId) return null;
|
|
1802
1812
|
const store = this.#segmentStoreForConversationDir(this.#sessionConversationDir(sessionId));
|
|
1803
|
-
|
|
1813
|
+
const metadata = store.metadata();
|
|
1814
|
+
return {
|
|
1815
|
+
...metadata,
|
|
1816
|
+
streamId: `${metadata.streamId}:visible-v${VISIBLE_HISTORY_PROJECTION_VERSION}`,
|
|
1817
|
+
};
|
|
1804
1818
|
}
|
|
1805
1819
|
|
|
1806
1820
|
/**
|
|
@@ -2119,7 +2133,7 @@ export class ConversationStore {
|
|
|
2119
2133
|
loadOlderBySession(sessionId, beforeSeq, turnsLimit = DEFAULT_RECENT_TURNS) {
|
|
2120
2134
|
if (!sessionId) return { messages: [], oldestSeq: null, hasMore: false };
|
|
2121
2135
|
const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
|
|
2122
|
-
const prefix = this.#readSessionRows(sessionId, { beforeSeq: cutoff })
|
|
2136
|
+
const prefix = this.#readSessionRows(sessionId, { beforeSeq: cutoff, projection: 'transcript' })
|
|
2123
2137
|
.filter(m => m && m.sessionId === sessionId && isVisibleConversationRow(m));
|
|
2124
2138
|
if (prefix.length === 0) return { messages: [], oldestSeq: null, hasMore: false };
|
|
2125
2139
|
const sliced = pairSanitize(sliceLastNTurns(prefix, turnsLimit));
|
|
@@ -2215,7 +2229,7 @@ export class ConversationStore {
|
|
|
2215
2229
|
const completedBeforeCursor = new Set();
|
|
2216
2230
|
const boundaryAssistants = [];
|
|
2217
2231
|
let boundaryLookbackRows = 0;
|
|
2218
|
-
for (const previous of this.#iterateSessionRows(sessionId, { beforeSeq: cutoff + 1, desc: true })) {
|
|
2232
|
+
for (const previous of this.#iterateSessionRows(sessionId, { beforeSeq: cutoff + 1, desc: true, projection: 'transcript' })) {
|
|
2219
2233
|
if (!previous || previous.sessionId !== sessionId) continue;
|
|
2220
2234
|
boundaryLookbackRows += 1;
|
|
2221
2235
|
if (boundaryLookbackRows > DELTA_TOOL_PAIR_EXTENSION_CAP) break;
|
|
@@ -2250,7 +2264,7 @@ export class ConversationStore {
|
|
|
2250
2264
|
let visibleBytes = after.reduce((sum, message) => sum + Buffer.byteLength(JSON.stringify(message)), 0);
|
|
2251
2265
|
let stoppedAtBudget = false;
|
|
2252
2266
|
let extensionRows = 0;
|
|
2253
|
-
const deltaRows = this.#iterateSessionRows(sessionId, { afterSeq: cutoff, desc: false });
|
|
2267
|
+
const deltaRows = this.#iterateSessionRows(sessionId, { afterSeq: cutoff, desc: false, projection: 'transcript' });
|
|
2254
2268
|
while (true) {
|
|
2255
2269
|
const step = deltaRows.next();
|
|
2256
2270
|
if (step.done) break;
|
|
@@ -2465,7 +2479,7 @@ export class ConversationStore {
|
|
|
2465
2479
|
const seen = new Set(messages.map(message => message?.id).filter(Boolean));
|
|
2466
2480
|
let followingUserTurns = 0;
|
|
2467
2481
|
|
|
2468
|
-
for (const message of this.#iterateSessionRows(sessionId, { afterSeq: anchorSeq, desc: false })) {
|
|
2482
|
+
for (const message of this.#iterateSessionRows(sessionId, { afterSeq: anchorSeq, desc: false, projection: 'transcript' })) {
|
|
2469
2483
|
if (!message || message.sessionId !== sessionId || !isVisibleConversationRow(message)) continue;
|
|
2470
2484
|
if (message.role === 'user') {
|
|
2471
2485
|
followingUserTurns += 1;
|
|
@@ -2480,6 +2494,7 @@ export class ConversationStore {
|
|
|
2480
2494
|
const entryStartSeq = Number.isFinite(opts.entryStartSeq) ? opts.entryStartSeq : anchorSeq;
|
|
2481
2495
|
const entryEndSeq = Number.isFinite(opts.entryEndSeq) ? opts.entryEndSeq : anchorSeq;
|
|
2482
2496
|
for (const message of this.#iterateSessionRows(sessionId, {
|
|
2497
|
+
projection: 'transcript',
|
|
2483
2498
|
afterSeq: entryStartSeq - 1,
|
|
2484
2499
|
beforeSeq: entryEndSeq + 1,
|
|
2485
2500
|
desc: false,
|
|
@@ -2674,7 +2689,7 @@ export class ConversationStore {
|
|
|
2674
2689
|
for (const dir of [this.#chatDir, ...this.#sessionConversationDirs({ primaryOnly: true })]) {
|
|
2675
2690
|
const store = this.#segmentStoreForConversationDir(dir);
|
|
2676
2691
|
if (!store.hasData()) continue;
|
|
2677
|
-
const rows = store.readAll({ includeCold: true });
|
|
2692
|
+
const rows = store.readAll({ includeCold: true, projection: 'transcript' });
|
|
2678
2693
|
let keepRows = [];
|
|
2679
2694
|
let dirty = false;
|
|
2680
2695
|
for (const msg of rows) {
|
|
@@ -2744,7 +2759,7 @@ export class ConversationStore {
|
|
|
2744
2759
|
for (const dir of [this.#chatDir, ...this.#sessionConversationDirs({ primaryOnly: true })]) {
|
|
2745
2760
|
const store = this.#segmentStoreForConversationDir(dir);
|
|
2746
2761
|
if (!store.hasData()) continue;
|
|
2747
|
-
const rows = store.readAll();
|
|
2762
|
+
const rows = store.readAll({ includeCold: true, projection: 'transcript' });
|
|
2748
2763
|
let dirty = false;
|
|
2749
2764
|
for (const msg of rows) {
|
|
2750
2765
|
if (!msg || msg.threadId !== sourceId) continue;
|
|
@@ -3159,7 +3174,9 @@ export class ConversationStore {
|
|
|
3159
3174
|
// and stop after the requested turn window is complete. Hidden/internal and
|
|
3160
3175
|
// non-turn rows are not allowed to force an unbounded scan; a hard parse cap
|
|
3161
3176
|
// conservatively marks the page truncated.
|
|
3162
|
-
for (const m of this.#iterateSessionRows(sessionId, {
|
|
3177
|
+
for (const m of this.#iterateSessionRows(sessionId, {
|
|
3178
|
+
beforeSeq, afterSeq, desc: true, projection: visibleOnly ? 'transcript' : 'context',
|
|
3179
|
+
})) {
|
|
3163
3180
|
if (parsed >= scanCap) {
|
|
3164
3181
|
truncated = true;
|
|
3165
3182
|
scanCapped = true;
|
|
@@ -3232,6 +3249,7 @@ export class ConversationStore {
|
|
|
3232
3249
|
beforeSeq,
|
|
3233
3250
|
desc: true,
|
|
3234
3251
|
scanStats: opts.scanStats,
|
|
3252
|
+
projection: 'transcript',
|
|
3235
3253
|
});
|
|
3236
3254
|
yield* iterateCanonicalVisibleEntriesNewestFirst(rows, sessionId);
|
|
3237
3255
|
}
|
|
@@ -3271,15 +3289,35 @@ export class ConversationStore {
|
|
|
3271
3289
|
*#iterateSessionRows(sessionId, opts = {}) {
|
|
3272
3290
|
const primaryDir = this.#sessionConversationDir(sessionId);
|
|
3273
3291
|
const segmentStore = this.#segmentStoreForConversationDir(primaryDir);
|
|
3274
|
-
|
|
3275
|
-
|
|
3292
|
+
const legacyEntries = this.#sessionFileEntries('all', sessionId, opts);
|
|
3293
|
+
// Interrupted migrations may leave a markdown copy of a folded JSONL row.
|
|
3294
|
+
// Apply the index's context-only tombstones to that copy too, including
|
|
3295
|
+
// when its reflection lies outside this page's sequence bounds. Legacy
|
|
3296
|
+
// markdown itself predates foldedMessageIds and has no fold metadata.
|
|
3297
|
+
const foldedIds = new Set(opts.projection === 'transcript'
|
|
3298
|
+
? [] : segmentStore.loadIndex().foldedMessageIds || []);
|
|
3299
|
+
const segments = segmentStore.scan({ includeCold: true, ...opts });
|
|
3300
|
+
let segment = segments.next();
|
|
3301
|
+
let legacyIndex = 0;
|
|
3302
|
+
// Merge by sequence rather than concatenating formats. A delta cursor must
|
|
3303
|
+
// not pass a legacy-only row, and the canonical JSONL copy wins duplicates.
|
|
3304
|
+
while (!segment.done || legacyIndex < legacyEntries.length) {
|
|
3305
|
+
const entry = legacyEntries[legacyIndex];
|
|
3306
|
+
const segmentSeq = segment.done ? null : parseSeqFromId(segment.value.id);
|
|
3307
|
+
if (!segment.done && (!entry || (opts.desc ? segmentSeq >= entry.seq : segmentSeq <= entry.seq))) {
|
|
3308
|
+
while (legacyEntries[legacyIndex]?.seq === segmentSeq) legacyIndex += 1;
|
|
3309
|
+
if (!foldedIds.has(segment.value.id)) yield segment.value;
|
|
3310
|
+
segment = segments.next();
|
|
3311
|
+
continue;
|
|
3312
|
+
}
|
|
3313
|
+
legacyIndex += 1;
|
|
3276
3314
|
try {
|
|
3277
3315
|
const msg = this.readMessageFile(entry.path);
|
|
3278
3316
|
addScanMetric(opts.scanStats, 'legacyFiles');
|
|
3279
3317
|
try { addScanMetric(opts.scanStats, 'bytes', statSync(entry.path).size); } catch {}
|
|
3280
3318
|
if (msg) {
|
|
3281
3319
|
addScanMetric(opts.scanStats, 'rows');
|
|
3282
|
-
yield msg;
|
|
3320
|
+
if (!foldedIds.has(msg.id)) yield msg;
|
|
3283
3321
|
}
|
|
3284
3322
|
} catch (err) {
|
|
3285
3323
|
if (isPermissionError(err)) continue;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import { parseMessage, parseSeqFromId } from './persist.js';
|
|
10
|
+
import { isVisibleConversationRow } from './internal-control.js';
|
|
10
11
|
|
|
11
12
|
function parseJsonLine(line) {
|
|
12
13
|
if (!line || !line.trim()) return null;
|
|
@@ -38,7 +39,7 @@ function searchableContent(msg) {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function matchesMessage(msg, terms) {
|
|
41
|
-
if (!msg || msg.role === 'tool') return false;
|
|
42
|
+
if (!msg || msg.role === 'tool' || !isVisibleConversationRow(msg)) return false;
|
|
42
43
|
const content = searchableContent(msg).toLocaleLowerCase();
|
|
43
44
|
return content.length > 0 && terms.every(term => content.includes(term));
|
|
44
45
|
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -7167,7 +7167,8 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
7167
7167
|
const afterSeqRaw = cacheIdentityMatches && msg && Number.isFinite(msg.afterSeq) ? msg.afterSeq : null;
|
|
7168
7168
|
const afterMessageId = (msg && typeof msg.afterMessageId === 'string') ? msg.afterMessageId : null;
|
|
7169
7169
|
let afterSeq = afterSeqRaw;
|
|
7170
|
-
|
|
7170
|
+
// A message-id cursor cannot bypass a stream/revision reset either.
|
|
7171
|
+
if (cacheIdentityMatches && afterSeq === null && afterMessageId && typeof session.conversationStore.getMessageSeqById === 'function') {
|
|
7171
7172
|
afterSeq = session.conversationStore.getMessageSeqById(afterMessageId);
|
|
7172
7173
|
}
|
|
7173
7174
|
if (sessionId && afterSeq !== null && typeof session.conversationStore.loadAfterSeqByGroup === 'function') {
|
|
@@ -7317,18 +7318,18 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
7317
7318
|
);
|
|
7318
7319
|
const afterSeqRaw = cacheIdentityMatches && msg && Number.isFinite(msg.afterSeq) ? msg.afterSeq : null;
|
|
7319
7320
|
traceDuration('history.cold_store_open', coldStoreStart);
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7321
|
+
let afterSeq = afterSeqRaw;
|
|
7322
|
+
if (cacheIdentityMatches && afterSeq === null && afterMessageId && typeof coldStore.getMessageSeqById === 'function') {
|
|
7323
|
+
afterSeq = coldStore.getMessageSeqById(afterMessageId);
|
|
7324
|
+
}
|
|
7325
|
+
// Match the warm path: stale or unresolvable cursors require a recent
|
|
7326
|
+
// replay, not an empty delta that leaves a partial browser cache intact.
|
|
7327
|
+
if (sessionId && afterSeq !== null && typeof coldStore.loadAfterSeqByGroup === 'function') {
|
|
7325
7328
|
const loadStart = perfNowMs();
|
|
7326
|
-
const delta =
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
})
|
|
7331
|
-
: { messages: [], latestSeq: null, hasMoreAfter: false };
|
|
7329
|
+
const delta = coldStore.loadAfterSeqByGroup(sessionId, afterSeq, {
|
|
7330
|
+
limit: deltaLimit,
|
|
7331
|
+
maxBytes: deltaMaxBytes,
|
|
7332
|
+
});
|
|
7332
7333
|
traceDuration('history.store_load_delta', loadStart, { detail: { count: delta.messages?.length || 0, afterSeq, cold: true } });
|
|
7333
7334
|
const emitStart = perfNowMs();
|
|
7334
7335
|
const projectedMessages = emitHistoryChunk({
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const SUPPORTED_WORK_ITEM_ATTACHMENT_PLATFORMS = new Set(['linux', 'darwin', 'win32']);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WorkItem attachment storage has a descriptor-anchored Linux implementation
|
|
5
|
+
* and a checked, exclusive-create implementation for macOS and Windows.
|
|
6
|
+
*/
|
|
7
|
+
export function supportsWorkItemAttachments(platform = process.platform) {
|
|
8
|
+
return SUPPORTED_WORK_ITEM_ATTACHMENT_PLATFORMS.has(platform);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function assertWorkItemAttachmentPlatform(platform = process.platform) {
|
|
12
|
+
if (!supportsWorkItemAttachments(platform)) {
|
|
13
|
+
throw new Error(`Secure WorkItem attachment storage is unavailable on ${platform || 'this platform'}`);
|
|
14
|
+
}
|
|
15
|
+
return platform;
|
|
16
|
+
}
|