@mercury-fw/core 0.25.0 → 0.25.1
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @mercury-fw/core
|
|
2
2
|
|
|
3
|
+
## 0.25.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5613e5e: - Mercury starts even when Qdrant isn't answering yet: the memory collections are set up in the background and retried until Qdrant is reachable, instead of crashing the process at startup.
|
|
8
|
+
- A new session's context primer goes on without the last-session recap when Qdrant doesn't answer, instead of failing the turn.
|
|
9
|
+
- The scaffolded app's `docker-compose.yml` restarts the app service unless it was stopped.
|
|
10
|
+
- @mercury-fw/plugin-types@0.25.1
|
|
11
|
+
- @mercury-fw/channel-types@0.25.1
|
|
12
|
+
- @mercury-fw/cli-engine@0.25.1
|
|
13
|
+
- @mercury-fw/confirm-engine@0.25.1
|
|
14
|
+
|
|
3
15
|
## 0.25.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3's startup setup (creating the Qdrant collections and their indexes),
|
|
3
|
+
* run in the background so an unreachable Qdrant never stops Mercury from
|
|
4
|
+
* starting: the episodic store is enrichment and fails soft (principle 3).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Runs `setup` now and, while it fails, again every `retryMs`, on a timer that
|
|
8
|
+
* doesn't keep the process alive. Logs once when Qdrant goes missing and once
|
|
9
|
+
* when it's back, never per retry. `done` resolves after the first success.
|
|
10
|
+
*/
|
|
11
|
+
export declare function setUpWhenReachable(setup: () => Promise<void>, { log, retryMs }: {
|
|
12
|
+
log: (msg: string) => void;
|
|
13
|
+
retryMs?: number;
|
|
14
|
+
}): {
|
|
15
|
+
done: Promise<void>;
|
|
16
|
+
};
|
|
@@ -7,6 +7,8 @@ export type ContextPrimerDeps = {
|
|
|
7
7
|
listWikiFilesInRootsFn: typeof listWikiFilesInRoots;
|
|
8
8
|
readWikiFileInRootsFn: typeof readWikiFileInRoots;
|
|
9
9
|
readIndexFileFn: typeof readIndexFile;
|
|
10
|
+
/** Where a skipped section says why (the recap, when Qdrant doesn't answer). */
|
|
11
|
+
log: (msg: string) => void;
|
|
10
12
|
};
|
|
11
13
|
/**
|
|
12
14
|
* Text of the primer for `userId`, built from injected deps only — never
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/core",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"test": "bun test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@mercury-fw/channel-types": "0.25.
|
|
35
|
-
"@mercury-fw/cli-engine": "0.25.
|
|
36
|
-
"@mercury-fw/confirm-engine": "0.25.
|
|
37
|
-
"@mercury-fw/plugin-types": "0.25.
|
|
34
|
+
"@mercury-fw/channel-types": "0.25.1",
|
|
35
|
+
"@mercury-fw/cli-engine": "0.25.1",
|
|
36
|
+
"@mercury-fw/confirm-engine": "0.25.1",
|
|
37
|
+
"@mercury-fw/plugin-types": "0.25.1",
|
|
38
38
|
"@qdrant/js-client-rest": "^1.19.0",
|
|
39
39
|
"ai": "^7.0.77",
|
|
40
40
|
"ai-sdk-ollama": "^4.2.0",
|
package/src/compose.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { ensureVerbatimCollection, listVerbatimBySession, listVerbatimSessions }
|
|
|
56
56
|
import { createVerbatimArchiveProvider } from "./memory/memory-provider.ts";
|
|
57
57
|
import { ensureSemanticFactsCollection, storeSemanticFact, searchSemanticFactsByTopic } from "./memory/semantic-facts-store.ts";
|
|
58
58
|
import { ensureToolCorrectionsCollection, storeToolCorrection, searchToolCorrectionsByTopic } from "./memory/tool-corrections-store.ts";
|
|
59
|
+
import { setUpWhenReachable } from "./memory/collection-setup.ts";
|
|
59
60
|
import { consolidateSemanticFact, consolidateToolCorrection, type ToolCorrectionConsolidationDeps } from "./cron/semantic-consolidation.ts";
|
|
60
61
|
import { createToolCorrectionExtractor } from "./session/tool-correction-extractor.ts";
|
|
61
62
|
import { createEmbedder } from "./memory/embedder.ts";
|
|
@@ -227,14 +228,12 @@ export async function composeMercury(config: MercuryConfig): Promise<ComposedApp
|
|
|
227
228
|
const qdrant = new QdrantClient({ url: process.env.QDRANT_URL ?? "http://qdrant:6333" });
|
|
228
229
|
const episodicCollection = process.env.QDRANT_EPISODIC_COLLECTION ?? "episodic_memory";
|
|
229
230
|
const episodicVectorSize = Number(process.env.QDRANT_EPISODIC_VECTOR_SIZE ?? "768");
|
|
230
|
-
await ensureEpisodicCollection(qdrant, episodicCollection, episodicVectorSize);
|
|
231
231
|
|
|
232
232
|
// Verbatim conversation archive (#4): a distinct collection holding the raw
|
|
233
233
|
// user<->model exchange, lossless and durable — separate from the lossy
|
|
234
234
|
// Layer-1 window and the derived episodic summaries above.
|
|
235
235
|
const verbatimCollection = process.env.QDRANT_VERBATIM_COLLECTION ?? "verbatim_archive";
|
|
236
236
|
const verbatimVectorSize = Number(process.env.QDRANT_VERBATIM_VECTOR_SIZE ?? "768");
|
|
237
|
-
await ensureVerbatimCollection(qdrant, verbatimCollection, verbatimVectorSize);
|
|
238
237
|
const verbatimProvider = createVerbatimArchiveProvider({ client: qdrant, collectionName: verbatimCollection, embed });
|
|
239
238
|
|
|
240
239
|
// Semantic consolidation (D-22/D-34): a separate Qdrant collection from
|
|
@@ -242,7 +241,6 @@ export async function composeMercury(config: MercuryConfig): Promise<ComposedApp
|
|
|
242
241
|
// vector embedded on the topic alone (see semantic-facts-store.ts for why).
|
|
243
242
|
const semanticFactsCollection = process.env.QDRANT_SEMANTIC_FACTS_COLLECTION ?? "semantic_facts";
|
|
244
243
|
const semanticFactsVectorSize = Number(process.env.QDRANT_SEMANTIC_FACTS_VECTOR_SIZE ?? "768");
|
|
245
|
-
await ensureSemanticFactsCollection(qdrant, semanticFactsCollection, semanticFactsVectorSize);
|
|
246
244
|
const extractFacts = createSemanticFactExtractor(model);
|
|
247
245
|
|
|
248
246
|
// Idempotent self-heal: the vault lives on a named Docker volume, empty on
|
|
@@ -322,7 +320,17 @@ export async function composeMercury(config: MercuryConfig): Promise<ComposedApp
|
|
|
322
320
|
// the tool-call trace only exists in memory for the duration of its turn.
|
|
323
321
|
const toolCorrectionsCollection = process.env.QDRANT_TOOL_CORRECTIONS_COLLECTION ?? "tool_corrections";
|
|
324
322
|
const toolCorrectionsVectorSize = Number(process.env.QDRANT_TOOL_CORRECTIONS_VECTOR_SIZE ?? "768");
|
|
325
|
-
|
|
323
|
+
// Every Layer-3 collection, set up in the background: Mercury starts even
|
|
324
|
+
// while Qdrant isn't answering yet, and memory switches on once it does.
|
|
325
|
+
setUpWhenReachable(
|
|
326
|
+
async () => {
|
|
327
|
+
await ensureEpisodicCollection(qdrant, episodicCollection, episodicVectorSize);
|
|
328
|
+
await ensureVerbatimCollection(qdrant, verbatimCollection, verbatimVectorSize);
|
|
329
|
+
await ensureSemanticFactsCollection(qdrant, semanticFactsCollection, semanticFactsVectorSize);
|
|
330
|
+
await ensureToolCorrectionsCollection(qdrant, toolCorrectionsCollection, toolCorrectionsVectorSize);
|
|
331
|
+
},
|
|
332
|
+
{ log: (msg) => console.error(`[memory] ${msg}`) },
|
|
333
|
+
);
|
|
326
334
|
const extractToolCorrections = createToolCorrectionExtractor(model, undefined, {
|
|
327
335
|
log: (msg) => console.error(`[cron] ${msg}`),
|
|
328
336
|
});
|
|
@@ -464,6 +472,7 @@ export async function composeMercury(config: MercuryConfig): Promise<ComposedApp
|
|
|
464
472
|
listWikiFilesInRootsFn: listWikiFilesInRoots,
|
|
465
473
|
readWikiFileInRootsFn: readWikiFileInRoots,
|
|
466
474
|
readIndexFileFn: readIndexFile,
|
|
475
|
+
log: (msg) => console.error(`[memory] ${msg}`),
|
|
467
476
|
});
|
|
468
477
|
return getOrCreateHistory(key, trackForCapture, primer);
|
|
469
478
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3's startup setup (creating the Qdrant collections and their indexes),
|
|
3
|
+
* run in the background so an unreachable Qdrant never stops Mercury from
|
|
4
|
+
* starting: the episodic store is enrichment and fails soft (principle 3).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Runs `setup` now and, while it fails, again every `retryMs`, on a timer that
|
|
9
|
+
* doesn't keep the process alive. Logs once when Qdrant goes missing and once
|
|
10
|
+
* when it's back, never per retry. `done` resolves after the first success.
|
|
11
|
+
*/
|
|
12
|
+
export function setUpWhenReachable(
|
|
13
|
+
setup: () => Promise<void>,
|
|
14
|
+
{ log, retryMs = 5000 }: { log: (msg: string) => void; retryMs?: number },
|
|
15
|
+
): { done: Promise<void> } {
|
|
16
|
+
let failing = false;
|
|
17
|
+
const done = new Promise<void>((resolve) => {
|
|
18
|
+
const attempt = async () => {
|
|
19
|
+
try {
|
|
20
|
+
await setup();
|
|
21
|
+
if (failing) log("Qdrant reachable, memory collections ready");
|
|
22
|
+
resolve();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (!failing) {
|
|
25
|
+
log(`Qdrant unreachable, Layer-3 memory is off until it answers (retrying every ${retryMs / 1000}s): ${String(err)}`);
|
|
26
|
+
}
|
|
27
|
+
failing = true;
|
|
28
|
+
setTimeout(attempt, retryMs).unref();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
void attempt();
|
|
32
|
+
});
|
|
33
|
+
return { done };
|
|
34
|
+
}
|
|
@@ -21,6 +21,8 @@ export type ContextPrimerDeps = {
|
|
|
21
21
|
listWikiFilesInRootsFn: typeof listWikiFilesInRoots;
|
|
22
22
|
readWikiFileInRootsFn: typeof readWikiFileInRoots;
|
|
23
23
|
readIndexFileFn: typeof readIndexFile;
|
|
24
|
+
/** Where a skipped section says why (the recap, when Qdrant doesn't answer). */
|
|
25
|
+
log: (msg: string) => void;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n/;
|
|
@@ -78,7 +80,12 @@ async function pendingConfirmationTokens(userId: string, deps: ContextPrimerDeps
|
|
|
78
80
|
* `index.ts` supplies the real Qdrant-backed episodic query and wiki reads.
|
|
79
81
|
*/
|
|
80
82
|
export async function buildContextPrimer(userId: string, deps: ContextPrimerDeps): Promise<string> {
|
|
81
|
-
|
|
83
|
+
// Episodic memory is enrichment: without Qdrant the primer goes on without
|
|
84
|
+
// the recap instead of failing the turn.
|
|
85
|
+
const entries = await deps.getLastSessionEntries(userId).catch((err: unknown) => {
|
|
86
|
+
deps.log(`last session for ${userId} unavailable, primer built without it: ${String(err)}`);
|
|
87
|
+
return [];
|
|
88
|
+
});
|
|
82
89
|
// Checked regardless of `entries` — a pending confirmation isn't tied to
|
|
83
90
|
// "was there a prior episodic session", it's simply still open right now.
|
|
84
91
|
const pendingTokens = await pendingConfirmationTokens(userId, deps);
|