fraim-hub 2.0.318 → 2.0.319
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/src/ai-hub/conversation-store.js +12 -10
- package/dist/src/ai-hub/history-migration.js +7 -4
- package/dist/src/ai-hub/history-policy.js +8 -0
- package/dist/src/ai-hub/history-writer-compatibility.js +6 -1
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +11 -1
- package/package.json +2 -2
|
@@ -652,12 +652,12 @@ class AiHubConversationStore {
|
|
|
652
652
|
if (fs_1.default.existsSync(path_1.default.join(dir, 'pending-head.json')) || fs_1.default.existsSync(path_1.default.join(dir, 'header-repair.json')))
|
|
653
653
|
(0, conversation_store_lock_1.withBucketLock)(this.lockPath(dir), () => this.repairDerived(dir, key));
|
|
654
654
|
const idx = this.loadIndex(dir, key);
|
|
655
|
-
|
|
655
|
+
// A legacy index (headers with no precomputed counts) or a rebuilding one both queue
|
|
656
|
+
// the same background upgrade, but neither blocks the read: counts are computed
|
|
657
|
+
// on the fly below from the headers already on hand, same as pre-#1682 behavior.
|
|
658
|
+
if ((idx.headers.length && !idx.counts) || idx.rebuilding) {
|
|
656
659
|
(0, history_migration_1.queueHistoryMigration)({ stateFile: this.stateFilePath, source: this.indexPath(dir), mode: 'index', bucketKey: key });
|
|
657
|
-
throw new history_policy_1.HistoryStoreError('history_migrating');
|
|
658
660
|
}
|
|
659
|
-
if (idx.rebuilding)
|
|
660
|
-
(0, history_migration_1.queueHistoryMigration)({ stateFile: this.stateFilePath, source: this.indexPath(dir), mode: 'index', bucketKey: key });
|
|
661
661
|
const counts = idx.counts ?? (0, conversation_ui_state_1.emptyConversationCounts)();
|
|
662
662
|
if (!idx.counts)
|
|
663
663
|
for (const header of idx.headers)
|
|
@@ -1130,19 +1130,21 @@ class AiHubConversationStore {
|
|
|
1130
1130
|
return cached.index;
|
|
1131
1131
|
}
|
|
1132
1132
|
this.hotIndexCache.delete(bucketDir);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1133
|
+
// An oversized or over-long legacy index queues its own background upgrade to
|
|
1134
|
+
// bounded headers, but a read must never block on that migration: it still parses
|
|
1135
|
+
// and returns the index directly below, just without the hot in-memory cache
|
|
1136
|
+
// (cacheIndex already refuses to cache anything this large, so nothing leaks).
|
|
1137
|
+
const oversizedFile = stat.size > history_policy_1.HISTORY_POLICY.indexBytes;
|
|
1138
|
+
if (oversizedFile && this.options.skipLegacyMigration)
|
|
1139
|
+
return null;
|
|
1140
|
+
if (oversizedFile)
|
|
1136
1141
|
(0, history_migration_1.queueHistoryMigration)({ stateFile: this.stateFilePath, source: file, mode: 'index' });
|
|
1137
|
-
throw new history_policy_1.HistoryStoreError('history_migrating');
|
|
1138
|
-
}
|
|
1139
1142
|
const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
1140
1143
|
if (Array.isArray(parsed.headers)) {
|
|
1141
1144
|
if (parsed.headers.length > history_policy_1.HISTORY_POLICY.hotHeaders) {
|
|
1142
1145
|
if (this.options.skipLegacyMigration)
|
|
1143
1146
|
return null;
|
|
1144
1147
|
(0, history_migration_1.queueHistoryMigration)({ stateFile: this.stateFilePath, source: file, mode: 'index' });
|
|
1145
|
-
throw new history_policy_1.HistoryStoreError('history_migrating');
|
|
1146
1148
|
}
|
|
1147
1149
|
return { ...parsed, activeId: parsed.activeId ?? null, headers: parsed.headers };
|
|
1148
1150
|
}
|
|
@@ -32,8 +32,11 @@ function queueHistoryMigration(request) {
|
|
|
32
32
|
if (running)
|
|
33
33
|
return;
|
|
34
34
|
const priorFailure = failures.get(request.source);
|
|
35
|
-
if (priorFailure)
|
|
36
|
-
|
|
35
|
+
if (priorFailure) {
|
|
36
|
+
if (Date.now() - priorFailure.at < history_policy_1.HISTORY_POLICY.migrationRetryAfterMs)
|
|
37
|
+
throw new history_policy_1.HistoryStoreError(priorFailure.code);
|
|
38
|
+
failures.delete(request.source);
|
|
39
|
+
}
|
|
37
40
|
const worker = new worker_threads_1.Worker(`
|
|
38
41
|
const { workerData, parentPort } = require('worker_threads');
|
|
39
42
|
if (workerData.module.endsWith('.ts')) require('tsx/cjs');
|
|
@@ -50,12 +53,12 @@ function queueHistoryMigration(request) {
|
|
|
50
53
|
}
|
|
51
54
|
if (!message.ok && message.code !== 'history_migrating' && message.code !== 'history_upgrade_required') {
|
|
52
55
|
workerCounters.failures++;
|
|
53
|
-
failures.set(request.source, message.code);
|
|
56
|
+
failures.set(request.source, { code: message.code, at: Date.now() });
|
|
54
57
|
if (failures.size > 16)
|
|
55
58
|
failures.delete(failures.keys().next().value);
|
|
56
59
|
}
|
|
57
60
|
});
|
|
58
|
-
worker.on('error', () => { workerCounters.failures++; failures.set(request.source, 'history_migration_failed'); });
|
|
61
|
+
worker.on('error', () => { workerCounters.failures++; failures.set(request.source, { code: 'history_migration_failed', at: Date.now() }); });
|
|
59
62
|
worker.on('exit', () => { running = undefined; resolve(); });
|
|
60
63
|
});
|
|
61
64
|
running = { source: request.source, completion };
|
|
@@ -16,6 +16,14 @@ exports.HISTORY_POLICY = Object.freeze({
|
|
|
16
16
|
maxPageSize: 100,
|
|
17
17
|
responseBytes: 2 * 1024 * 1024,
|
|
18
18
|
controlDocumentBytes: 16 * 1024 * 1024,
|
|
19
|
+
// A migration source that just failed (e.g. a transient Windows file lock) is retried
|
|
20
|
+
// after this window instead of being blacklisted for the life of the process.
|
|
21
|
+
migrationRetryAfterMs: 30 * 1000,
|
|
22
|
+
// A writer-compatibility registration this old is treated as stale regardless of
|
|
23
|
+
// whether its recorded pid currently matches a live process: Windows recycles pids
|
|
24
|
+
// quickly, and a crashed writer that never removed its own file can otherwise match
|
|
25
|
+
// an unrelated later process and block migration store-wide, forever.
|
|
26
|
+
staleRuntimeRegistrationMs: 24 * 60 * 60 * 1000,
|
|
19
27
|
});
|
|
20
28
|
class HistoryStoreError extends Error {
|
|
21
29
|
constructor(code, statusCode = 503) {
|
|
@@ -41,7 +41,12 @@ function assertHistoryMigrationAllowed(stateFile, ownerPid) {
|
|
|
41
41
|
}
|
|
42
42
|
if (!Number.isSafeInteger(runtime.pid) || runtime.pid <= 0)
|
|
43
43
|
throw new history_policy_1.HistoryStoreError('history_upgrade_required');
|
|
44
|
-
|
|
44
|
+
// A pid match alone is not proof of liveness for an old registration: Windows
|
|
45
|
+
// recycles pids quickly, so a crashed writer's leftover file can otherwise match
|
|
46
|
+
// an unrelated process started long afterward.
|
|
47
|
+
const startedAtMs = typeof runtime.startedAt === 'string' ? Date.parse(runtime.startedAt) : NaN;
|
|
48
|
+
const stale = Number.isFinite(startedAtMs) && Date.now() - startedAtMs > history_policy_1.HISTORY_POLICY.staleRuntimeRegistrationMs;
|
|
49
|
+
if (runtime.pid === process.pid || stale || !(0, process_liveness_1.isPidAlive)(runtime.pid))
|
|
45
50
|
continue;
|
|
46
51
|
if (runtime.historyFormatVersion !== 3)
|
|
47
52
|
throw new history_policy_1.HistoryStoreError('history_upgrade_required');
|
|
@@ -138,6 +138,16 @@ function runtimeFromEnv(env = process.env) {
|
|
|
138
138
|
? { executable: env.FRAIM_PACKAGED_CLI_EXECUTABLE, script: env.FRAIM_PACKAGED_CLI_SCRIPT }
|
|
139
139
|
: null;
|
|
140
140
|
}
|
|
141
|
+
// Regular Node.js (and ELECTRON_RUN_AS_NODE) cannot see inside Electron's
|
|
142
|
+
// .asar archives. For paths like …/app.asar/node_modules/…, verify the
|
|
143
|
+
// archive file itself rather than the internal entry.
|
|
144
|
+
function existsOrAsarExists(filePath) {
|
|
145
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
146
|
+
const asarSep = normalized.indexOf('.asar/');
|
|
147
|
+
if (asarSep === -1)
|
|
148
|
+
return fs_1.default.existsSync(filePath);
|
|
149
|
+
return fs_1.default.existsSync(filePath.slice(0, asarSep + '.asar'.length));
|
|
150
|
+
}
|
|
141
151
|
function getPackagedFraimRuntime(env = process.env) {
|
|
142
152
|
const envRuntime = runtimeFromEnv(env);
|
|
143
153
|
if (envRuntime)
|
|
@@ -146,7 +156,7 @@ function getPackagedFraimRuntime(env = process.env) {
|
|
|
146
156
|
const raw = JSON.parse(fs_1.default.readFileSync(getPackagedFraimRuntimePath(), 'utf8'));
|
|
147
157
|
if (!raw.executable || !raw.script)
|
|
148
158
|
return null;
|
|
149
|
-
if (!fs_1.default.existsSync(raw.executable) || !
|
|
159
|
+
if (!fs_1.default.existsSync(raw.executable) || !existsOrAsarExists(raw.script))
|
|
150
160
|
return null;
|
|
151
161
|
return { executable: raw.executable, script: raw.script };
|
|
152
162
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.319",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"author": "Sid Mathur <sid.mathur@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/mathursrus/FRAIM#readme",
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
"electron-updater": "^6.8.9",
|
|
213
213
|
"express": "^5.2.1",
|
|
214
214
|
"extract-zip": "^2.0.1",
|
|
215
|
-
"fraim": "2.0.
|
|
215
|
+
"fraim": "2.0.319",
|
|
216
216
|
"mongodb": "^7.0.0",
|
|
217
217
|
"node-cron": "4.2.1",
|
|
218
218
|
"node-edge-tts": "^1.2.10",
|