@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.
Files changed (63) hide show
  1. package/dist/src/agent-transcript.d.ts +8 -0
  2. package/dist/src/agent-transcript.js +29 -0
  3. package/dist/src/cluster-review.js +1 -1
  4. package/dist/src/config.d.ts +4 -0
  5. package/dist/src/config.js +14 -1
  6. package/dist/src/date-option.d.ts +1 -0
  7. package/dist/src/date-option.js +8 -0
  8. package/dist/src/diagnostics.d.ts +13 -4
  9. package/dist/src/diagnostics.js +5 -0
  10. package/dist/src/manager.d.ts +3 -1
  11. package/dist/src/manager.js +61 -57
  12. package/dist/src/memory-whisperer.d.ts +2 -1
  13. package/dist/src/memory-whisperer.js +241 -114
  14. package/dist/src/mlx-query.d.ts +22 -0
  15. package/dist/src/mlx-query.js +155 -0
  16. package/dist/src/people-dossier-review.js +1 -1
  17. package/dist/src/people-hooks.d.ts +1 -1
  18. package/dist/src/people-hooks.js +13 -7
  19. package/dist/src/people-primer-tool.js +1 -1
  20. package/dist/src/people-primer.js +3 -2
  21. package/dist/src/plugin.js +10 -5
  22. package/dist/src/quality-audit.js +86 -81
  23. package/dist/src/response-audit.js +1 -1
  24. package/dist/src/response-episodes.js +4 -11
  25. package/dist/src/response-judge.d.ts +1 -1
  26. package/dist/src/response-judge.js +18 -15
  27. package/dist/src/response-runtime.js +1 -8
  28. package/dist/src/response-stages.d.ts +8 -8
  29. package/dist/src/response-text.d.ts +5 -0
  30. package/dist/src/response-text.js +23 -1
  31. package/dist/src/retrieval-telemetry.d.ts +2 -2
  32. package/dist/src/retrieval-telemetry.js +1 -1
  33. package/dist/src/review-tools.js +1 -1
  34. package/dist/src/session-sync.js +8 -22
  35. package/dist/src/skill-whisperer.d.ts +1 -2
  36. package/dist/src/skill-whisperer.js +77 -72
  37. package/dist/src/training-candidates.d.ts +9 -1
  38. package/dist/src/training-candidates.js +12 -7
  39. package/dist/src/training-gate.js +3 -3
  40. package/dist/src/training-input.js +6 -34
  41. package/dist/src/training-judge.js +3 -3
  42. package/dist/src/training-queries.js +5 -4
  43. package/dist/src/training-runtime.js +1 -8
  44. package/dist/src/training.js +5 -4
  45. package/dist/src/typesafe-client.d.ts +15 -0
  46. package/dist/src/typesafe-client.js +75 -0
  47. package/dist/src/typesafe-review.d.ts +2 -6
  48. package/dist/src/typesafe-review.js +49 -55
  49. package/dist/src/typesafe.d.ts +3 -7
  50. package/dist/src/typesafe.js +108 -163
  51. package/dist/src/whisperer-context.d.ts +1 -0
  52. package/dist/src/whisperer-context.js +9 -0
  53. package/dist/src/whisperer-prompt.d.ts +3 -0
  54. package/dist/src/whisperer-prompt.js +21 -0
  55. package/docs/configuration.md +12 -2
  56. package/docs/peoplesql.md +3 -1
  57. package/docs/response-audit.md +4 -1
  58. package/docs/retrieval.md +128 -20
  59. package/openclaw.plugin.json +13 -4
  60. package/package.json +2 -1
  61. package/src/mlx-query-worker.py +104 -0
  62. package/dist/src/typesafe-transport.d.ts +0 -10
  63. 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-v1", 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." };
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
  }
@@ -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"];
@@ -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: {
@@ -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);
@@ -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;
@@ -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
- const update = await store.update({ collections: [] });
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
- const update = await store.update({ collections: [source.collection] });
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
- const status = await store.getStatus();
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
- const update = await store.update({ collections: [sessions.collection] });
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
- const status = await store.getStatus();
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): void;
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 {};