@joshuaswarren/openclaw-engram 9.0.75 → 9.0.77

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/index.js CHANGED
@@ -6697,6 +6697,16 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
6697
6697
  supportsExplainTraces() {
6698
6698
  return versionAtLeast(parseQmdVersion(this.cliVersion), [1, 1, 2]);
6699
6699
  }
6700
+ /**
6701
+ * QMD v2 (>= 2.0.0) uses a new MCP tool API:
6702
+ * - `search` and `vsearch` tools removed; only `query` tool exists
6703
+ * - `query` accepts `{ searches: [{ type, query }], collections?: string[] }`
6704
+ * instead of `{ query: string, collection?: string }`
6705
+ * - `collection` (singular) → `collections` (plural array)
6706
+ */
6707
+ isQmdV2() {
6708
+ return versionAtLeast(parseQmdVersion(this.cliVersion), [2, 0, 0]);
6709
+ }
6700
6710
  resolveSearchOptions(options) {
6701
6711
  const normalized = normalizeSearchOptions(options);
6702
6712
  if (!normalized) return void 0;
@@ -6842,29 +6852,43 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
6842
6852
  async searchViaDaemon(query, collection, maxResults, options, signal) {
6843
6853
  if (!this.daemonSession || !this.daemonAvailable) return null;
6844
6854
  const startedAtMs = Date.now();
6855
+ const v2 = this.isQmdV2();
6845
6856
  try {
6846
- const args = {
6847
- query,
6848
- limit: maxResults
6849
- };
6850
- if (collection) {
6851
- args.collection = collection;
6852
- }
6853
- if (options?.intent) {
6854
- args.intent = options.intent;
6855
- }
6856
- if (options?.explain === true) {
6857
- args.explain = true;
6857
+ let args;
6858
+ if (v2) {
6859
+ const searches = [{ type: "lex", query }];
6860
+ searches.push({ type: "vec", query });
6861
+ args = { searches, limit: maxResults };
6862
+ if (collection) {
6863
+ args.collections = [collection];
6864
+ }
6865
+ if (options?.intent) {
6866
+ args.intent = options.intent;
6867
+ }
6868
+ if (options?.explain === true) {
6869
+ args.explain = true;
6870
+ }
6871
+ } else {
6872
+ args = { query, limit: maxResults };
6873
+ if (collection) {
6874
+ args.collection = collection;
6875
+ }
6876
+ if (options?.intent) {
6877
+ args.intent = options.intent;
6878
+ }
6879
+ if (options?.explain === true) {
6880
+ args.explain = true;
6881
+ }
6858
6882
  }
6859
6883
  const result = await this.daemonSession.callTool("query", args, QMD_DAEMON_TIMEOUT_MS, signal);
6860
6884
  const durationMs = Date.now() - startedAtMs;
6861
6885
  if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
6862
6886
  log.warn(
6863
- `SLOW QMD daemon query: durationMs=${durationMs} collection=${collection ?? "global"} maxResults=${maxResults} queryChars=${query.length}`
6887
+ `SLOW QMD daemon query: durationMs=${durationMs} collection=${collection ?? "global"} maxResults=${maxResults} queryChars=${query.length} v2=${v2}`
6864
6888
  );
6865
6889
  }
6866
6890
  const results = parseMcpSearchResult(result, "daemon");
6867
- log.debug(`QMD daemon search: ${results.length} results in ${durationMs}ms`);
6891
+ log.debug(`QMD daemon search: ${results.length} results in ${durationMs}ms (v2=${v2})`);
6868
6892
  this.recordDaemonSuccess();
6869
6893
  return results;
6870
6894
  } catch (err) {
@@ -6884,16 +6908,31 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
6884
6908
  async bm25SearchViaDaemon(query, collection, maxResults, signal) {
6885
6909
  if (!this.daemonSession || !this.daemonAvailable) return null;
6886
6910
  const startedAtMs = Date.now();
6911
+ const v2 = this.isQmdV2();
6887
6912
  try {
6888
- const result = await this.daemonSession.callTool(
6889
- "search",
6890
- { query, limit: maxResults, collection },
6891
- QMD_DAEMON_TIMEOUT_MS,
6892
- signal
6893
- );
6913
+ let result;
6914
+ if (v2) {
6915
+ result = await this.daemonSession.callTool(
6916
+ "query",
6917
+ {
6918
+ searches: [{ type: "lex", query }],
6919
+ collections: [collection],
6920
+ limit: maxResults
6921
+ },
6922
+ QMD_DAEMON_TIMEOUT_MS,
6923
+ signal
6924
+ );
6925
+ } else {
6926
+ result = await this.daemonSession.callTool(
6927
+ "search",
6928
+ { query, limit: maxResults, collection },
6929
+ QMD_DAEMON_TIMEOUT_MS,
6930
+ signal
6931
+ );
6932
+ }
6894
6933
  const durationMs = Date.now() - startedAtMs;
6895
6934
  const results = parseMcpSearchResult(result);
6896
- log.debug(`QMD daemon bm25: ${results.length} results in ${durationMs}ms`);
6935
+ log.debug(`QMD daemon bm25: ${results.length} results in ${durationMs}ms (v2=${v2})`);
6897
6936
  this.recordDaemonSuccess();
6898
6937
  return results;
6899
6938
  } catch (err) {
@@ -6913,16 +6952,31 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
6913
6952
  async vsearchViaDaemon(query, collection, maxResults, signal) {
6914
6953
  if (!this.daemonSession || !this.daemonAvailable) return null;
6915
6954
  const startedAtMs = Date.now();
6955
+ const v2 = this.isQmdV2();
6916
6956
  try {
6917
- const result = await this.daemonSession.callTool(
6918
- "vsearch",
6919
- { query, limit: maxResults, collection },
6920
- QMD_DAEMON_TIMEOUT_MS,
6921
- signal
6922
- );
6957
+ let result;
6958
+ if (v2) {
6959
+ result = await this.daemonSession.callTool(
6960
+ "query",
6961
+ {
6962
+ searches: [{ type: "vec", query }],
6963
+ collections: [collection],
6964
+ limit: maxResults
6965
+ },
6966
+ QMD_DAEMON_TIMEOUT_MS,
6967
+ signal
6968
+ );
6969
+ } else {
6970
+ result = await this.daemonSession.callTool(
6971
+ "vsearch",
6972
+ { query, limit: maxResults, collection },
6973
+ QMD_DAEMON_TIMEOUT_MS,
6974
+ signal
6975
+ );
6976
+ }
6923
6977
  const durationMs = Date.now() - startedAtMs;
6924
6978
  const results = parseMcpSearchResult(result);
6925
- log.debug(`QMD daemon vsearch: ${results.length} results in ${durationMs}ms`);
6979
+ log.debug(`QMD daemon vsearch: ${results.length} results in ${durationMs}ms (v2=${v2})`);
6926
6980
  this.recordDaemonSuccess();
6927
6981
  return results;
6928
6982
  } catch (err) {
@@ -39896,7 +39950,7 @@ import path69 from "path";
39896
39950
  import os6 from "os";
39897
39951
 
39898
39952
  // src/opik-exporter.ts
39899
- import { createHash as createHash13, randomUUID as randomUUID3 } from "crypto";
39953
+ import { createHash as createHash13, randomBytes } from "crypto";
39900
39954
  import { readFileSync as readFileSync4 } from "fs";
39901
39955
  import os5 from "os";
39902
39956
  import path68 from "path";
@@ -39919,6 +39973,20 @@ function readOpikOpenclawConfig(log2) {
39919
39973
  return {};
39920
39974
  }
39921
39975
  }
39976
+ function uuidV7() {
39977
+ const now = Date.now();
39978
+ const bytes = randomBytes(16);
39979
+ bytes[0] = now / 2 ** 40 & 255;
39980
+ bytes[1] = now / 2 ** 32 & 255;
39981
+ bytes[2] = now / 2 ** 24 & 255;
39982
+ bytes[3] = now / 2 ** 16 & 255;
39983
+ bytes[4] = now / 2 ** 8 & 255;
39984
+ bytes[5] = now & 255;
39985
+ bytes[6] = bytes[6] & 15 | 112;
39986
+ bytes[8] = bytes[8] & 63 | 128;
39987
+ const hex = bytes.toString("hex");
39988
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
39989
+ }
39922
39990
  function buildHeaders(cfg) {
39923
39991
  const headers = { "Content-Type": "application/json" };
39924
39992
  if (cfg.workspaceName) headers["Comet-Workspace"] = cfg.workspaceName;
@@ -40049,8 +40117,8 @@ var OpikExporter = class _OpikExporter {
40049
40117
  }
40050
40118
  if (evt.timings) metadata.timings = evt.timings;
40051
40119
  const span = {
40052
- id: randomUUID3(),
40053
- trace_id: evt.sessionKey ? this.sessionToTraceId(evt.sessionKey) : randomUUID3(),
40120
+ id: uuidV7(),
40121
+ trace_id: evt.sessionKey ? this.sessionToTraceId(evt.sessionKey) : uuidV7(),
40054
40122
  project_name: this.cfg.projectName,
40055
40123
  name: "engram:recall",
40056
40124
  type: "general",
@@ -40095,8 +40163,8 @@ var OpikExporter = class _OpikExporter {
40095
40163
  if (evt.tokenUsage?.output != null) usage.completion_tokens = evt.tokenUsage.output;
40096
40164
  if (evt.tokenUsage?.total != null) usage.total_tokens = evt.tokenUsage.total;
40097
40165
  const span = {
40098
- id: state?.spanId ?? randomUUID3(),
40099
- trace_id: state?.traceId ?? randomUUID3(),
40166
+ id: state?.spanId ?? uuidV7(),
40167
+ trace_id: state?.traceId ?? uuidV7(),
40100
40168
  project_name: this.cfg.projectName,
40101
40169
  name: `engram:${evt.operation}`,
40102
40170
  type: "llm",
@@ -40119,13 +40187,15 @@ var OpikExporter = class _OpikExporter {
40119
40187
  // Helpers
40120
40188
  // -------------------------------------------------------------------------
40121
40189
  /**
40122
- * Convert a sessionKey to a stable UUID v5-like deterministic ID so that
40190
+ * Convert a sessionKey to a stable, deterministic UUID v7-shaped ID so that
40123
40191
  * spans for the same session share a trace_id and are threaded in Opik.
40124
- * Uses SHA-256 so sessions with similar keys produce distinct trace IDs.
40192
+ * Uses SHA-256 for collision resistance. The timestamp bytes (0-5) come from
40193
+ * the hash itself — they don't reflect real time, but Opik only validates
40194
+ * the version/variant bits, not timestamp ordering.
40125
40195
  */
40126
40196
  sessionToTraceId(sessionKey) {
40127
40197
  const digest = createHash13("sha256").update(sessionKey).digest();
40128
- digest[6] = digest[6] & 15 | 64;
40198
+ digest[6] = digest[6] & 15 | 112;
40129
40199
  digest[8] = digest[8] & 63 | 128;
40130
40200
  const hex = digest.slice(0, 16).toString("hex");
40131
40201
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;