@remnic/core 10.0.64 → 10.0.66
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/access-admin-ops-surface.js +2 -2
- package/dist/access-authorization-probe.js +3 -3
- package/dist/access-boundary.js +2 -2
- package/dist/access-cli.js +21 -21
- package/dist/access-extraction-force-flush.js +2 -2
- package/dist/access-http-briefing.js +3 -3
- package/dist/access-http-query.js +2 -2
- package/dist/access-http.js +14 -14
- package/dist/access-identity-continuity-surface.js +2 -2
- package/dist/access-lcm-surface.js +2 -2
- package/dist/access-mcp.js +9 -9
- package/dist/access-namespace-preflight.js +2 -2
- package/dist/access-observe-write-surface.js +2 -2
- package/dist/access-operations-batch.js +3 -3
- package/dist/access-operations.js +8 -8
- package/dist/access-recall-concurrency.js +2 -2
- package/dist/access-recall-response.js +2 -2
- package/dist/access-recall-surface.js +2 -2
- package/dist/access-service.js +2 -2
- package/dist/access-surface-catalog.js +4 -4
- package/dist/buffer.js +2 -2
- package/dist/{chunk-3LSTL6EY.js → chunk-3H25IRJJ.js} +2 -2
- package/dist/{chunk-HRRAOTOQ.js → chunk-3NJJQAL3.js} +8 -8
- package/dist/{chunk-FKGLJJVS.js → chunk-A5FOXI3B.js} +2 -2
- package/dist/{chunk-JPY3RMG2.js → chunk-E6VQJ75T.js} +2 -2
- package/dist/{chunk-MRF5V5CD.js → chunk-F32JRMX2.js} +2 -2
- package/dist/{chunk-N4T5FW6U.js → chunk-FMYAU5MP.js} +2 -2
- package/dist/{chunk-OVL5AD3N.js → chunk-IPCSZLLF.js} +2 -2
- package/dist/{chunk-7JYK7NNY.js → chunk-JYXFIILI.js} +2 -2
- package/dist/{chunk-HOR4RMUM.js → chunk-KFPJIKUW.js} +5 -5
- package/dist/{chunk-KQJUEQ2E.js → chunk-PL75KQGF.js} +3 -3
- package/dist/{chunk-KNMSD2MQ.js → chunk-QAN5ZDRS.js} +2 -2
- package/dist/{chunk-6D3IWYNY.js → chunk-QNZPYH36.js} +6 -6
- package/dist/chunk-QNZPYH36.js.map +1 -0
- package/dist/{chunk-GRAUQXQB.js → chunk-RAU25Q6J.js} +2 -2
- package/dist/{chunk-HHL4MX3W.js → chunk-SH6ZYUTX.js} +2 -2
- package/dist/{chunk-T22FBDG3.js → chunk-T763ADEK.js} +2 -2
- package/dist/{chunk-UH6ROXFJ.js → chunk-VC65GNUS.js} +2 -2
- package/dist/{chunk-YJQJRXUI.js → chunk-VC7GKR2V.js} +2 -2
- package/dist/{chunk-R7WYAXPO.js → chunk-VHSQU3RO.js} +81 -42
- package/dist/chunk-VHSQU3RO.js.map +1 -0
- package/dist/{chunk-JL5HXL25.js → chunk-VSZNPAGL.js} +2 -2
- package/dist/{chunk-2ZSRACNK.js → chunk-XMBDI725.js} +15 -38
- package/dist/chunk-XMBDI725.js.map +1 -0
- package/dist/{chunk-P2EM5LKH.js → chunk-ZGHPELZ6.js} +2 -2
- package/dist/cli.js +16 -16
- package/dist/external-wiki-access.js +3 -3
- package/dist/extraction.js +3 -3
- package/dist/index.js +21 -21
- package/dist/memory-browse-operations.js +3 -3
- package/dist/orchestration/buffer-drain-runner.js +21 -21
- package/dist/orchestration/extraction-persist-deps.d.ts +0 -2
- package/dist/orchestration/extraction-persist-deps.d.ts.map +1 -1
- package/dist/orchestration/extraction-persist.d.ts +10 -4
- package/dist/orchestration/extraction-persist.d.ts.map +1 -1
- package/dist/orchestration/extraction-run-helpers.d.ts +3 -2
- package/dist/orchestration/extraction-run-helpers.d.ts.map +1 -1
- package/dist/orchestration/extraction-run.d.ts +3 -6
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestrator.d.ts +2 -17
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +21 -21
- package/dist/recall-navigation-operations.js +3 -3
- package/dist/recall-navigation.js +2 -2
- package/dist/source-agent-qualifier.js +2 -2
- package/dist/support-passport/index.js +5 -5
- package/dist/threading.d.ts +21 -4
- package/dist/threading.d.ts.map +1 -1
- package/dist/threading.js +1 -1
- package/package.json +2 -2
- package/src/access-observe-replay-durability.test.ts +1 -1
- package/src/connector-provenance-lifecycle.test.ts +5 -8
- package/src/faithfulness-graph-guard.test.ts +11 -6
- package/src/harmonic-construction.test.ts +16 -10
- package/src/orchestration/extraction-persist-deps.ts +0 -2
- package/src/orchestration/extraction-persist.ts +18 -10
- package/src/orchestration/extraction-run-helpers.ts +5 -4
- package/src/orchestration/extraction-run.test.ts +1 -2
- package/src/orchestration/extraction-run.ts +6 -6
- package/src/orchestrator.ts +6 -22
- package/src/testing/orchestrator-lite.ts +1 -1
- package/src/threading.test.ts +153 -0
- package/src/threading.ts +95 -45
- package/dist/chunk-2ZSRACNK.js.map +0 -1
- package/dist/chunk-6D3IWYNY.js.map +0 -1
- package/dist/chunk-R7WYAXPO.js.map +0 -1
- /package/dist/{chunk-3LSTL6EY.js.map → chunk-3H25IRJJ.js.map} +0 -0
- /package/dist/{chunk-HRRAOTOQ.js.map → chunk-3NJJQAL3.js.map} +0 -0
- /package/dist/{chunk-FKGLJJVS.js.map → chunk-A5FOXI3B.js.map} +0 -0
- /package/dist/{chunk-JPY3RMG2.js.map → chunk-E6VQJ75T.js.map} +0 -0
- /package/dist/{chunk-MRF5V5CD.js.map → chunk-F32JRMX2.js.map} +0 -0
- /package/dist/{chunk-N4T5FW6U.js.map → chunk-FMYAU5MP.js.map} +0 -0
- /package/dist/{chunk-OVL5AD3N.js.map → chunk-IPCSZLLF.js.map} +0 -0
- /package/dist/{chunk-7JYK7NNY.js.map → chunk-JYXFIILI.js.map} +0 -0
- /package/dist/{chunk-HOR4RMUM.js.map → chunk-KFPJIKUW.js.map} +0 -0
- /package/dist/{chunk-KQJUEQ2E.js.map → chunk-PL75KQGF.js.map} +0 -0
- /package/dist/{chunk-KNMSD2MQ.js.map → chunk-QAN5ZDRS.js.map} +0 -0
- /package/dist/{chunk-GRAUQXQB.js.map → chunk-RAU25Q6J.js.map} +0 -0
- /package/dist/{chunk-HHL4MX3W.js.map → chunk-SH6ZYUTX.js.map} +0 -0
- /package/dist/{chunk-T22FBDG3.js.map → chunk-T763ADEK.js.map} +0 -0
- /package/dist/{chunk-UH6ROXFJ.js.map → chunk-VC65GNUS.js.map} +0 -0
- /package/dist/{chunk-YJQJRXUI.js.map → chunk-VC7GKR2V.js.map} +0 -0
- /package/dist/{chunk-JL5HXL25.js.map → chunk-VSZNPAGL.js.map} +0 -0
- /package/dist/{chunk-P2EM5LKH.js.map → chunk-ZGHPELZ6.js.map} +0 -0
|
@@ -119,16 +119,48 @@ function parseConversationThread(raw) {
|
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
var ThreadingManager = class {
|
|
122
|
+
var ThreadingManager = class _ThreadingManager {
|
|
123
123
|
constructor(threadsDir, gapMinutes = 30) {
|
|
124
124
|
this.threadsDir = threadsDir;
|
|
125
125
|
this.gapMinutes = gapMinutes;
|
|
126
126
|
}
|
|
127
127
|
threadsDir;
|
|
128
128
|
gapMinutes;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Per-session thread-run state. One global slot let two concurrent
|
|
131
|
+
* extractions (bounded-parallel queue) interleave sessions and attach
|
|
132
|
+
* turns to the wrong thread, so the run state is keyed by session.
|
|
133
|
+
* Bounded: a long-lived daemon sees one entry per active session.
|
|
134
|
+
*/
|
|
135
|
+
runStateBySession = /* @__PURE__ */ new Map();
|
|
136
|
+
/**
|
|
137
|
+
* Run states with a processTurn awaiting inside them. A session parked on
|
|
138
|
+
* `saveThread` can watch 512 other sessions insert and evict; dropping its
|
|
139
|
+
* state mid-await loses its thread id and forks its thread, so eviction
|
|
140
|
+
* never takes an in-flight state (the cap may be exceeded briefly by the
|
|
141
|
+
* number of in-flight extractions, which is bounded by the queue).
|
|
142
|
+
*/
|
|
143
|
+
inFlightRunStates = /* @__PURE__ */ new Set();
|
|
144
|
+
static RUN_STATE_MAX_SESSIONS = 512;
|
|
145
|
+
runStateFor(turn) {
|
|
146
|
+
const key = turn.sessionKey ?? "";
|
|
147
|
+
let state = this.runStateBySession.get(key);
|
|
148
|
+
if (state) {
|
|
149
|
+
this.runStateBySession.delete(key);
|
|
150
|
+
this.runStateBySession.set(key, state);
|
|
151
|
+
} else {
|
|
152
|
+
state = { currentThreadId: null, lastTurnTimestamp: null };
|
|
153
|
+
this.runStateBySession.set(key, state);
|
|
154
|
+
if (this.runStateBySession.size > _ThreadingManager.RUN_STATE_MAX_SESSIONS) {
|
|
155
|
+
for (const [oldestKey, oldest] of this.runStateBySession) {
|
|
156
|
+
if (oldest === state || this.inFlightRunStates.has(oldest)) continue;
|
|
157
|
+
this.runStateBySession.delete(oldestKey);
|
|
158
|
+
if (this.runStateBySession.size <= _ThreadingManager.RUN_STATE_MAX_SESSIONS) break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return state;
|
|
163
|
+
}
|
|
132
164
|
async ensureDirectory() {
|
|
133
165
|
await mkdir(this.threadsDir, { recursive: true });
|
|
134
166
|
}
|
|
@@ -182,20 +214,20 @@ var ThreadingManager = class {
|
|
|
182
214
|
}
|
|
183
215
|
/**
|
|
184
216
|
* Check if we should start a new thread based on session key and time gap.
|
|
217
|
+
* State is per session, so the cross-session check the shared slot needed
|
|
218
|
+
* is gone: different sessions never share run state at all.
|
|
185
219
|
*/
|
|
186
220
|
shouldStartNewThread(turn) {
|
|
187
221
|
const turnTime = new Date(turn.timestamp).getTime();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (this.lastTurnTimestamp) {
|
|
192
|
-
const gapMs = turnTime - this.lastTurnTimestamp;
|
|
222
|
+
const state = this.runStateFor(turn);
|
|
223
|
+
if (state.lastTurnTimestamp) {
|
|
224
|
+
const gapMs = turnTime - state.lastTurnTimestamp;
|
|
193
225
|
const gapMinutes = gapMs / (1e3 * 60);
|
|
194
226
|
if (gapMinutes > this.gapMinutes) {
|
|
195
227
|
return true;
|
|
196
228
|
}
|
|
197
229
|
}
|
|
198
|
-
if (!
|
|
230
|
+
if (!state.currentThreadId) {
|
|
199
231
|
return true;
|
|
200
232
|
}
|
|
201
233
|
return false;
|
|
@@ -207,36 +239,41 @@ var ThreadingManager = class {
|
|
|
207
239
|
async processTurn(turn, episodeIds) {
|
|
208
240
|
await this.ensureDirectory();
|
|
209
241
|
const turnTime = new Date(turn.timestamp).getTime();
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
242
|
+
const state = this.runStateFor(turn);
|
|
243
|
+
this.inFlightRunStates.add(state);
|
|
244
|
+
try {
|
|
245
|
+
if (this.shouldStartNewThread(turn)) {
|
|
246
|
+
const threadId = `thread-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
247
|
+
const thread = {
|
|
248
|
+
id: threadId,
|
|
249
|
+
title: "New Thread",
|
|
250
|
+
// Will be updated later with keywords
|
|
251
|
+
createdAt: turn.timestamp,
|
|
252
|
+
updatedAt: turn.timestamp,
|
|
253
|
+
sessionKey: turn.sessionKey,
|
|
254
|
+
episodeIds: [...episodeIds],
|
|
255
|
+
linkedThreadIds: []
|
|
256
|
+
};
|
|
257
|
+
await this.saveThread(thread);
|
|
258
|
+
state.currentThreadId = threadId;
|
|
259
|
+
log.debug(`created new thread ${threadId}`);
|
|
260
|
+
} else if (state.currentThreadId) {
|
|
261
|
+
const thread = await this.loadThread(state.currentThreadId);
|
|
262
|
+
if (thread) {
|
|
263
|
+
for (const id of episodeIds) {
|
|
264
|
+
if (!thread.episodeIds.includes(id)) {
|
|
265
|
+
thread.episodeIds.push(id);
|
|
266
|
+
}
|
|
231
267
|
}
|
|
268
|
+
thread.updatedAt = turn.timestamp;
|
|
269
|
+
await this.saveThread(thread);
|
|
232
270
|
}
|
|
233
|
-
thread.updatedAt = turn.timestamp;
|
|
234
|
-
await this.saveThread(thread);
|
|
235
271
|
}
|
|
272
|
+
state.lastTurnTimestamp = turnTime;
|
|
273
|
+
return state.currentThreadId;
|
|
274
|
+
} finally {
|
|
275
|
+
this.inFlightRunStates.delete(state);
|
|
236
276
|
}
|
|
237
|
-
this.lastTurnTimestamp = turnTime;
|
|
238
|
-
this.lastSessionKey = turn.sessionKey ?? null;
|
|
239
|
-
return this.currentThreadId;
|
|
240
277
|
}
|
|
241
278
|
/**
|
|
242
279
|
* Append persisted memory IDs to an existing thread.
|
|
@@ -351,22 +388,24 @@ var ThreadingManager = class {
|
|
|
351
388
|
return true;
|
|
352
389
|
}
|
|
353
390
|
/**
|
|
354
|
-
* Get the current thread ID (if any).
|
|
391
|
+
* Get the current thread ID (if any). With per-session run state there is
|
|
392
|
+
* no single current thread; null unless exactly one session is tracked.
|
|
355
393
|
*/
|
|
356
394
|
getCurrentThreadId() {
|
|
357
|
-
|
|
395
|
+
if (this.runStateBySession.size === 1) {
|
|
396
|
+
return this.runStateBySession.values().next().value?.currentThreadId ?? null;
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
358
399
|
}
|
|
359
400
|
/**
|
|
360
401
|
* Reset threading state (e.g., on plugin restart).
|
|
361
402
|
*/
|
|
362
403
|
reset() {
|
|
363
|
-
this.
|
|
364
|
-
this.lastTurnTimestamp = null;
|
|
365
|
-
this.lastSessionKey = null;
|
|
404
|
+
this.runStateBySession.clear();
|
|
366
405
|
}
|
|
367
406
|
};
|
|
368
407
|
|
|
369
408
|
export {
|
|
370
409
|
ThreadingManager
|
|
371
410
|
};
|
|
372
|
-
//# sourceMappingURL=chunk-
|
|
411
|
+
//# sourceMappingURL=chunk-VHSQU3RO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/threading.ts"],"sourcesContent":["/**\n * Conversation Threading (Phase 3B)\n *\n * Groups related memories into conversation threads with auto-generated titles.\n * Thread boundary detection: new session key OR time gap > threshold.\n */\n\nimport { readdir, readFile, writeFile, mkdir, stat } from \"node:fs/promises\";\nimport { log } from \"./logger.js\";\nimport type { BufferTurn, ConversationThread } from \"./types.js\";\nimport {\n encodeStoragePathSegment,\n encodeStoragePathSegmentWithHash,\n isSafeLegacyPathSegment,\n resolveSafeStoragePath,\n} from \"./storage-paths.js\";\n\n/** Stop words for title extraction */\nconst STOP_WORDS = new Set([\n \"a\", \"an\", \"the\", \"is\", \"are\", \"was\", \"were\", \"be\", \"been\", \"being\",\n \"have\", \"has\", \"had\", \"do\", \"does\", \"did\", \"will\", \"would\", \"could\",\n \"should\", \"may\", \"might\", \"must\", \"shall\", \"can\", \"need\", \"to\", \"of\",\n \"in\", \"for\", \"on\", \"with\", \"at\", \"by\", \"from\", \"as\", \"into\", \"through\",\n \"during\", \"before\", \"after\", \"above\", \"below\", \"between\", \"this\", \"that\",\n \"these\", \"those\", \"i\", \"me\", \"my\", \"we\", \"our\", \"you\", \"your\", \"he\",\n \"she\", \"it\", \"they\", \"them\", \"their\", \"what\", \"which\", \"who\", \"how\",\n \"when\", \"where\", \"why\", \"and\", \"but\", \"or\", \"if\", \"because\", \"so\",\n \"just\", \"about\", \"like\", \"also\", \"very\", \"really\", \"here\", \"there\",\n]);\n\n/**\n * Extract top keywords from content for thread title.\n * Simple TF approach (no IDF needed for single thread).\n */\nfunction extractKeywords(content: string, maxKeywords: number = 3): string[] {\n const words = content\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length >= 4 && !STOP_WORDS.has(w));\n\n // Count frequencies\n const freq = new Map<string, number>();\n for (const word of words) {\n freq.set(word, (freq.get(word) ?? 0) + 1);\n }\n\n // Sort by frequency, take top N\n return [...freq.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, maxKeywords)\n .map(([word]) => word);\n}\n\n/**\n * Generate a thread title from keywords.\n */\nfunction generateTitle(keywords: string[]): string {\n if (keywords.length === 0) return \"Untitled Thread\";\n // Capitalize and join\n return keywords\n .map((k) => k.charAt(0).toUpperCase() + k.slice(1))\n .join(\", \");\n}\n\nfunction isConversationThread(value: unknown): value is ConversationThread {\n if (value === null || typeof value !== \"object\") return false;\n const candidate = value as Partial<ConversationThread>;\n return (\n typeof candidate.id === \"string\" &&\n typeof candidate.title === \"string\" &&\n typeof candidate.createdAt === \"string\" &&\n typeof candidate.updatedAt === \"string\" &&\n Array.isArray(candidate.episodeIds) &&\n Array.isArray(candidate.linkedThreadIds)\n );\n}\n\nfunction parseConversationThread(raw: string): ConversationThread | null {\n try {\n const parsed = JSON.parse(raw) as unknown;\n return isConversationThread(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/** One session's thread-run state: its current thread and last turn time. */\ninterface ThreadingRunState {\n currentThreadId: string | null;\n lastTurnTimestamp: number | null;\n}\n\nexport class ThreadingManager {\n /**\n * Per-session thread-run state. One global slot let two concurrent\n * extractions (bounded-parallel queue) interleave sessions and attach\n * turns to the wrong thread, so the run state is keyed by session.\n * Bounded: a long-lived daemon sees one entry per active session.\n */\n private readonly runStateBySession = new Map<string, ThreadingRunState>();\n /**\n * Run states with a processTurn awaiting inside them. A session parked on\n * `saveThread` can watch 512 other sessions insert and evict; dropping its\n * state mid-await loses its thread id and forks its thread, so eviction\n * never takes an in-flight state (the cap may be exceeded briefly by the\n * number of in-flight extractions, which is bounded by the queue).\n */\n private readonly inFlightRunStates = new Set<ThreadingRunState>();\n private static readonly RUN_STATE_MAX_SESSIONS = 512;\n\n private runStateFor(turn: BufferTurn): ThreadingRunState {\n const key = turn.sessionKey ?? \"\";\n let state = this.runStateBySession.get(key);\n if (state) {\n // Refresh recency: Map iteration is insertion-ordered, so re-putting a\n // touched session makes eviction true LRU instead of first-in-first-out —\n // an active session must not be the oldest entry just because it started\n // first.\n this.runStateBySession.delete(key);\n this.runStateBySession.set(key, state);\n } else {\n state = { currentThreadId: null, lastTurnTimestamp: null };\n this.runStateBySession.set(key, state);\n if (this.runStateBySession.size > ThreadingManager.RUN_STATE_MAX_SESSIONS) {\n for (const [oldestKey, oldest] of this.runStateBySession) {\n // Never evict the state being inserted: before the caller registers\n // it in-flight, every older entry can be in flight and the loop\n // would otherwise delete this turn's own state and fork the thread.\n if (oldest === state || this.inFlightRunStates.has(oldest)) continue;\n this.runStateBySession.delete(oldestKey);\n if (this.runStateBySession.size <= ThreadingManager.RUN_STATE_MAX_SESSIONS) break;\n }\n }\n }\n return state;\n }\n\n constructor(\n private readonly threadsDir: string,\n private readonly gapMinutes: number = 30,\n ) {}\n\n async ensureDirectory(): Promise<void> {\n await mkdir(this.threadsDir, { recursive: true });\n }\n\n private encodedThreadFileName(threadId: string): string {\n return `${encodeStoragePathSegment(threadId)}.json`;\n }\n\n private collisionSafeThreadFileName(threadId: string): string {\n return `${encodeStoragePathSegmentWithHash(threadId)}.json`;\n }\n\n private legacyThreadFileName(threadId: string): string | undefined {\n if (!isSafeLegacyPathSegment(threadId)) return undefined;\n const legacyFile = `${threadId}.json`;\n return legacyFile === this.encodedThreadFileName(threadId) ? undefined : legacyFile;\n }\n\n private async threadCandidateStatus(\n filePath: string,\n threadId: string,\n ): Promise<\"missing\" | \"matches\" | \"invalid\" | \"mismatches\"> {\n try {\n if (!(await stat(filePath)).isFile()) return \"mismatches\";\n const thread = parseConversationThread(await readFile(filePath, \"utf-8\"));\n if (!thread) return \"invalid\";\n return thread.id === threadId ? \"matches\" : \"mismatches\";\n } catch (err) {\n const code =\n err && typeof err === \"object\" && \"code\" in err\n ? (err as { code?: string }).code\n : undefined;\n if (code === \"ENOENT\") return \"missing\";\n throw err;\n }\n }\n\n private async threadFilePathForWrite(threadId: string): Promise<string> {\n const encodedPath = await resolveSafeStoragePath(\n this.threadsDir,\n this.encodedThreadFileName(threadId),\n );\n const encodedStatus = await this.threadCandidateStatus(encodedPath, threadId);\n if (encodedStatus === \"matches\") return encodedPath;\n\n const legacyFile = this.legacyThreadFileName(threadId);\n if (legacyFile) {\n const legacyPath = await resolveSafeStoragePath(this.threadsDir, legacyFile);\n if ((await this.threadCandidateStatus(legacyPath, threadId)) === \"matches\") {\n return legacyPath;\n }\n }\n\n if (encodedStatus === \"missing\") return encodedPath;\n\n const collisionSafePath = await resolveSafeStoragePath(\n this.threadsDir,\n this.collisionSafeThreadFileName(threadId),\n );\n const collisionSafeStatus = await this.threadCandidateStatus(collisionSafePath, threadId);\n if (collisionSafeStatus === \"mismatches\") {\n throw new Error(`thread storage path collision for thread id: ${threadId}`);\n }\n\n return collisionSafePath;\n }\n\n /**\n * Check if we should start a new thread based on session key and time gap.\n * State is per session, so the cross-session check the shared slot needed\n * is gone: different sessions never share run state at all.\n */\n shouldStartNewThread(turn: BufferTurn): boolean {\n const turnTime = new Date(turn.timestamp).getTime();\n const state = this.runStateFor(turn);\n\n // Time gap > threshold = new thread\n if (state.lastTurnTimestamp) {\n const gapMs = turnTime - state.lastTurnTimestamp;\n const gapMinutes = gapMs / (1000 * 60);\n if (gapMinutes > this.gapMinutes) {\n return true;\n }\n }\n\n // No current thread = new thread\n if (!state.currentThreadId) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Process a turn and return the thread ID it belongs to.\n * Creates a new thread if needed.\n */\n async processTurn(turn: BufferTurn, episodeIds: string[]): Promise<string> {\n await this.ensureDirectory();\n\n const turnTime = new Date(turn.timestamp).getTime();\n const state = this.runStateFor(turn);\n this.inFlightRunStates.add(state);\n try {\n if (this.shouldStartNewThread(turn)) {\n // Create new thread\n const threadId = `thread-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;\n const thread: ConversationThread = {\n id: threadId,\n title: \"New Thread\", // Will be updated later with keywords\n createdAt: turn.timestamp,\n updatedAt: turn.timestamp,\n sessionKey: turn.sessionKey,\n episodeIds: [...episodeIds],\n linkedThreadIds: [],\n };\n\n await this.saveThread(thread);\n state.currentThreadId = threadId;\n log.debug(`created new thread ${threadId}`);\n } else if (state.currentThreadId) {\n // Add episodes to current thread\n const thread = await this.loadThread(state.currentThreadId);\n if (thread) {\n for (const id of episodeIds) {\n if (!thread.episodeIds.includes(id)) {\n thread.episodeIds.push(id);\n }\n }\n thread.updatedAt = turn.timestamp;\n await this.saveThread(thread);\n }\n }\n\n state.lastTurnTimestamp = turnTime;\n\n return state.currentThreadId!;\n } finally {\n this.inFlightRunStates.delete(state);\n }\n }\n\n /**\n * Append persisted memory IDs to an existing thread.\n * Keeps IDs unique and updates the thread timestamp.\n */\n async appendEpisodeIds(threadId: string, episodeIds: string[]): Promise<void> {\n if (episodeIds.length === 0) return;\n const thread = await this.loadThread(threadId);\n if (!thread) return;\n\n let changed = false;\n for (const id of episodeIds) {\n if (!thread.episodeIds.includes(id)) {\n thread.episodeIds.push(id);\n changed = true;\n }\n }\n if (!changed) return;\n\n thread.updatedAt = new Date().toISOString();\n await this.saveThread(thread);\n }\n\n /**\n * Update thread title based on accumulated content.\n */\n async updateThreadTitle(threadId: string, content: string): Promise<void> {\n const thread = await this.loadThread(threadId);\n if (!thread) return;\n\n const keywords = extractKeywords(content);\n const title = generateTitle(keywords);\n\n if (title !== thread.title) {\n thread.title = title;\n thread.updatedAt = new Date().toISOString();\n await this.saveThread(thread);\n log.debug(`updated thread ${threadId} title: ${title}`);\n }\n }\n\n /**\n * Get all threads, sorted by updatedAt desc.\n */\n async getAllThreads(): Promise<ConversationThread[]> {\n try {\n const files = await readdir(this.threadsDir);\n const threadsById = new Map<\n string,\n { thread: ConversationThread; storagePriority: number }\n >();\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n try {\n const raw = await readFile(\n await resolveSafeStoragePath(this.threadsDir, file),\n \"utf-8\",\n );\n const thread = parseConversationThread(raw);\n if (!thread) continue;\n\n const legacyFile = this.legacyThreadFileName(thread.id);\n const storagePriority =\n file === this.encodedThreadFileName(thread.id)\n ? 3\n : file === this.collisionSafeThreadFileName(thread.id)\n ? 2\n : legacyFile && file === legacyFile\n ? 1\n : 0;\n const current = threadsById.get(thread.id);\n if (!current || storagePriority > current.storagePriority) {\n threadsById.set(thread.id, { thread, storagePriority });\n }\n } catch {\n // skip unreadable or malformed thread files\n }\n }\n\n return [...threadsById.values()].map(({ thread }) => thread).sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n );\n } catch {\n return [];\n }\n }\n\n /**\n * Get a thread by ID.\n */\n async loadThread(threadId: string): Promise<ConversationThread | null> {\n const candidates = [\n this.encodedThreadFileName(threadId),\n this.legacyThreadFileName(threadId),\n this.collisionSafeThreadFileName(threadId),\n ];\n for (const file of candidates) {\n if (!file) continue;\n try {\n const filePath = await resolveSafeStoragePath(this.threadsDir, file);\n const raw = await readFile(filePath, \"utf-8\");\n const thread = parseConversationThread(raw);\n if (thread?.id === threadId) return thread;\n } catch {\n // try the next candidate\n }\n }\n return null;\n }\n\n /**\n * Save a thread.\n */\n async saveThread(thread: ConversationThread): Promise<void> {\n await this.ensureDirectory();\n const filePath = await this.threadFilePathForWrite(thread.id);\n await writeFile(filePath, JSON.stringify(thread, null, 2), \"utf-8\");\n }\n\n /**\n * Link two threads together.\n */\n async linkThreads(threadId1: string, threadId2: string): Promise<boolean> {\n const thread1 = await this.loadThread(threadId1);\n const thread2 = await this.loadThread(threadId2);\n\n if (!thread1 || !thread2) return false;\n\n // Add bidirectional links\n if (!thread1.linkedThreadIds.includes(threadId2)) {\n thread1.linkedThreadIds.push(threadId2);\n await this.saveThread(thread1);\n }\n\n if (!thread2.linkedThreadIds.includes(threadId1)) {\n thread2.linkedThreadIds.push(threadId1);\n await this.saveThread(thread2);\n }\n\n return true;\n }\n\n /**\n * Get the current thread ID (if any). With per-session run state there is\n * no single current thread; null unless exactly one session is tracked.\n */\n getCurrentThreadId(): string | null {\n if (this.runStateBySession.size === 1) {\n return this.runStateBySession.values().next().value?.currentThreadId ?? null;\n }\n return null;\n }\n\n /**\n * Reset threading state (e.g., on plugin restart).\n */\n reset(): void {\n this.runStateBySession.clear();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
defineOperation,
|
|
22
22
|
parseNamespacePreflightWriteOp,
|
|
23
23
|
resolveAuthorizedNamespaceWritablePreflight
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-RAU25Q6J.js";
|
|
25
25
|
import {
|
|
26
26
|
parseSharedWriteOutputControls
|
|
27
27
|
} from "./chunk-IFBSZGW3.js";
|
|
@@ -618,4 +618,4 @@ defineOperation({ name: "graph_events", description: "SSE graph events.", schema
|
|
|
618
618
|
defineOperation({ name: "chat_message", description: "Chat message.", schema: strictSchema({ message: S.str, chatSessionId: S.str }), handler: async (_i, _ctx) => ({ result: { ok: true } }) });
|
|
619
619
|
defineOperation({ name: "chat_events", description: "SSE chat events.", schema: strictSchema({}), handler: async (_i, _ctx) => ({ result: { stream: "sse" } }) });
|
|
620
620
|
defineOperation({ name: "adapters_status", description: "Adapter registry metadata (issue #1850 round 5). Marker op: the HTTP transport builds the response from its own adapterRegistry, but the op still must be registered so it appears in listRegisteredOperations and enforceTokenOp can gate it for scoped tokens.", schema: strictSchema({}), handler: async (_i, _ctx) => ({ result: { ok: true } }) });
|
|
621
|
-
//# sourceMappingURL=chunk-
|
|
621
|
+
//# sourceMappingURL=chunk-VSZNPAGL.js.map
|
|
@@ -175,7 +175,7 @@ import {
|
|
|
175
175
|
} from "./chunk-VA3GNAWQ.js";
|
|
176
176
|
import {
|
|
177
177
|
ExtractionEngine
|
|
178
|
-
} from "./chunk-
|
|
178
|
+
} from "./chunk-A5FOXI3B.js";
|
|
179
179
|
import {
|
|
180
180
|
parseMemoryActionEligibilityContext
|
|
181
181
|
} from "./chunk-JHU4EYDV.js";
|
|
@@ -234,7 +234,7 @@ import {
|
|
|
234
234
|
runTimelinePublishCli,
|
|
235
235
|
screenCandidateFact,
|
|
236
236
|
summarizeVaultPublish
|
|
237
|
-
} from "./chunk-
|
|
237
|
+
} from "./chunk-KFPJIKUW.js";
|
|
238
238
|
import {
|
|
239
239
|
normalizeReplaySessionKey
|
|
240
240
|
} from "./chunk-AJVLDJU6.js";
|
|
@@ -247,7 +247,7 @@ import {
|
|
|
247
247
|
} from "./chunk-SHHZWSSE.js";
|
|
248
248
|
import {
|
|
249
249
|
ThreadingManager
|
|
250
|
-
} from "./chunk-
|
|
250
|
+
} from "./chunk-VHSQU3RO.js";
|
|
251
251
|
import {
|
|
252
252
|
compareVerifiedSemanticRuleResults,
|
|
253
253
|
searchVerifiedSemanticRules
|
|
@@ -299,7 +299,7 @@ import {
|
|
|
299
299
|
} from "./chunk-ECF627KC.js";
|
|
300
300
|
import {
|
|
301
301
|
SmartBuffer
|
|
302
|
-
} from "./chunk-
|
|
302
|
+
} from "./chunk-3H25IRJJ.js";
|
|
303
303
|
import {
|
|
304
304
|
semanticChunkContent
|
|
305
305
|
} from "./chunk-2GBFKHBZ.js";
|
|
@@ -373,7 +373,7 @@ import {
|
|
|
373
373
|
} from "./chunk-QW27DOHG.js";
|
|
374
374
|
import {
|
|
375
375
|
MeetingsInputError
|
|
376
|
-
} from "./chunk-
|
|
376
|
+
} from "./chunk-3NJJQAL3.js";
|
|
377
377
|
import {
|
|
378
378
|
WearablesInputError,
|
|
379
379
|
describeErrorForOperator
|
|
@@ -400,7 +400,7 @@ import {
|
|
|
400
400
|
detectPassiveCorrections,
|
|
401
401
|
renderSessionExperiencePreview,
|
|
402
402
|
scoreSessionExperienceForPrompt
|
|
403
|
-
} from "./chunk-
|
|
403
|
+
} from "./chunk-RAU25Q6J.js";
|
|
404
404
|
import {
|
|
405
405
|
importCapsule
|
|
406
406
|
} from "./chunk-QDZWQBLU.js";
|
|
@@ -480,7 +480,7 @@ import {
|
|
|
480
480
|
ExtractionDeadlineError,
|
|
481
481
|
ExtractionRunCoordinator,
|
|
482
482
|
classifyExtractionOrigin
|
|
483
|
-
} from "./chunk-
|
|
483
|
+
} from "./chunk-QNZPYH36.js";
|
|
484
484
|
import {
|
|
485
485
|
ARTIFACT_STATUS_CACHE_TTL_MS,
|
|
486
486
|
BulkImportBatchPartialFailureError,
|
|
@@ -6454,6 +6454,7 @@ var ExtractionPersistCoordinator = class {
|
|
|
6454
6454
|
});
|
|
6455
6455
|
const supersessionOrderingAt = (validAt) => validAt && validAt.length > 0 ? validAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
6456
6456
|
const pendingReviewPersistedIds = [];
|
|
6457
|
+
let deferredCount = 0;
|
|
6457
6458
|
const persistedIdsByStorage = /* @__PURE__ */ new Map();
|
|
6458
6459
|
const trackPersistedId = (targetStorage, id, options = {}) => {
|
|
6459
6460
|
if (options.includeReturnedIds !== false) {
|
|
@@ -6960,7 +6961,7 @@ var ExtractionPersistCoordinator = class {
|
|
|
6960
6961
|
"persistExtraction: result or result.facts is invalid, skipping",
|
|
6961
6962
|
{ resultType: typeof result, factsType: typeof result?.facts }
|
|
6962
6963
|
);
|
|
6963
|
-
return { persistedIds, memoryPathById };
|
|
6964
|
+
return { persistedIds, memoryPathById, deferredCount, pendingReviewIds: pendingReviewPersistedIds };
|
|
6964
6965
|
}
|
|
6965
6966
|
const chunkingConfig = {
|
|
6966
6967
|
targetTokens: this.deps.config.chunkingTargetTokens,
|
|
@@ -7067,7 +7068,6 @@ var ExtractionPersistCoordinator = class {
|
|
|
7067
7068
|
}
|
|
7068
7069
|
let judgeVerdictsByFactIndex = null;
|
|
7069
7070
|
let judgeGatedCount = 0;
|
|
7070
|
-
this.deps.setLastPersistExtractionDeferredCount(0);
|
|
7071
7071
|
if (lifecycleCaps.extractionJudge) {
|
|
7072
7072
|
let preJudgeRedactionRules = [];
|
|
7073
7073
|
try {
|
|
@@ -7195,7 +7195,7 @@ var ExtractionPersistCoordinator = class {
|
|
|
7195
7195
|
log.info(
|
|
7196
7196
|
`extraction-judge: ${judgeResult.verdicts.size}/${judgeCandidates.length} facts evaluated, ${judgeResult.cached} cached, ${judgeResult.judged} judged, ${judgeResult.deferred} deferred` + (judgeResult.deferredCappedToReject > 0 ? ` (${judgeResult.deferredCappedToReject} cap-rejected)` : "") + `, ${judgeResult.elapsed}ms`
|
|
7197
7197
|
);
|
|
7198
|
-
|
|
7198
|
+
deferredCount = judgeResult.deferred;
|
|
7199
7199
|
} catch (err) {
|
|
7200
7200
|
log.warn(
|
|
7201
7201
|
`extraction-judge: pipeline error, proceeding without filtering (fail-open): ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -8226,8 +8226,7 @@ ${q.context ?? ""}`], entityScreenOn, entityScreenProfile);
|
|
|
8226
8226
|
log.info(
|
|
8227
8227
|
`persisted: ${facts.length - dedupedCount - importanceGatedCount - judgeGatedCount - redactionGatedCount - semanticMergedCount} facts${dedupSuffix}${gatedSuffix}${judgeSuffix}${redactionSuffix}, ${entities.length} entities, ${questions.length} questions, ${profileUpdates.length} profile updates`
|
|
8228
8228
|
);
|
|
8229
|
-
|
|
8230
|
-
return { persistedIds, memoryPathById };
|
|
8229
|
+
return { persistedIds, memoryPathById, deferredCount, pendingReviewIds: pendingReviewPersistedIds };
|
|
8231
8230
|
}
|
|
8232
8231
|
};
|
|
8233
8232
|
|
|
@@ -21874,19 +21873,6 @@ var Orchestrator = class {
|
|
|
21874
21873
|
* (rule 11) — these hang off the orchestrator instance.
|
|
21875
21874
|
*/
|
|
21876
21875
|
faithfulnessCounters = createFaithfulnessCounters();
|
|
21877
|
-
/**
|
|
21878
|
-
* Side-channel: number of facts deferred in the most recent
|
|
21879
|
-
* `persistExtraction` call (issue #562, PR 2). The caller reads this after
|
|
21880
|
-
* `persistExtraction` returns to decide whether to retain buffer turns for
|
|
21881
|
-
* the next extraction pass. Not part of the return signature because many
|
|
21882
|
-
* callers already destructure `persistedIds` by position.
|
|
21883
|
-
*/
|
|
21884
|
-
lastPersistExtractionDeferredCount = 0;
|
|
21885
|
-
/**
|
|
21886
|
-
* Side-channel (#1635): pending_review persisted ids from the last
|
|
21887
|
-
* persistExtraction; runExtraction excludes them from the thread episode set.
|
|
21888
|
-
*/
|
|
21889
|
-
lastPersistExtractionPendingReviewIds = [];
|
|
21890
21876
|
_fastGatewayLlm;
|
|
21891
21877
|
get fastGatewayLlm() {
|
|
21892
21878
|
return this._fastGatewayLlm;
|
|
@@ -22014,11 +22000,10 @@ var Orchestrator = class {
|
|
|
22014
22000
|
getCodingContextForSession: (sessionKey) => this.getCodingContextForSession(sessionKey),
|
|
22015
22001
|
applyCodingNamespaceOverlay: (sessionKey, namespace) => this.applyCodingNamespaceOverlay(sessionKey, namespace),
|
|
22016
22002
|
boxBuilderFor: (storage) => this.boxBuilderFor(storage),
|
|
22017
|
-
appendPersistedThreadEpisodes: (threadId, ids) => this.appendPersistedThreadEpisodes(threadId, ids),
|
|
22003
|
+
appendPersistedThreadEpisodes: (threadId, ids, pendingReviewIds) => this.appendPersistedThreadEpisodes(threadId, ids, pendingReviewIds),
|
|
22018
22004
|
maybeScheduleConsolidation: (nonZero) => this.maybeScheduleConsolidation(nonZero),
|
|
22019
22005
|
requestQmdMaintenance: () => this.requestQmdMaintenance(),
|
|
22020
22006
|
runTierMigrationCycle: (storage, trigger, options) => this.runTierMigrationCycle(storage, trigger, options),
|
|
22021
|
-
getLastPersistExtractionDeferredCount: () => this.lastPersistExtractionDeferredCount,
|
|
22022
22007
|
recordProcessedExtractionFingerprint: (storage, fingerprint, preloadedMeta) => this.recordProcessedExtractionFingerprint(storage, fingerprint, preloadedMeta)
|
|
22023
22008
|
});
|
|
22024
22009
|
}
|
|
@@ -22067,12 +22052,6 @@ var Orchestrator = class {
|
|
|
22067
22052
|
getJudgeDeferCounts: () => this.judgeDeferCounts,
|
|
22068
22053
|
getFaithfulnessCounters: () => this.faithfulnessCounters,
|
|
22069
22054
|
getEmbeddingFallback: () => this.embeddingFallback,
|
|
22070
|
-
setLastPersistExtractionDeferredCount: (v) => {
|
|
22071
|
-
this.lastPersistExtractionDeferredCount = v;
|
|
22072
|
-
},
|
|
22073
|
-
setLastPersistExtractionPendingReviewIds: (ids) => {
|
|
22074
|
-
this.lastPersistExtractionPendingReviewIds = ids;
|
|
22075
|
-
},
|
|
22076
22055
|
addContentHashDedup: (targetStorage, content) => this.addContentHashDedup(targetStorage, content),
|
|
22077
22056
|
hasContentHashDedup: (targetStorage, content) => this.hasContentHashDedup(targetStorage, content),
|
|
22078
22057
|
backfillTemporalBoundsOnDedupHit: (targetStorage, dedupContent, bounds, entityRef, sourceConnector) => this.backfillTemporalBoundsOnDedupHit(targetStorage, dedupContent, bounds, entityRef, sourceConnector),
|
|
@@ -23712,7 +23691,7 @@ ${doc.content}` : doc.content,
|
|
|
23712
23691
|
this.maintenanceScheduler.requestQmdMaintenanceForTool(reason);
|
|
23713
23692
|
}
|
|
23714
23693
|
async persistExtraction(result, storage, threadIdForExtraction, sourceContext, baseNamespace, scopeProfileWritePlan, sourceText, graphCaps = resolveGraphConstructionCapabilities(this.config), lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config)) {
|
|
23715
|
-
|
|
23694
|
+
return await this.extractionPersistCoordinator.persistExtraction(
|
|
23716
23695
|
result,
|
|
23717
23696
|
storage,
|
|
23718
23697
|
threadIdForExtraction,
|
|
@@ -23723,15 +23702,13 @@ ${doc.content}` : doc.content,
|
|
|
23723
23702
|
graphCaps,
|
|
23724
23703
|
lifecycleCaps
|
|
23725
23704
|
);
|
|
23726
|
-
return { persistedIds, memoryPathById };
|
|
23727
23705
|
}
|
|
23728
23706
|
/**
|
|
23729
23707
|
* Append persisted ids to the thread episode set, excluding pending_review
|
|
23730
23708
|
* fact ids (#1635) so they don't re-seed predecessor edges. Fail-open like
|
|
23731
23709
|
* the raw appendEpisodeIds call it replaces.
|
|
23732
23710
|
*/
|
|
23733
|
-
async appendPersistedThreadEpisodes(threadId, persistedIds) {
|
|
23734
|
-
const pendingReviewIds = this.lastPersistExtractionPendingReviewIds ?? [];
|
|
23711
|
+
async appendPersistedThreadEpisodes(threadId, persistedIds, pendingReviewIds) {
|
|
23735
23712
|
const episodeIds = pendingReviewIds.length > 0 ? persistedIds.filter((id) => !pendingReviewIds.includes(id)) : persistedIds;
|
|
23736
23713
|
if (episodeIds.length === 0) return;
|
|
23737
23714
|
try {
|
|
@@ -31644,4 +31621,4 @@ export {
|
|
|
31644
31621
|
qmdStartupCollectionChecksWithTimeout,
|
|
31645
31622
|
Orchestrator
|
|
31646
31623
|
};
|
|
31647
|
-
//# sourceMappingURL=chunk-
|
|
31624
|
+
//# sourceMappingURL=chunk-XMBDI725.js.map
|