@xdarkicex/openclaw-memory-libravdb 1.4.80 → 1.5.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/README.md CHANGED
@@ -187,7 +187,7 @@ All keys are optional. For the full reference, see [Configuration](./docs/config
187
187
 
188
188
  - New install: [Install](./docs/install.md), [Installation reference](./docs/installation.md)
189
189
  - Understand the design: [Problem](./docs/problem.md), [Architecture](./docs/architecture.md), [ADRs](./docs/architecture-decisions/README.md)
190
- - Configure: [Configuration](./docs/configuration.md), [Features](./docs/features.md), [Embedding profiles](./docs/embedding-profiles.md), [Models](./docs/models.md)
190
+ - Configure: [Configuration](./docs/configuration.md), [TLS configuration](./docs/TLS_configuration.md), [mTLS configuration](./docs/mTLS_configuration.md), [Features](./docs/features.md), [Embedding profiles](./docs/embedding-profiles.md), [Models](./docs/models.md)
191
191
  - Operate safely: [Security](./docs/security.md), [Uninstall](./docs/uninstall.md)
192
192
  - Advanced operations: [Performance and tuning](./docs/performance-and-tuning.md)
193
193
  - Work from source: [Development](./docs/development.md), [Contributing](./docs/contributing.md)
@@ -1,10 +1,29 @@
1
+ import * as grpc from "@grpc/grpc-js";
1
2
  export interface GrpcClientOptions {
2
3
  endpoint: string;
3
4
  secret?: string;
4
5
  timeoutMs?: number;
6
+ tlsCaPath?: string;
7
+ tlsMode?: "auto" | "tls" | "insecure";
8
+ tlsClientCertPath?: string;
9
+ tlsClientKeyPath?: string;
5
10
  }
6
11
  export declare function resolveGrpcTarget(endpoint: string): string;
7
- export declare function resolveGrpcCredentialMode(endpoint: string): "insecure" | "tls";
12
+ /**
13
+ * Selects gRPC credential mode based on endpoint address class.
14
+ *
15
+ * - Unix socket endpoints → plaintext (local-only transport)
16
+ * - Loopback addresses (localhost, 127.0.0.1, ::1) → plaintext
17
+ * - All other TCP and DNS targets → TLS
18
+ *
19
+ * resolveGrpcCredentials uses this classification to return the
20
+ * appropriate grpc.ChannelCredentials. Pass tlsCaPath to load a
21
+ * custom CA certificate PEM file for self-signed or private CA
22
+ * deployments. Omit tlsCaPath for publicly trusted certificates
23
+ * (Let's Encrypt, cert-manager) — the system CA pool is used.
24
+ */
25
+ export declare function resolveGrpcCredentialMode(endpoint: string, tlsMode?: "auto" | "tls" | "insecure"): "insecure" | "tls";
26
+ export declare function resolveGrpcCredentials(endpoint: string, tlsCaPath?: string, tlsMode?: "auto" | "tls" | "insecure", tlsClientCertPath?: string, tlsClientKeyPath?: string): grpc.ChannelCredentials;
8
27
  export declare class GrpcKernelClient {
9
28
  private client;
10
29
  private readonly secret;
@@ -1,6 +1,7 @@
1
1
  import { createHmac } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import fs from "node:fs";
4
5
  import * as grpc from "@grpc/grpc-js";
5
6
  import * as protoLoader from "@grpc/proto-loader";
6
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -10,18 +11,72 @@ const PROTO_PATH = path.resolve(__dirname, "./proto/intelligence_kernel/v1/kerne
10
11
  export function resolveGrpcTarget(endpoint) {
11
12
  return endpoint.startsWith("tcp:") ? endpoint.substring(4) : endpoint;
12
13
  }
13
- export function resolveGrpcCredentialMode(endpoint) {
14
+ /**
15
+ * Selects gRPC credential mode based on endpoint address class.
16
+ *
17
+ * - Unix socket endpoints → plaintext (local-only transport)
18
+ * - Loopback addresses (localhost, 127.0.0.1, ::1) → plaintext
19
+ * - All other TCP and DNS targets → TLS
20
+ *
21
+ * resolveGrpcCredentials uses this classification to return the
22
+ * appropriate grpc.ChannelCredentials. Pass tlsCaPath to load a
23
+ * custom CA certificate PEM file for self-signed or private CA
24
+ * deployments. Omit tlsCaPath for publicly trusted certificates
25
+ * (Let's Encrypt, cert-manager) — the system CA pool is used.
26
+ */
27
+ export function resolveGrpcCredentialMode(endpoint, tlsMode) {
28
+ if (tlsMode === "tls")
29
+ return "tls";
30
+ if (tlsMode === "insecure")
31
+ return "insecure";
32
+ // "auto" or undefined — address-based heuristic
14
33
  const target = resolveGrpcTarget(endpoint).trim();
15
- if (target.startsWith("unix:")) {
34
+ if (target.startsWith("unix:"))
16
35
  return "insecure";
17
- }
18
36
  const host = extractGrpcHost(target);
19
37
  return isLoopbackHost(host) ? "insecure" : "tls";
20
38
  }
21
- function resolveGrpcCredentials(endpoint) {
22
- return resolveGrpcCredentialMode(endpoint) === "insecure"
23
- ? grpc.credentials.createInsecure()
24
- : grpc.credentials.createSsl();
39
+ export function resolveGrpcCredentials(endpoint, tlsCaPath, tlsMode, tlsClientCertPath, tlsClientKeyPath) {
40
+ if (resolveGrpcCredentialMode(endpoint, tlsMode) === "insecure") {
41
+ return grpc.credentials.createInsecure();
42
+ }
43
+ // tlsMode is "tls" or "auto"/undefined resolved to "tls"
44
+ let rootCerts = null;
45
+ if (tlsCaPath) {
46
+ try {
47
+ rootCerts = fs.readFileSync(tlsCaPath);
48
+ }
49
+ catch (err) {
50
+ const msg = err instanceof Error ? err.message : String(err);
51
+ throw new Error(`LibraVDB: failed to load TLS CA certificate from "${tlsCaPath}": ${msg}`);
52
+ }
53
+ }
54
+ // Client certificate and key must both be present or both absent
55
+ const hasCert = tlsClientCertPath !== undefined;
56
+ const hasKey = tlsClientKeyPath !== undefined;
57
+ if (hasCert !== hasKey) {
58
+ throw new Error("LibraVDB: grpcEndpointTlsClientCert and grpcEndpointTlsClientKey " +
59
+ "must both be set or both be omitted");
60
+ }
61
+ let clientKey = null;
62
+ let clientCert = null;
63
+ if (tlsClientCertPath && tlsClientKeyPath) {
64
+ try {
65
+ clientCert = fs.readFileSync(tlsClientCertPath);
66
+ }
67
+ catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ throw new Error(`LibraVDB: failed to load TLS client certificate from "${tlsClientCertPath}": ${msg}`);
70
+ }
71
+ try {
72
+ clientKey = fs.readFileSync(tlsClientKeyPath);
73
+ }
74
+ catch (err) {
75
+ const msg = err instanceof Error ? err.message : String(err);
76
+ throw new Error(`LibraVDB: failed to load TLS client key from "${tlsClientKeyPath}": ${msg}`);
77
+ }
78
+ }
79
+ return grpc.credentials.createSsl(rootCerts, clientKey, clientCert);
25
80
  }
26
81
  function extractGrpcHost(target) {
27
82
  const withoutDnsPrefix = target.startsWith("dns:///") ? target.slice("dns:///".length) : target;
@@ -54,7 +109,7 @@ export class GrpcKernelClient {
54
109
  const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
55
110
  const kernelService = protoDescriptor.intelligence_kernel.v1.IntelligenceKernel;
56
111
  const target = resolveGrpcTarget(options.endpoint);
57
- this.client = new kernelService(target, resolveGrpcCredentials(options.endpoint));
112
+ this.client = new kernelService(target, resolveGrpcCredentials(options.endpoint, options.tlsCaPath, options.tlsMode, options.tlsClientCertPath, options.tlsClientKeyPath));
58
113
  }
59
114
  getMetadata(signed = true) {
60
115
  const md = new grpc.Metadata();
package/dist/index.js CHANGED
@@ -8810,14 +8810,14 @@ var require_tls_helpers = __commonJS({
8810
8810
  Object.defineProperty(exports2, "__esModule", { value: true });
8811
8811
  exports2.CIPHER_SUITES = void 0;
8812
8812
  exports2.getDefaultRootsData = getDefaultRootsData;
8813
- var fs4 = __require("fs");
8813
+ var fs5 = __require("fs");
8814
8814
  exports2.CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES;
8815
8815
  var DEFAULT_ROOTS_FILE_PATH = process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH;
8816
8816
  var defaultRootsData = null;
8817
8817
  function getDefaultRootsData() {
8818
8818
  if (DEFAULT_ROOTS_FILE_PATH) {
8819
8819
  if (defaultRootsData === null) {
8820
- defaultRootsData = fs4.readFileSync(DEFAULT_ROOTS_FILE_PATH);
8820
+ defaultRootsData = fs5.readFileSync(DEFAULT_ROOTS_FILE_PATH);
8821
8821
  }
8822
8822
  return defaultRootsData;
8823
8823
  }
@@ -14324,7 +14324,7 @@ var require_fetch = __commonJS({
14324
14324
  module2.exports = fetch;
14325
14325
  var asPromise = require_aspromise();
14326
14326
  var inquire2 = require_inquire();
14327
- var fs4 = inquire2("fs");
14327
+ var fs5 = inquire2("fs");
14328
14328
  function fetch(filename, options, callback) {
14329
14329
  if (typeof options === "function") {
14330
14330
  callback = options;
@@ -14333,8 +14333,8 @@ var require_fetch = __commonJS({
14333
14333
  options = {};
14334
14334
  if (!callback)
14335
14335
  return asPromise(fetch, this, filename, options);
14336
- if (!options.xhr && fs4 && fs4.readFile)
14337
- return fs4.readFile(filename, function fetchReadFileCallback(err, contents) {
14336
+ if (!options.xhr && fs5 && fs5.readFile)
14337
+ return fs5.readFile(filename, function fetchReadFileCallback(err, contents) {
14338
14338
  return err && typeof XMLHttpRequest !== "undefined" ? fetch.xhr(filename, options, callback) : err ? callback(err) : callback(null, options.binary ? contents : contents.toString("utf8"));
14339
14339
  });
14340
14340
  return fetch.xhr(filename, options, callback);
@@ -20608,7 +20608,7 @@ var require_util2 = __commonJS({
20608
20608
  "use strict";
20609
20609
  Object.defineProperty(exports2, "__esModule", { value: true });
20610
20610
  exports2.addCommonProtos = exports2.loadProtosWithOptionsSync = exports2.loadProtosWithOptions = void 0;
20611
- var fs4 = __require("fs");
20611
+ var fs5 = __require("fs");
20612
20612
  var path5 = __require("path");
20613
20613
  var Protobuf = require_protobufjs();
20614
20614
  function addIncludePathResolver(root, includePaths) {
@@ -20620,7 +20620,7 @@ var require_util2 = __commonJS({
20620
20620
  for (const directory of includePaths) {
20621
20621
  const fullPath = path5.join(directory, target);
20622
20622
  try {
20623
- fs4.accessSync(fullPath, fs4.constants.R_OK);
20623
+ fs5.accessSync(fullPath, fs5.constants.R_OK);
20624
20624
  return fullPath;
20625
20625
  } catch (err) {
20626
20626
  continue;
@@ -30442,7 +30442,7 @@ var require_certificate_provider = __commonJS({
30442
30442
  "use strict";
30443
30443
  Object.defineProperty(exports2, "__esModule", { value: true });
30444
30444
  exports2.FileWatcherCertificateProvider = void 0;
30445
- var fs4 = __require("fs");
30445
+ var fs5 = __require("fs");
30446
30446
  var logging = require_logging();
30447
30447
  var constants_1 = require_constants();
30448
30448
  var util_1 = __require("util");
@@ -30450,7 +30450,7 @@ var require_certificate_provider = __commonJS({
30450
30450
  function trace(text) {
30451
30451
  logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
30452
30452
  }
30453
- var readFilePromise = (0, util_1.promisify)(fs4.readFile);
30453
+ var readFilePromise = (0, util_1.promisify)(fs5.readFile);
30454
30454
  var FileWatcherCertificateProvider = class {
30455
30455
  constructor(config) {
30456
30456
  this.config = config;
@@ -39661,21 +39661,63 @@ var protoLoader = __toESM(require_src2(), 1);
39661
39661
  import { createHmac } from "node:crypto";
39662
39662
  import path3 from "node:path";
39663
39663
  import { fileURLToPath } from "node:url";
39664
+ import fs3 from "node:fs";
39664
39665
  var __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
39665
39666
  var PROTO_PATH = path3.resolve(__dirname2, "./proto/intelligence_kernel/v1/kernel.proto");
39666
39667
  function resolveGrpcTarget(endpoint) {
39667
39668
  return endpoint.startsWith("tcp:") ? endpoint.substring(4) : endpoint;
39668
39669
  }
39669
- function resolveGrpcCredentialMode(endpoint) {
39670
+ function resolveGrpcCredentialMode(endpoint, tlsMode) {
39671
+ if (tlsMode === "tls") return "tls";
39672
+ if (tlsMode === "insecure") return "insecure";
39670
39673
  const target = resolveGrpcTarget(endpoint).trim();
39671
- if (target.startsWith("unix:")) {
39672
- return "insecure";
39673
- }
39674
+ if (target.startsWith("unix:")) return "insecure";
39674
39675
  const host = extractGrpcHost(target);
39675
39676
  return isLoopbackHost(host) ? "insecure" : "tls";
39676
39677
  }
39677
- function resolveGrpcCredentials(endpoint) {
39678
- return resolveGrpcCredentialMode(endpoint) === "insecure" ? grpc.credentials.createInsecure() : grpc.credentials.createSsl();
39678
+ function resolveGrpcCredentials(endpoint, tlsCaPath, tlsMode, tlsClientCertPath, tlsClientKeyPath) {
39679
+ if (resolveGrpcCredentialMode(endpoint, tlsMode) === "insecure") {
39680
+ return grpc.credentials.createInsecure();
39681
+ }
39682
+ let rootCerts = null;
39683
+ if (tlsCaPath) {
39684
+ try {
39685
+ rootCerts = fs3.readFileSync(tlsCaPath);
39686
+ } catch (err) {
39687
+ const msg = err instanceof Error ? err.message : String(err);
39688
+ throw new Error(
39689
+ `LibraVDB: failed to load TLS CA certificate from "${tlsCaPath}": ${msg}`
39690
+ );
39691
+ }
39692
+ }
39693
+ const hasCert = tlsClientCertPath !== void 0;
39694
+ const hasKey = tlsClientKeyPath !== void 0;
39695
+ if (hasCert !== hasKey) {
39696
+ throw new Error(
39697
+ "LibraVDB: grpcEndpointTlsClientCert and grpcEndpointTlsClientKey must both be set or both be omitted"
39698
+ );
39699
+ }
39700
+ let clientKey = null;
39701
+ let clientCert = null;
39702
+ if (tlsClientCertPath && tlsClientKeyPath) {
39703
+ try {
39704
+ clientCert = fs3.readFileSync(tlsClientCertPath);
39705
+ } catch (err) {
39706
+ const msg = err instanceof Error ? err.message : String(err);
39707
+ throw new Error(
39708
+ `LibraVDB: failed to load TLS client certificate from "${tlsClientCertPath}": ${msg}`
39709
+ );
39710
+ }
39711
+ try {
39712
+ clientKey = fs3.readFileSync(tlsClientKeyPath);
39713
+ } catch (err) {
39714
+ const msg = err instanceof Error ? err.message : String(err);
39715
+ throw new Error(
39716
+ `LibraVDB: failed to load TLS client key from "${tlsClientKeyPath}": ${msg}`
39717
+ );
39718
+ }
39719
+ }
39720
+ return grpc.credentials.createSsl(rootCerts, clientKey, clientCert);
39679
39721
  }
39680
39722
  function extractGrpcHost(target) {
39681
39723
  const withoutDnsPrefix = target.startsWith("dns:///") ? target.slice("dns:///".length) : target;
@@ -39708,7 +39750,13 @@ var GrpcKernelClient = class {
39708
39750
  const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
39709
39751
  const kernelService = protoDescriptor.intelligence_kernel.v1.IntelligenceKernel;
39710
39752
  const target = resolveGrpcTarget(options.endpoint);
39711
- this.client = new kernelService(target, resolveGrpcCredentials(options.endpoint));
39753
+ this.client = new kernelService(target, resolveGrpcCredentials(
39754
+ options.endpoint,
39755
+ options.tlsCaPath,
39756
+ options.tlsMode,
39757
+ options.tlsClientCertPath,
39758
+ options.tlsClientKeyPath
39759
+ ));
39712
39760
  }
39713
39761
  getMetadata(signed = true) {
39714
39762
  const md = new grpc.Metadata();
@@ -39782,7 +39830,7 @@ var GrpcKernelClient = class {
39782
39830
  };
39783
39831
 
39784
39832
  // src/sidecar.ts
39785
- import fs3 from "node:fs";
39833
+ import fs4 from "node:fs";
39786
39834
  import net from "node:net";
39787
39835
  import os3 from "node:os";
39788
39836
  import path4 from "node:path";
@@ -40077,7 +40125,7 @@ function resolveConfiguredEndpoint(cfg) {
40077
40125
  function daemonProvisioningHint() {
40078
40126
  return "If you installed the npm package, install and start libravdbd separately; the package does not provision the daemon binary, ONNX Runtime, or model assets.";
40079
40127
  }
40080
- function defaultEndpoint(platform = process.platform, homeDir = os3.homedir(), pathExists = fs3.existsSync) {
40128
+ function defaultEndpoint(platform = process.platform, homeDir = os3.homedir(), pathExists = fs4.existsSync) {
40081
40129
  const envEndpoint = normalizeConfiguredEndpoint(process.env.LIBRAVDB_RPC_ENDPOINT);
40082
40130
  if (envEndpoint) {
40083
40131
  return envEndpoint;
@@ -40178,6 +40226,8 @@ function sleep2(delayMs) {
40178
40226
  import { readFileSync as readFileSync2 } from "node:fs";
40179
40227
  var DEFAULT_RPC_TIMEOUT_MS = 3e4;
40180
40228
  var STARTUP_HEALTH_TIMEOUT_MS = 2e3;
40229
+ var VALID_TLS_MODES = ["auto", "tls", "insecure"];
40230
+ var isTlsModeValid = (m) => VALID_TLS_MODES.includes(m);
40181
40231
  function resolveStartupHealthTimeoutMs(cfg) {
40182
40232
  return Math.max(STARTUP_HEALTH_TIMEOUT_MS, cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS);
40183
40233
  }
@@ -40192,6 +40242,32 @@ function createPluginRuntime(cfg, logger = console) {
40192
40242
  }
40193
40243
  if (!started) {
40194
40244
  started = (async () => {
40245
+ if (cfg.grpcEndpoint) {
40246
+ if (cfg.grpcEndpointTlsMode !== void 0 && !isTlsModeValid(cfg.grpcEndpointTlsMode)) {
40247
+ throw new Error(
40248
+ `LibraVDB: invalid grpcEndpointTlsMode "${cfg.grpcEndpointTlsMode}" \u2014 must be "auto", "tls", or "insecure"`
40249
+ );
40250
+ }
40251
+ const hasClientCert = cfg.grpcEndpointTlsClientCert !== void 0;
40252
+ const hasClientKey = cfg.grpcEndpointTlsClientKey !== void 0;
40253
+ if (hasClientCert !== hasClientKey) {
40254
+ throw new Error(
40255
+ "LibraVDB: grpcEndpointTlsClientCert and grpcEndpointTlsClientKey must both be set or both be omitted"
40256
+ );
40257
+ }
40258
+ if (cfg.grpcEndpointTlsMode === "insecure") {
40259
+ if (cfg.grpcEndpointTlsCa) {
40260
+ logger.warn?.(
40261
+ `LibraVDB: grpcEndpointTlsCa is set but grpcEndpointTlsMode is "insecure" \u2014 the CA file will not be used`
40262
+ );
40263
+ }
40264
+ if (cfg.grpcEndpointTlsClientCert) {
40265
+ logger.warn?.(
40266
+ `LibraVDB: grpcEndpointTlsClientCert is set but grpcEndpointTlsMode is "insecure" \u2014 client certificate will not be sent`
40267
+ );
40268
+ }
40269
+ }
40270
+ }
40195
40271
  const sidecar = await startSidecar(cfg, logger);
40196
40272
  const rpc = new RpcClient(sidecar.socket, {
40197
40273
  timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS
@@ -40208,12 +40284,16 @@ function createPluginRuntime(cfg, logger = console) {
40208
40284
  }
40209
40285
  let kernel = null;
40210
40286
  if (cfg.grpcEndpoint) {
40287
+ const secret = loadSecretFromEnv();
40211
40288
  try {
40212
- const secret = loadSecretFromEnv();
40213
40289
  kernel = new GrpcKernelClient({
40214
40290
  endpoint: cfg.grpcEndpoint,
40215
40291
  secret,
40216
- timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS
40292
+ timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS,
40293
+ tlsCaPath: cfg.grpcEndpointTlsCa,
40294
+ tlsMode: cfg.grpcEndpointTlsMode,
40295
+ tlsClientCertPath: cfg.grpcEndpointTlsClientCert,
40296
+ tlsClientKeyPath: cfg.grpcEndpointTlsClientKey
40217
40297
  });
40218
40298
  } catch (error) {
40219
40299
  logger.warn?.(`LibraVDB: failed to initialize gRPC kernel client: ${formatError(error)}`);
@@ -4,6 +4,8 @@ import type { LoggerLike, PluginConfig } from "./types.js";
4
4
  export type RpcGetter = () => Promise<RpcClient>;
5
5
  export declare const DEFAULT_RPC_TIMEOUT_MS = 30000;
6
6
  export declare const STARTUP_HEALTH_TIMEOUT_MS = 2000;
7
+ export declare const VALID_TLS_MODES: readonly ["auto", "tls", "insecure"];
8
+ export type ValidTlsMode = typeof VALID_TLS_MODES[number];
7
9
  export declare function resolveStartupHealthTimeoutMs(cfg: PluginConfig): number;
8
10
  export interface LifecycleHint {
9
11
  hook: "before_reset" | "session_end";
@@ -5,6 +5,8 @@ import { formatError } from "./format-error.js";
5
5
  import { readFileSync } from "node:fs";
6
6
  export const DEFAULT_RPC_TIMEOUT_MS = 30000;
7
7
  export const STARTUP_HEALTH_TIMEOUT_MS = 2000;
8
+ export const VALID_TLS_MODES = ["auto", "tls", "insecure"];
9
+ const isTlsModeValid = (m) => VALID_TLS_MODES.includes(m);
8
10
  export function resolveStartupHealthTimeoutMs(cfg) {
9
11
  return Math.max(STARTUP_HEALTH_TIMEOUT_MS, cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS);
10
12
  }
@@ -19,6 +21,30 @@ export function createPluginRuntime(cfg, logger = console) {
19
21
  }
20
22
  if (!started) {
21
23
  started = (async () => {
24
+ if (cfg.grpcEndpoint) {
25
+ if (cfg.grpcEndpointTlsMode !== undefined &&
26
+ !isTlsModeValid(cfg.grpcEndpointTlsMode)) {
27
+ throw new Error(`LibraVDB: invalid grpcEndpointTlsMode "${cfg.grpcEndpointTlsMode}" — ` +
28
+ `must be "auto", "tls", or "insecure"`);
29
+ }
30
+ const hasClientCert = cfg.grpcEndpointTlsClientCert !== undefined;
31
+ const hasClientKey = cfg.grpcEndpointTlsClientKey !== undefined;
32
+ if (hasClientCert !== hasClientKey) {
33
+ throw new Error("LibraVDB: grpcEndpointTlsClientCert and " +
34
+ "grpcEndpointTlsClientKey must both be set or both be omitted");
35
+ }
36
+ if (cfg.grpcEndpointTlsMode === "insecure") {
37
+ if (cfg.grpcEndpointTlsCa) {
38
+ logger.warn?.(`LibraVDB: grpcEndpointTlsCa is set but grpcEndpointTlsMode ` +
39
+ `is "insecure" — the CA file will not be used`);
40
+ }
41
+ if (cfg.grpcEndpointTlsClientCert) {
42
+ logger.warn?.(`LibraVDB: grpcEndpointTlsClientCert is set but ` +
43
+ `grpcEndpointTlsMode is "insecure" — client certificate ` +
44
+ `will not be sent`);
45
+ }
46
+ }
47
+ }
22
48
  const sidecar = await startSidecar(cfg, logger);
23
49
  const rpc = new RpcClient(sidecar.socket, {
24
50
  timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS,
@@ -37,15 +63,20 @@ export function createPluginRuntime(cfg, logger = console) {
37
63
  }
38
64
  let kernel = null;
39
65
  if (cfg.grpcEndpoint) {
66
+ const secret = loadSecretFromEnv();
40
67
  try {
41
- const secret = loadSecretFromEnv();
42
68
  kernel = new GrpcKernelClient({
43
69
  endpoint: cfg.grpcEndpoint,
44
70
  secret,
45
71
  timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS,
72
+ tlsCaPath: cfg.grpcEndpointTlsCa,
73
+ tlsMode: cfg.grpcEndpointTlsMode,
74
+ tlsClientCertPath: cfg.grpcEndpointTlsClientCert,
75
+ tlsClientKeyPath: cfg.grpcEndpointTlsClientKey,
46
76
  });
47
77
  }
48
78
  catch (error) {
79
+ // Only gRPC init errors land here — config validation already threw above
49
80
  logger.warn?.(`LibraVDB: failed to initialize gRPC kernel client: ${formatError(error)}`);
50
81
  }
51
82
  }
package/dist/types.d.ts CHANGED
@@ -90,6 +90,23 @@ export interface PluginConfig {
90
90
  maxRetries?: number;
91
91
  logLevel?: "debug" | "info" | "warn" | "error";
92
92
  grpcEndpoint?: string;
93
+ grpcEndpointTlsCa?: string;
94
+ /** Path to a client certificate PEM file for mTLS.
95
+ * The file must contain a PEM-encoded X.509 certificate. Leaf first;
96
+ * intermediates may follow in the same file. Required when the daemon
97
+ * requires a client certificate (mTLS). Must be paired with
98
+ * grpcEndpointTlsClientKey. */
99
+ grpcEndpointTlsClientCert?: string;
100
+ /** Path to the client private key PEM file.
101
+ * Must correspond to the leaf certificate in grpcEndpointTlsClientCert.
102
+ * Accepts RSA (PKCS#1/PKCS#8), ECDSA (P-256/P-384/P-521), Ed25519.
103
+ * Required when grpcEndpointTlsClientCert is set. */
104
+ grpcEndpointTlsClientKey?: string;
105
+ /** Controls gRPC credential mode.
106
+ * "auto" (default) — loopback and unix → plaintext, remote → TLS.
107
+ * "tls" — always use TLS regardless of address.
108
+ * "insecure" — always use plaintext (service mesh, tunnel). */
109
+ grpcEndpointTlsMode?: "auto" | "tls" | "insecure";
93
110
  }
94
111
  export interface SearchResult {
95
112
  id: string;
@@ -0,0 +1,112 @@
1
+ # TLS Configuration
2
+
3
+ The plugin selects the right credentials automatically based on which address it connects to. Unix sockets and loopback addresses (localhost, 127.0.0.1, ::1) always use plaintext. All other addresses use TLS. In most deployments no TLS configuration is needed at all. The only time manual configuration is required is when the daemon serves TLS on a loopback address, when the daemon uses a self-signed or private-CA certificate, or when infrastructure such as a service mesh handles TLS outside the plugin.
4
+
5
+ ## Default behavior
6
+
7
+ The plugin applies the following rules automatically:
8
+
9
+ | Endpoint type | Credential mode |
10
+ |---|---|
11
+ | Unix socket (`unix:/path/to/socket`) | Plaintext |
12
+ | Loopback (`tcp:127.0.0.1:port`, `tcp:localhost:port`, `[::1]:port`) | Plaintext |
13
+ | Any other TCP or DNS address | TLS |
14
+
15
+ Because these rules are automatic, most users do not set any TLS-related fields. Plaintext is used where it is safe (local transport) and TLS is used where it is needed (network transport).
16
+
17
+ ## Configuration fields
18
+
19
+ | Field | Type | Default | When to use |
20
+ |---|---|---|---|
21
+ | `grpcEndpoint` | string | — | The daemon address. Set to a unix socket path, a loopback address, or a remote host. |
22
+ | `grpcEndpointTlsCa` | string | — | Path to a CA certificate PEM file. Required only when the daemon certificate is self-signed or signed by a private CA not in the system certificate store. |
23
+ | `grpcEndpointTlsMode` | `"auto"` \| `"tls"` \| `"insecure"` | `"auto"` | Override the automatic selection. `"auto"` applies the default rules above. `"tls"` forces TLS regardless of address. `"insecure"` forces plaintext regardless of address. |
24
+
25
+ `grpcEndpointTlsMode` values explained:
26
+
27
+ - **`"auto"`** (default) — apply the automatic rules. Unix sockets and loopback use plaintext; all other addresses use TLS.
28
+ - **`"tls"`** — always use TLS, even for loopback addresses. Use this when the daemon has TLS enabled on a loopback address.
29
+ - **`"insecure"`** — always use plaintext, even for remote addresses. Use this only when a service mesh or TLS-terminating tunnel handles encryption externally.
30
+
31
+ ## Deployment scenarios
32
+
33
+ ### Local daemon (default)
34
+
35
+ The daemon runs on the same machine, listening on a unix socket or a loopback address.
36
+
37
+ ```json
38
+ {
39
+ "grpcEndpoint": "unix:/home/user/.libravdbd/run/libravdb.sock"
40
+ }
41
+ ```
42
+
43
+ or:
44
+
45
+ ```json
46
+ {
47
+ "grpcEndpoint": "tcp:127.0.0.1:9090"
48
+ }
49
+ ```
50
+
51
+ The plugin automatically uses plaintext. No TLS fields are needed.
52
+
53
+ ### Remote daemon with a trusted certificate
54
+
55
+ The daemon runs on a remote host and presents a certificate issued by a public CA such as Let's Encrypt or cert-manager.
56
+
57
+ ```json
58
+ {
59
+ "grpcEndpoint": "tcp:libravdb.k8s.internal:9090"
60
+ }
61
+ ```
62
+
63
+ TLS is automatic. The plugin uses the system certificate store to verify the daemon's certificate, so no additional configuration is needed.
64
+
65
+ ### Remote daemon with a self-signed or private CA certificate
66
+
67
+ The daemon runs on a remote host and uses a self-signed certificate or a certificate signed by a private/internal CA not in the system certificate store.
68
+
69
+ ```json
70
+ {
71
+ "grpcEndpoint": "tcp:libravdb.internal:9090",
72
+ "grpcEndpointTlsCa": "/etc/certs/company-ca.pem"
73
+ }
74
+ ```
75
+
76
+ The CA certificate must be the certificate of the CA that signed the daemon's server certificate — not the server certificate itself. The plugin uses this CA to verify the daemon's certificate during the TLS handshake. Without it, the plugin will reject the daemon's certificate as untrusted.
77
+
78
+ ### TLS on a loopback address
79
+
80
+ The daemon has TLS enabled on a loopback address. This is uncommon. The automatic rules would select plaintext for a loopback address, so an explicit override is required.
81
+
82
+ ```json
83
+ {
84
+ "grpcEndpoint": "tcp:127.0.0.1:9090",
85
+ "grpcEndpointTlsMode": "tls"
86
+ }
87
+ ```
88
+
89
+ Set `grpcEndpointTlsMode` to `"tls"` to force the plugin to use TLS even on the loopback address. The plugin will use the system certificate store for verification. If the daemon uses a self-signed certificate, add `grpcEndpointTlsCa` as well.
90
+
91
+ ## Service mesh and tunnels
92
+
93
+ When the daemon runs behind Istio, Envoy, or any other infrastructure that terminates TLS at the mesh or tunnel layer, the plugin should not attempt its own TLS. Set `grpcEndpointTlsMode` to `"insecure"` so the plugin uses plaintext and lets the mesh handle encryption:
94
+
95
+ ```json
96
+ {
97
+ "grpcEndpoint": "tcp:libravdb.mesh.svc:9090",
98
+ "grpcEndpointTlsMode": "insecure"
99
+ }
100
+ ```
101
+
102
+ This applies even for remote addresses. The mesh terminates TLS at the boundary, and the plugin communicates with the mesh over plaintext on the inside.
103
+
104
+ ## Error reference
105
+
106
+ | Error | Likely cause | Fix |
107
+ |---|---|---|
108
+ | `UNAVAILABLE / connection closed / TLS handshake failed` when connecting to a loopback address | The daemon has TLS enabled on a loopback address but the plugin is using plaintext (the default for loopback). | Add `"grpcEndpointTlsMode": "tls"` to the plugin config. |
109
+ | `x509: certificate signed by unknown authority` | The daemon uses a self-signed certificate or a certificate from a private CA not trusted by the system. | Set `grpcEndpointTlsCa` to the path of the CA certificate PEM file that signed the daemon's server certificate. |
110
+ | `failed to load TLS CA certificate from "...": ENOENT: no such file or directory` | The file path given in `grpcEndpointTlsCa` does not exist on the machine. | Verify the file path is correct and the CA certificate file exists. |
111
+ | `LibraVDB: invalid grpcEndpointTlsMode "..."` | The value set in `grpcEndpointTlsMode` is not one of the accepted values. | Change the value to `"auto"`, `"tls"`, or `"insecure"`. |
112
+ | `LIBRAVDB: grpcEndpointTlsCa is set but grpcEndpointTlsMode is "insecure"` (warning) | Both `grpcEndpointTlsCa` and `grpcEndpointTlsMode: "insecure"` are set. The CA file will not be used. | Remove `grpcEndpointTlsCa` if plaintext is intended, or change `grpcEndpointTlsMode` to `"auto"` or `"tls"` to use the CA file. |
@@ -12,10 +12,60 @@ CPU when a provider is unavailable.
12
12
  | Key | Type | Default | Notes |
13
13
  |---|---|---|---|
14
14
  | `sidecarPath` | string | `auto` | `"auto"` probes standard socket paths; set `unix:/path` or `tcp:host:port` to override |
15
- | `grpcEndpoint` | string | — | Optional gRPC kernel endpoint for hosts using the gRPC kernel transport |
15
+ | `grpcEndpoint` | string | — | gRPC kernel endpoint. See `grpcEndpointTlsMode` for credential control. |
16
+ | `grpcEndpointTlsCa` | string | — | Path to CA certificate PEM file. Only needed for self-signed or private CA certs. Omit when using Let's Encrypt or cert-manager. |
17
+ | `grpcEndpointTlsMode` | string | `"auto"` | gRPC credential mode. `"auto"`: loopback/unix → plaintext, remote → TLS. `"tls"`: always TLS. `"insecure"`: always plaintext. |
16
18
  | `rpcTimeoutMs` | number | `30000` | Per-call timeout for service RPC (ms) |
17
19
  | `dbPath` | string | auto-named | Explicit DB path; when set bypasses model-specific naming |
18
20
 
21
+ ### gRPC TLS behavior
22
+
23
+ The plugin selects credentials automatically based on the endpoint:
24
+
25
+ | Endpoint format | Credential mode |
26
+ |---|---|
27
+ | `unix:/path/to/sock` | Plaintext (local) |
28
+ | `tcp:127.0.0.1:port` / `tcp:localhost:port` / `[::1]:port` | Plaintext (loopback) |
29
+ | Any other TCP or DNS target | TLS |
30
+
31
+ Use `grpcEndpointTlsMode` to override the default behavior:
32
+
33
+ | Value | When to use |
34
+ |---|---|
35
+ | `"auto"` (default) | Standard operation — plugin heuristic matches daemon TLS setting automatically. |
36
+ | `"tls"` | Daemon has TLS enabled on loopback or unix socket (rare; use when the daemon's `LIBRAVDB_GRPC_TLS_*` env vars are set on a local address). |
37
+ | `"insecure"` | Service mesh or TLS-terminating tunnel handles encryption externally; both sides are plaintext. |
38
+
39
+ **Default (local daemon):** No TLS configuration needed.
40
+ Unix socket and loopback endpoints are always plaintext regardless
41
+ of any TLS settings.
42
+
43
+ **K8 / remote daemon with CA-issued cert:**
44
+ No extra configuration needed. The plugin uses the system CA pool,
45
+ which trusts certs issued by Let's Encrypt, cert-manager, and
46
+ other public CAs automatically.
47
+
48
+ **Remote daemon with self-signed or private CA cert:**
49
+ Set `grpcEndpointTlsCa` to the path of the CA certificate PEM file:
50
+ ```json
51
+ {
52
+ "grpcEndpoint": "tcp:yourdaemon.internal:50051",
53
+ "grpcEndpointTlsCa": "/etc/certs/ca.pem"
54
+ }
55
+ ```
56
+ The daemon must be configured with matching TLS cert and key via
57
+ `LIBRAVDB_GRPC_TLS_CERT` and `LIBRAVDB_GRPC_TLS_KEY`.
58
+
59
+ **Local daemon with TLS enabled:**
60
+ If the daemon has `LIBRAVDB_GRPC_TLS_CERT`/`LIBRAVDB_GRPC_TLS_KEY` set on a loopback
61
+ address, explicitly set `grpcEndpointTlsMode: "tls"` to match:
62
+ ```json
63
+ {
64
+ "grpcEndpoint": "tcp:127.0.0.1:9090",
65
+ "grpcEndpointTlsMode": "tls"
66
+ }
67
+ ```
68
+
19
69
  ## Embedding
20
70
 
21
71
  | Key | Type | Default | Notes |
@@ -0,0 +1,78 @@
1
+ # mTLS Configuration
2
+
3
+ The plugin supports mutual TLS (mTLS) for gRPC connections to the Vector Service. In mTLS, both the client and the server present X.509 certificates, and each side verifies the other's certificate against a trusted CA. When the Vector Service is configured with a client CA, it will reject any client that does not present a valid certificate signed by that CA.
4
+
5
+ ## When mTLS is required
6
+
7
+ The Vector Service operator enables mTLS by configuring a client CA on the service side. When this is enabled, the Vector Service requires every connecting client to present a certificate signed by that CA. If the plugin is configured to use TLS but does not present a client certificate, the TLS handshake will fail and the connection will be rejected.
8
+
9
+ The plugin cannot detect whether the daemon requires mTLS — it must be configured explicitly. If connections fail with a "client did not provide a certificate" error, the plugin likely needs a client certificate configured.
10
+
11
+ ## Configuration fields
12
+
13
+ | Field | Type | When to use |
14
+ |---|---|---|
15
+ | `grpcEndpointTlsClientCert` | string (file path) | Path to a PEM-encoded X.509 client certificate. Required when the Vector Service requires a client certificate. |
16
+ | `grpcEndpointTlsClientKey` | string (file path) | Path to the PEM-encoded private key that corresponds to the certificate. Required when `grpcEndpointTlsClientCert` is set. |
17
+
18
+ Both fields must be set together or not at all. Setting one without the other will cause a configuration error at startup.
19
+
20
+ These fields only take effect when the connection uses TLS — that is, when `grpcEndpointTlsMode` is not `"insecure"` and the endpoint is not a loopback address or Unix socket. See [TLS configuration](./TLS_configuration.md) for the full TLS behavior reference.
21
+
22
+ ## Certificate requirements
23
+
24
+ - **Format**: PEM-encoded X.509 certificate (-----BEGIN CERTIFICATE-----)
25
+ - **Chain order**: Leaf certificate must be first in the file; intermediate certificates may follow in the same file
26
+ - **Signing**: The certificate must be signed by the CA that the Vector Service operator configured as the client CA
27
+ - **Key types accepted**:
28
+ - RSA (any key size)
29
+ - ECDSA (P-256, P-384, P-521)
30
+ - Ed25519
31
+ - **Key file format**: PEM-encoded private key (-----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY----- etc.)
32
+ - **Key/cert match**: The private key must correspond to the leaf certificate's public key
33
+ - **Revocation**: The Vector Service does not perform revocation checking. Expired certificates are rejected (`NotAfter` boundary). Revoked but unexpired certificates are not detected. Keep certificate lifetimes short.
34
+
35
+ ## Example configuration
36
+
37
+ ```json
38
+ {
39
+ "grpcEndpoint": "tcp:libravdb.internal:9090",
40
+ "grpcEndpointTlsCa": "/etc/certs/company-ca.pem",
41
+ "grpcEndpointTlsClientCert": "/etc/certs/plugin-client.crt",
42
+ "grpcEndpointTlsClientKey": "/etc/certs/plugin-client.key"
43
+ }
44
+ ```
45
+
46
+ This example shows all four gRPC TLS fields configured together for a remote Vector Service that uses a private CA and requires mTLS.
47
+
48
+ ## Generating a client certificate (quick reference)
49
+
50
+ The following example shows OpenSSL commands to generate a client key, create a CSR, and sign it with a private CA. This is for illustration only — operators may use cert-manager, Vault, step, or other PKI tooling instead.
51
+
52
+ **1. Create a client private key:**
53
+ ```bash
54
+ openssl genpkey -algorithm ED25519 -out client.key
55
+ ```
56
+
57
+ **2. Create a certificate signing request (CSR):**
58
+ ```bash
59
+ openssl req -new -key client.key -out client.csr -subj "/CN=openclaw-plugin/O=LibraVDB"
60
+ ```
61
+
62
+ **3. Sign the CSR with the private CA:**
63
+ ```bash
64
+ openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
65
+ -CAcreateserial -out client.crt -days 365
66
+ ```
67
+
68
+ Replace `ca.crt` and `ca.key` with the CA certificate and key that the daemon operator provided.
69
+
70
+ ## Error reference
71
+
72
+ | Error | Likely cause | Fix |
73
+ |---|---|---|
74
+ | `tls: client did not provide a certificate` | mTLS is required by the Vector Service, but no client certificate is configured in the plugin | Set `grpcEndpointTlsClientCert` and `grpcEndpointTlsClientKey` in the plugin config |
75
+ | `tls: failed to verify client certificate: x509: certificate signed by unknown authority` | The client certificate is not signed by the Vector Service's client CA | Obtain a certificate signed by the CA the Vector Service operator configured as the client CA |
76
+ | `LibraVDB: failed to load TLS client certificate from "...": ENOENT: no such file or directory` | The certificate file path does not exist or is not readable | Verify the path set in `grpcEndpointTlsClientCert` exists and has correct permissions |
77
+ | `LibraVDB: failed to load TLS client key from "...": ENOENT: no such file or directory` | The key file path does not exist or is not readable | Verify the path set in `grpcEndpointTlsClientKey` exists and has correct permissions |
78
+ | `LibraVDB: grpcEndpointTlsClientCert and grpcEndpointTlsClientKey must both be set or both be omitted` | Only one of the two fields is set | Set both fields or remove both from the configuration |
@@ -2,7 +2,7 @@
2
2
  "id": "libravdb-memory",
3
3
  "name": "LibraVDB Memory",
4
4
  "description": "Persistent vector memory with three-tier hybrid scoring",
5
- "version": "1.4.80",
5
+ "version": "1.5.1",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
@@ -46,6 +46,27 @@
46
46
  "type": "string",
47
47
  "description": "Optional gRPC kernel endpoint for hosts using the daemon kernel transport."
48
48
  },
49
+ "grpcEndpointTlsCa": {
50
+ "type": "string",
51
+ "description": "Path to a CA certificate PEM file for verifying the daemon's TLS certificate. Only needed for self-signed or private CA certificates. Not required when using a publicly trusted certificate (Let's Encrypt, cert-manager, etc)."
52
+ },
53
+ "grpcEndpointTlsClientCert": {
54
+ "type": "string",
55
+ "description": "Path to a PEM-encoded X.509 client certificate for mTLS. Required when the daemon requires a client certificate. Must be paired with grpcEndpointTlsClientKey."
56
+ },
57
+ "grpcEndpointTlsClientKey": {
58
+ "type": "string",
59
+ "description": "Path to the client private key PEM file. Required when grpcEndpointTlsClientCert is set. Accepts RSA, ECDSA (P-256/P-384/P-521), Ed25519 keys."
60
+ },
61
+ "grpcEndpointTlsMode": {
62
+ "type": "string",
63
+ "enum": [
64
+ "auto",
65
+ "tls",
66
+ "insecure"
67
+ ],
68
+ "description": "gRPC credential mode. auto (default): loopback and unix socket use plaintext, remote addresses use TLS. tls: always use TLS regardless of address. insecure: always use plaintext (use with service mesh or tunnel that handles TLS externally)."
69
+ },
49
70
  "authoredHardBudgetFraction": {
50
71
  "type": "number",
51
72
  "minimum": 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.4.80",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -43,7 +43,7 @@
43
43
  "pluginSdkVersion": "2026.4.11"
44
44
  },
45
45
  "install": {
46
- "minHostVersion": ">=2026.3.22"
46
+ "minHostVersion": ">=2026.4.11"
47
47
  }
48
48
  },
49
49
  "scripts": {