@unblocklabs/unblock-memory 0.3.27 → 0.3.29
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/agent-transcript.d.ts +8 -0
- package/dist/src/agent-transcript.js +29 -0
- package/dist/src/cluster-review.js +1 -1
- package/dist/src/config.d.ts +4 -0
- package/dist/src/config.js +14 -1
- package/dist/src/date-option.d.ts +1 -0
- package/dist/src/date-option.js +8 -0
- package/dist/src/diagnostics.d.ts +13 -4
- package/dist/src/diagnostics.js +5 -0
- package/dist/src/manager.d.ts +3 -1
- package/dist/src/manager.js +61 -57
- package/dist/src/memory-whisperer.d.ts +2 -1
- package/dist/src/memory-whisperer.js +241 -114
- package/dist/src/mlx-query.d.ts +22 -0
- package/dist/src/mlx-query.js +155 -0
- package/dist/src/people-dossier-review.js +1 -1
- package/dist/src/people-hooks.d.ts +1 -1
- package/dist/src/people-hooks.js +13 -7
- package/dist/src/people-primer-tool.js +1 -1
- package/dist/src/people-primer.js +3 -2
- package/dist/src/plugin.js +10 -5
- package/dist/src/quality-audit.js +86 -81
- package/dist/src/response-audit.js +1 -1
- package/dist/src/response-episodes.js +4 -11
- package/dist/src/response-judge.d.ts +1 -1
- package/dist/src/response-judge.js +18 -15
- package/dist/src/response-runtime.js +1 -8
- package/dist/src/response-stages.d.ts +8 -8
- package/dist/src/response-text.d.ts +5 -0
- package/dist/src/response-text.js +23 -1
- package/dist/src/retrieval-telemetry.d.ts +2 -2
- package/dist/src/retrieval-telemetry.js +1 -1
- package/dist/src/review-tools.js +1 -1
- package/dist/src/session-sync.js +8 -22
- package/dist/src/skill-whisperer.d.ts +1 -2
- package/dist/src/skill-whisperer.js +77 -72
- package/dist/src/training-candidates.d.ts +9 -1
- package/dist/src/training-candidates.js +12 -7
- package/dist/src/training-gate.js +3 -3
- package/dist/src/training-input.js +6 -34
- package/dist/src/training-judge.js +3 -3
- package/dist/src/training-queries.js +5 -4
- package/dist/src/training-runtime.js +1 -8
- package/dist/src/training.js +5 -4
- package/dist/src/typesafe-client.d.ts +15 -0
- package/dist/src/typesafe-client.js +75 -0
- package/dist/src/typesafe-review.d.ts +2 -6
- package/dist/src/typesafe-review.js +49 -55
- package/dist/src/typesafe.d.ts +3 -7
- package/dist/src/typesafe.js +108 -163
- package/dist/src/whisperer-context.d.ts +1 -0
- package/dist/src/whisperer-context.js +9 -0
- package/dist/src/whisperer-prompt.d.ts +3 -0
- package/dist/src/whisperer-prompt.js +21 -0
- package/docs/configuration.md +12 -2
- package/docs/peoplesql.md +3 -1
- package/docs/response-audit.md +4 -1
- package/docs/retrieval.md +128 -20
- package/openclaw.plugin.json +13 -4
- package/package.json +2 -1
- package/src/mlx-query-worker.py +104 -0
- package/dist/src/typesafe-transport.d.ts +0 -10
- package/dist/src/typesafe-transport.js +0 -26
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
/** The OpenClaw agent database versions whose transcript layout we read. */
|
|
3
|
+
export declare function agentTranscriptSchemaVersion(db: DatabaseSync, errorMessage?: string): number;
|
|
4
|
+
export declare function assertAgentTranscriptIdentity(db: DatabaseSync, agentId: string, version: number, errorMessage?: string): void;
|
|
5
|
+
export declare function assertAgentTranscriptSchema(db: DatabaseSync, agentId: string, errorMessage?: string): void;
|
|
6
|
+
export declare const ACTIVE_EVENTS_FROM = "FROM session_transcript_active_events a\n JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq";
|
|
7
|
+
export declare const ACTIVE_EVENT_COUNT_SQL = "SELECT COUNT(*) n,COALESCE(SUM(length(e.event_json)),0) bytes\n FROM session_transcript_active_events a\n JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq WHERE a.session_id=?";
|
|
8
|
+
export declare const ACTIVE_EVENT_ROWS_SQL = "SELECT e.seq,e.event_json eventJson,e.created_at createdAt\n FROM session_transcript_active_events a\n JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq WHERE a.session_id=? ORDER BY a.active_position";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The OpenClaw agent database versions whose transcript layout we read. */
|
|
2
|
+
export function agentTranscriptSchemaVersion(db, errorMessage) {
|
|
3
|
+
const version = db.prepare("PRAGMA user_version").get()?.user_version;
|
|
4
|
+
if (typeof version !== "number" || ![17, 18, 19].includes(version)) {
|
|
5
|
+
throw new Error(errorMessage ?? "unsupported OpenClaw agent database schema: expected one of 17, 18, 19, " +
|
|
6
|
+
`found ${String(version ?? "unknown")}`);
|
|
7
|
+
}
|
|
8
|
+
return version;
|
|
9
|
+
}
|
|
10
|
+
export function assertAgentTranscriptIdentity(db, agentId, version, errorMessage) {
|
|
11
|
+
const fail = (message) => { throw new Error(errorMessage ?? message); };
|
|
12
|
+
const meta = db.prepare("SELECT role, schema_version AS schemaVersion, agent_id AS agentId " +
|
|
13
|
+
"FROM schema_meta WHERE meta_key = 'primary' LIMIT 1").get();
|
|
14
|
+
if (meta?.role !== "agent" || meta.schemaVersion !== version) {
|
|
15
|
+
fail("unsupported OpenClaw agent database primary schema metadata");
|
|
16
|
+
}
|
|
17
|
+
if (meta?.agentId !== agentId) {
|
|
18
|
+
fail(`OpenClaw agent database belongs to ${String(meta?.agentId)}, not ${agentId}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function assertAgentTranscriptSchema(db, agentId, errorMessage) {
|
|
22
|
+
assertAgentTranscriptIdentity(db, agentId, agentTranscriptSchemaVersion(db, errorMessage), errorMessage);
|
|
23
|
+
}
|
|
24
|
+
export const ACTIVE_EVENTS_FROM = `FROM session_transcript_active_events a
|
|
25
|
+
JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq`;
|
|
26
|
+
export const ACTIVE_EVENT_COUNT_SQL = `SELECT COUNT(*) n,COALESCE(SUM(length(e.event_json)),0) bytes
|
|
27
|
+
${ACTIVE_EVENTS_FROM} WHERE a.session_id=?`;
|
|
28
|
+
export const ACTIVE_EVENT_ROWS_SQL = `SELECT e.seq,e.event_json eventJson,e.created_at createdAt
|
|
29
|
+
${ACTIVE_EVENTS_FROM} WHERE a.session_id=? ORDER BY a.active_position`;
|
|
@@ -59,6 +59,6 @@ export async function reviewClusterIngestion(params) {
|
|
|
59
59
|
});
|
|
60
60
|
return { status: "ok", runId: snapshot.runId, clusterId: params.clusterId, members, recurring,
|
|
61
61
|
sampled: sample.length, considered: snapshot.considered, clusterSize: snapshot.clusterSize,
|
|
62
|
-
policy: "jev-1.13.0:cluster-defects-
|
|
62
|
+
policy: "jev-1.13.0:cluster-defects-v2-isolated", scope: "Center/edge sample of approved complete chunks only. Recurring labels are hypotheses, not proof of a shared cause or permission to change any member. Unreviewed members remain unknown." };
|
|
63
63
|
});
|
|
64
64
|
}
|
package/dist/src/config.d.ts
CHANGED
|
@@ -69,6 +69,10 @@ export type UnblockMemoryConfig = {
|
|
|
69
69
|
maxHints: number;
|
|
70
70
|
cooldownTurns: number;
|
|
71
71
|
timeoutMs: number;
|
|
72
|
+
mlx?: {
|
|
73
|
+
pythonPath: string;
|
|
74
|
+
modelPath: string;
|
|
75
|
+
};
|
|
72
76
|
};
|
|
73
77
|
};
|
|
74
78
|
export declare const DEFAULT_PEOPLE_CONFIG: UnblockMemoryConfig["people"];
|
package/dist/src/config.js
CHANGED
|
@@ -91,7 +91,19 @@ function resolveMemoryWhisperer(value, corpora) {
|
|
|
91
91
|
throw new Error("unblock-memory memoryWhisperer must be an object");
|
|
92
92
|
}
|
|
93
93
|
const config = value;
|
|
94
|
-
assertOnlyKeys(config, Object.keys(DEFAULT_MEMORY_WHISPERER), "memoryWhisperer");
|
|
94
|
+
assertOnlyKeys(config, [...Object.keys(DEFAULT_MEMORY_WHISPERER), "mlx"], "memoryWhisperer");
|
|
95
|
+
let mlx;
|
|
96
|
+
if (config.mlx !== undefined) {
|
|
97
|
+
if (!config.mlx || typeof config.mlx !== "object" || Array.isArray(config.mlx))
|
|
98
|
+
throw new Error("memoryWhisperer.mlx must be an object");
|
|
99
|
+
const value = config.mlx;
|
|
100
|
+
assertOnlyKeys(value, ["pythonPath", "modelPath"], "memoryWhisperer.mlx");
|
|
101
|
+
if (typeof value.pythonPath !== "string" || !isAbsolute(value.pythonPath) ||
|
|
102
|
+
typeof value.modelPath !== "string" || !isAbsolute(value.modelPath)) {
|
|
103
|
+
throw new Error("memoryWhisperer.mlx requires absolute pythonPath and modelPath");
|
|
104
|
+
}
|
|
105
|
+
mlx = { pythonPath: value.pythonPath, modelPath: value.modelPath };
|
|
106
|
+
}
|
|
95
107
|
const enabled = config.enabled ?? false;
|
|
96
108
|
if (typeof enabled !== "boolean")
|
|
97
109
|
throw new Error("unblock-memory memoryWhisperer.enabled must be a boolean");
|
|
@@ -120,6 +132,7 @@ function resolveMemoryWhisperer(value, corpora) {
|
|
|
120
132
|
enabled, complementaryHints, corpora: [...new Set(selected)], historyMessages, cooldownTurns, minUsefulness,
|
|
121
133
|
maxHints: positiveInteger(config.maxHints, 2, "memoryWhisperer.maxHints", 2),
|
|
122
134
|
timeoutMs: positiveInteger(config.timeoutMs, 3000, "memoryWhisperer.timeoutMs", 10_000),
|
|
135
|
+
...(mlx ? { mlx } : {}),
|
|
123
136
|
};
|
|
124
137
|
}
|
|
125
138
|
function assertOnlyKeys(value, allowed, label) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function dateOption(value: string | undefined, fallback: number): number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function dateOption(value, fallback) {
|
|
2
|
+
if (value === undefined)
|
|
3
|
+
return fallback;
|
|
4
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value) {
|
|
5
|
+
throw new Error("Dates must be valid YYYY-MM-DD UTC dates");
|
|
6
|
+
}
|
|
7
|
+
return Date.parse(value);
|
|
8
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { RetrievalTelemetry } from "./retrieval-telemetry.js";
|
|
2
2
|
type Whisperer = "skill" | "memory";
|
|
3
|
-
type Outcome = "missing_key" | "typesafe_disabled" | "no_candidates" | "rejected" | "cooldown" | "emitted" | "failed" | "timed_out" | "cancelled" | "unavailable" | "payload_limit" | "redundancy_unavailable";
|
|
3
|
+
type Outcome = "missing_key" | "typesafe_disabled" | "no_candidates" | "rejected" | "cooldown" | "emitted" | "failed" | "timed_out" | "cancelled" | "unavailable" | "payload_limit" | "redundancy_unavailable" | "recall_not_needed" | "queries_generated" | "query_fallback" | "judge_candidate_failed";
|
|
4
4
|
/** Process-local, content-free and bounded. Agent IDs are keys, never included in snapshots. */
|
|
5
5
|
export declare class WhispererDiagnostics {
|
|
6
6
|
#private;
|
|
7
|
+
static shared(): WhispererDiagnostics;
|
|
7
8
|
record(agentId: string, whisperer: Whisperer, outcome: Outcome): void;
|
|
8
9
|
measureMemory(agentId: string, observation: Parameters<RetrievalTelemetry["record"]>[1]): void;
|
|
9
10
|
snapshot(agentId: string): {
|
|
10
11
|
skill: {
|
|
12
|
+
cancelled?: number | undefined;
|
|
11
13
|
unavailable?: number | undefined;
|
|
12
14
|
rejected?: number | undefined;
|
|
13
15
|
failed?: number | undefined;
|
|
14
|
-
cancelled?: number | undefined;
|
|
15
16
|
timed_out?: number | undefined;
|
|
16
17
|
missing_key?: number | undefined;
|
|
17
18
|
typesafe_disabled?: number | undefined;
|
|
@@ -20,12 +21,16 @@ export declare class WhispererDiagnostics {
|
|
|
20
21
|
emitted?: number | undefined;
|
|
21
22
|
payload_limit?: number | undefined;
|
|
22
23
|
redundancy_unavailable?: number | undefined;
|
|
24
|
+
recall_not_needed?: number | undefined;
|
|
25
|
+
queries_generated?: number | undefined;
|
|
26
|
+
query_fallback?: number | undefined;
|
|
27
|
+
judge_candidate_failed?: number | undefined;
|
|
23
28
|
};
|
|
24
29
|
memory: {
|
|
30
|
+
cancelled?: number | undefined;
|
|
25
31
|
unavailable?: number | undefined;
|
|
26
32
|
rejected?: number | undefined;
|
|
27
33
|
failed?: number | undefined;
|
|
28
|
-
cancelled?: number | undefined;
|
|
29
34
|
timed_out?: number | undefined;
|
|
30
35
|
missing_key?: number | undefined;
|
|
31
36
|
typesafe_disabled?: number | undefined;
|
|
@@ -34,6 +39,10 @@ export declare class WhispererDiagnostics {
|
|
|
34
39
|
emitted?: number | undefined;
|
|
35
40
|
payload_limit?: number | undefined;
|
|
36
41
|
redundancy_unavailable?: number | undefined;
|
|
42
|
+
recall_not_needed?: number | undefined;
|
|
43
|
+
queries_generated?: number | undefined;
|
|
44
|
+
query_fallback?: number | undefined;
|
|
45
|
+
judge_candidate_failed?: number | undefined;
|
|
37
46
|
};
|
|
38
47
|
telemetry: {
|
|
39
48
|
scope: string;
|
|
@@ -41,11 +50,11 @@ export declare class WhispererDiagnostics {
|
|
|
41
50
|
[k: string]: {
|
|
42
51
|
calls: number;
|
|
43
52
|
outcomes: {
|
|
53
|
+
cancelled?: number | undefined;
|
|
44
54
|
ok?: number | undefined;
|
|
45
55
|
skipped?: number | undefined;
|
|
46
56
|
failed?: number | undefined;
|
|
47
57
|
empty?: number | undefined;
|
|
48
|
-
cancelled?: number | undefined;
|
|
49
58
|
timed_out?: number | undefined;
|
|
50
59
|
};
|
|
51
60
|
measurements: {
|
package/dist/src/diagnostics.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { RetrievalTelemetry } from "./retrieval-telemetry.js";
|
|
2
2
|
/** Process-local, content-free and bounded. Agent IDs are keys, never included in snapshots. */
|
|
3
3
|
export class WhispererDiagnostics {
|
|
4
|
+
static shared() {
|
|
5
|
+
const scope = globalThis;
|
|
6
|
+
const symbol = Symbol.for("unblock-memory.whisperer-diagnostics.v1");
|
|
7
|
+
return (scope[symbol] ??= new WhispererDiagnostics());
|
|
8
|
+
}
|
|
4
9
|
#agents = new Map();
|
|
5
10
|
#entry(agentId) {
|
|
6
11
|
let entry = this.#agents.get(agentId);
|
package/dist/src/manager.d.ts
CHANGED
|
@@ -57,11 +57,11 @@ export declare class QmdMemoryManager implements MemorySearchManagerContract {
|
|
|
57
57
|
[k: string]: {
|
|
58
58
|
calls: number;
|
|
59
59
|
outcomes: {
|
|
60
|
+
cancelled?: number | undefined;
|
|
60
61
|
ok?: number | undefined;
|
|
61
62
|
skipped?: number | undefined;
|
|
62
63
|
failed?: number | undefined;
|
|
63
64
|
empty?: number | undefined;
|
|
64
|
-
cancelled?: number | undefined;
|
|
65
65
|
timed_out?: number | undefined;
|
|
66
66
|
};
|
|
67
67
|
measurements: {
|
|
@@ -220,6 +220,8 @@ export declare class QmdMemoryManager implements MemorySearchManagerContract {
|
|
|
220
220
|
};
|
|
221
221
|
}): MaintenanceTask | undefined;
|
|
222
222
|
search(query: string, opts?: CorpusSearchOptions): Promise<CorpusMemorySearchResult[]>;
|
|
223
|
+
/** Whisperer-only discovery: exact trained recipe, no query-conditioned reranker or merged cap. */
|
|
224
|
+
searchWhisperer(queries: readonly string[], opts: Pick<CorpusSearchOptions, "corpora" | "signal" | "maxSnippetChars">): Promise<CorpusMemorySearchResult[]>;
|
|
223
225
|
searchSkills(query: string, minScore: number, limit: number): Promise<SkillSearchCandidate[]>;
|
|
224
226
|
readFile(params: {
|
|
225
227
|
relPath: string;
|
package/dist/src/manager.js
CHANGED
|
@@ -16,6 +16,7 @@ import { reviewIndexedClaim } from "./evidence-review.js";
|
|
|
16
16
|
import { reviewClusterIngestion } from "./cluster-review.js";
|
|
17
17
|
import { abortable } from "./abortable.js";
|
|
18
18
|
import { RetrievalTelemetry } from "./retrieval-telemetry.js";
|
|
19
|
+
import { trainingCandidates } from "./training-candidates.js";
|
|
19
20
|
const DEFAULT_READ_LINES = 120;
|
|
20
21
|
const MAX_READ_CHARS = 12_000;
|
|
21
22
|
const WATCH_DEBOUNCE_MS = 250;
|
|
@@ -486,50 +487,43 @@ export class QmdMemoryManager {
|
|
|
486
487
|
.filter((source) => selected.has(source.corpus))
|
|
487
488
|
.map((source) => source.collection);
|
|
488
489
|
}
|
|
490
|
+
async #updateAndEmbed(store, collection, force) {
|
|
491
|
+
const update = await store.update({ collections: collection ? [collection] : [] });
|
|
492
|
+
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
493
|
+
const analysisStore = store;
|
|
494
|
+
const changed = update.indexed + update.updated + update.removed > 0 || update.needsEmbedding > 0;
|
|
495
|
+
if ((changed || force) && analysisStore.internal)
|
|
496
|
+
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
497
|
+
const embed = await store.embed({
|
|
498
|
+
...(collection ? { collection } : {}),
|
|
499
|
+
force,
|
|
500
|
+
chunkStrategy: "semantic",
|
|
501
|
+
});
|
|
502
|
+
this.#recordEmbedding(embed);
|
|
503
|
+
const chunksEmbedded = completedEmbeddingCount(embed);
|
|
504
|
+
if (!changed && !force && chunksEmbedded > 0 && analysisStore.internal) {
|
|
505
|
+
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
506
|
+
}
|
|
507
|
+
return chunksEmbedded;
|
|
508
|
+
}
|
|
509
|
+
async #refreshIndexStatus(store) {
|
|
510
|
+
const status = await store.getStatus();
|
|
511
|
+
const collections = await store.listCollections();
|
|
512
|
+
this.#files = collections.reduce((total, collection) => total + collection.active_count, 0);
|
|
513
|
+
this.#dirty = status.needsEmbedding > 0;
|
|
514
|
+
}
|
|
489
515
|
sync(params) {
|
|
490
516
|
const run = async () => {
|
|
491
517
|
const store = await this.#getStore();
|
|
492
518
|
this.#dirty = true;
|
|
493
|
-
const analysisStore = store;
|
|
494
519
|
const collections = this.#qmdSources().filter((source) => source.kind !== "sessions");
|
|
495
|
-
let analysisMarkedStale = false;
|
|
496
|
-
const markAnalysisStale = () => {
|
|
497
|
-
if (analysisMarkedStale || !analysisStore.internal)
|
|
498
|
-
return;
|
|
499
|
-
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
500
|
-
analysisMarkedStale = true;
|
|
501
|
-
};
|
|
502
520
|
if (collections.length === 0) {
|
|
503
|
-
|
|
504
|
-
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
505
|
-
if (update.indexed + update.updated + update.removed > 0 ||
|
|
506
|
-
update.needsEmbedding > 0 || params?.force === true) {
|
|
507
|
-
markAnalysisStale();
|
|
508
|
-
}
|
|
509
|
-
const embed = await store.embed({ force: params?.force, chunkStrategy: "semantic" });
|
|
510
|
-
this.#recordEmbedding(embed);
|
|
511
|
-
if (completedEmbeddingCount(embed) > 0)
|
|
512
|
-
markAnalysisStale();
|
|
521
|
+
await this.#updateAndEmbed(store, undefined, params?.force);
|
|
513
522
|
}
|
|
514
523
|
for (const source of collections) {
|
|
515
|
-
|
|
516
|
-
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
517
|
-
const changed = update.indexed + update.updated + update.removed > 0 || update.needsEmbedding > 0;
|
|
518
|
-
if (changed || params?.force === true)
|
|
519
|
-
markAnalysisStale();
|
|
520
|
-
const embed = await store.embed({
|
|
521
|
-
collection: source.collection,
|
|
522
|
-
force: params?.force,
|
|
523
|
-
chunkStrategy: "semantic",
|
|
524
|
-
});
|
|
525
|
-
this.#recordEmbedding(embed);
|
|
526
|
-
if (completedEmbeddingCount(embed) > 0)
|
|
527
|
-
markAnalysisStale();
|
|
524
|
+
await this.#updateAndEmbed(store, source.collection, params?.force);
|
|
528
525
|
}
|
|
529
|
-
|
|
530
|
-
const indexedCollections = await store.listCollections();
|
|
531
|
-
this.#files = indexedCollections.reduce((total, collection) => total + collection.active_count, 0);
|
|
532
|
-
this.#dirty = status.needsEmbedding > 0;
|
|
526
|
+
await this.#refreshIndexStatus(store);
|
|
533
527
|
};
|
|
534
528
|
return this.#enqueue(run);
|
|
535
529
|
}
|
|
@@ -547,24 +541,7 @@ export class QmdMemoryManager {
|
|
|
547
541
|
index: async () => {
|
|
548
542
|
onPhase?.("indexing");
|
|
549
543
|
const store = await this.#getStore();
|
|
550
|
-
|
|
551
|
-
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
552
|
-
const analysisStore = store;
|
|
553
|
-
const invalidatesAnalysis = update.indexed + update.updated + update.removed > 0 ||
|
|
554
|
-
update.needsEmbedding > 0;
|
|
555
|
-
if (invalidatesAnalysis && analysisStore.internal) {
|
|
556
|
-
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
557
|
-
}
|
|
558
|
-
const embed = await store.embed({
|
|
559
|
-
collection: sessions.collection,
|
|
560
|
-
chunkStrategy: "semantic",
|
|
561
|
-
});
|
|
562
|
-
const chunksEmbedded = completedEmbeddingCount(embed);
|
|
563
|
-
this.#recordEmbedding(embed);
|
|
564
|
-
if (!invalidatesAnalysis && chunksEmbedded > 0 && analysisStore.internal) {
|
|
565
|
-
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
566
|
-
}
|
|
567
|
-
return chunksEmbedded;
|
|
544
|
+
return this.#updateAndEmbed(store, sessions.collection);
|
|
568
545
|
},
|
|
569
546
|
});
|
|
570
547
|
this.#sessionMetadata = sessionMetadataByPath(synced.manifest);
|
|
@@ -572,10 +549,7 @@ export class QmdMemoryManager {
|
|
|
572
549
|
if (synced.result.skipReason)
|
|
573
550
|
return synced.result;
|
|
574
551
|
const store = await this.#getStore();
|
|
575
|
-
|
|
576
|
-
const collections = await store.listCollections();
|
|
577
|
-
this.#files = collections.reduce((total, collection) => total + collection.active_count, 0);
|
|
578
|
-
this.#dirty = status.needsEmbedding > 0;
|
|
552
|
+
await this.#refreshIndexStatus(store);
|
|
579
553
|
return synced.result;
|
|
580
554
|
});
|
|
581
555
|
}
|
|
@@ -857,9 +831,39 @@ export class QmdMemoryManager {
|
|
|
857
831
|
expand: false,
|
|
858
832
|
});
|
|
859
833
|
opts?.signal?.throwIfAborted();
|
|
834
|
+
return this.#renderSearchHits(hits, store, opts);
|
|
835
|
+
}
|
|
836
|
+
/** Whisperer-only discovery: exact trained recipe, no query-conditioned reranker or merged cap. */
|
|
837
|
+
async searchWhisperer(queries, opts) {
|
|
838
|
+
return this.#enqueue(async () => {
|
|
839
|
+
opts.signal?.throwIfAborted();
|
|
840
|
+
const collections = this.#collectionNames(opts.corpora);
|
|
841
|
+
if (!collections.length)
|
|
842
|
+
return [];
|
|
843
|
+
if (this.#sessions && collections.includes(this.#sessions.collection))
|
|
844
|
+
await this.#refreshSessionMetadata();
|
|
845
|
+
const store = await this.#getAnalysisStore();
|
|
846
|
+
const hits = new Map();
|
|
847
|
+
// Serialize native QMD work. Cancellation prevents further queries/collections.
|
|
848
|
+
for (const query of queries)
|
|
849
|
+
for (const collection of collections) {
|
|
850
|
+
opts.signal?.throwIfAborted();
|
|
851
|
+
for (const hit of await trainingCandidates(store, query, collection, query, opts.signal)) {
|
|
852
|
+
const key = JSON.stringify([hit.file, hit.bestChunkPos, hit.bestChunk]);
|
|
853
|
+
if (!hits.has(key))
|
|
854
|
+
hits.set(key, { file: hit.file, body: hit.body, bestChunk: hit.bestChunk,
|
|
855
|
+
chunkPos: hit.bestChunkPos, chunkLen: hit.bestChunk.length, displayPath: hit.file, score: hit.score });
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
opts.signal?.throwIfAborted();
|
|
859
|
+
return this.#renderSearchHits([...hits.values()], store, opts);
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
async #renderSearchHits(hits, store, opts) {
|
|
860
863
|
const tokenizer = store.internal?.llm;
|
|
861
864
|
const results = [];
|
|
862
865
|
for (const hit of hits) {
|
|
866
|
+
opts?.signal?.throwIfAborted();
|
|
863
867
|
// Proactive hints must retain the entire matched chunk, even when expanded
|
|
864
868
|
// turn/message context exceeds their budget. Ordinary search is unchanged.
|
|
865
869
|
if (hit.bestChunk.length > (opts?.maxSnippetChars ?? Infinity))
|
|
@@ -9,8 +9,9 @@ type MemoryWhispererRuntime = {
|
|
|
9
9
|
}): Promise<{
|
|
10
10
|
manager: {
|
|
11
11
|
search(query: string, opts?: CorpusSearchOptions): Promise<CorpusMemorySearchResult[]>;
|
|
12
|
+
searchWhisperer?(queries: readonly string[], opts: Pick<CorpusSearchOptions, "corpora" | "signal" | "maxSnippetChars">): Promise<CorpusMemorySearchResult[]>;
|
|
12
13
|
} | null;
|
|
13
14
|
}>;
|
|
14
15
|
};
|
|
15
|
-
export declare function registerMemoryWhisperer(api: OpenClawPluginApi, runtime: MemoryWhispererRuntime, config: UnblockMemoryConfig["memoryWhisperer"], typesafe: UnblockMemoryConfig["typesafe"], diagnostics?: WhispererDiagnostics):
|
|
16
|
+
export declare function registerMemoryWhisperer(api: OpenClawPluginApi, runtime: MemoryWhispererRuntime, config: UnblockMemoryConfig["memoryWhisperer"], typesafe: UnblockMemoryConfig["typesafe"], diagnostics?: WhispererDiagnostics): Parameters<typeof api.on<"before_prompt_build">>[1] | undefined;
|
|
16
17
|
export {};
|