@remnic/plugin-openclaw 9.39.0 → 9.41.0

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
@@ -5294,48 +5294,84 @@ import { Worker } from "worker_threads";
5294
5294
  import { expandTildePath } from "@remnic/core";
5295
5295
  var DEFAULT_HOST = "127.0.0.1";
5296
5296
  var DEFAULT_PORT = 4318;
5297
+ var LIVENESS_PATH = "/engram/v1/live";
5297
5298
  var LEGACY_HEALTH_PATH = "/engram/v1/health";
5298
- var SYNC_HEALTH_TIMEOUT_MS = 2e3;
5299
+ var DEFAULT_DAEMON_HEALTH_TIMEOUT_MS = 1e4;
5300
+ function parseBridgeHealthTimeoutMs(value) {
5301
+ if (value === void 0) return DEFAULT_DAEMON_HEALTH_TIMEOUT_MS;
5302
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
5303
+ if (typeof parsed !== "number" || !Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1 || parsed > 12e4) {
5304
+ throw new Error(
5305
+ `bridgeHealthTimeoutMs must be an integer in [1, 120000]; got ${String(value)}`
5306
+ );
5307
+ }
5308
+ return parsed;
5309
+ }
5310
+ function parseOpenClawBridgeConfig(config) {
5311
+ return {
5312
+ healthTimeoutMs: parseBridgeHealthTimeoutMs(config.bridgeHealthTimeoutMs)
5313
+ };
5314
+ }
5315
+ function runHealthWorker(request, data) {
5316
+ const view = new Int32Array(data.state);
5317
+ let completed = false;
5318
+ function finish(ok) {
5319
+ if (completed) return;
5320
+ completed = true;
5321
+ Atomics.store(view, 0, ok ? 1 : 2);
5322
+ Atomics.notify(view, 0);
5323
+ }
5324
+ function probe(pathname, fallbackPath) {
5325
+ const remainingMs = data.deadline - Date.now();
5326
+ if (remainingMs <= 0) {
5327
+ finish(false);
5328
+ return;
5329
+ }
5330
+ let responseReceived = false;
5331
+ try {
5332
+ const headers = {};
5333
+ if (data.token) headers.Authorization = `Bearer ${data.token}`;
5334
+ const req = request(
5335
+ {
5336
+ hostname: data.host,
5337
+ port: data.port,
5338
+ path: pathname,
5339
+ method: "GET",
5340
+ timeout: remainingMs,
5341
+ headers
5342
+ },
5343
+ (res) => {
5344
+ responseReceived = true;
5345
+ const statusCode = res.statusCode;
5346
+ res.resume();
5347
+ if (statusCode === 200) {
5348
+ finish(true);
5349
+ } else if (statusCode === 404 && fallbackPath) {
5350
+ probe(fallbackPath, null);
5351
+ } else {
5352
+ finish(false);
5353
+ }
5354
+ }
5355
+ );
5356
+ req.on("error", () => {
5357
+ if (!responseReceived) finish(false);
5358
+ });
5359
+ req.on("timeout", () => {
5360
+ req.destroy();
5361
+ if (!responseReceived) finish(false);
5362
+ });
5363
+ req.end();
5364
+ } catch {
5365
+ finish(false);
5366
+ }
5367
+ }
5368
+ probe(data.path, data.fallbackPath);
5369
+ }
5299
5370
  var HEALTH_WORKER_SOURCE = `
5300
5371
  import { request } from "node:http";
5301
5372
  import { workerData } from "node:worker_threads";
5302
-
5303
- const view = new Int32Array(workerData.state);
5304
- let completed = false;
5305
-
5306
- function finish(ok) {
5307
- if (completed) return;
5308
- completed = true;
5309
- Atomics.store(view, 0, ok ? 1 : 2);
5310
- Atomics.notify(view, 0);
5311
- }
5312
-
5313
- try {
5314
- const headers = {};
5315
- if (workerData.token) headers.Authorization = "Bearer " + workerData.token;
5316
- const req = request(
5317
- {
5318
- hostname: workerData.host,
5319
- port: workerData.port,
5320
- path: workerData.path,
5321
- method: "GET",
5322
- timeout: workerData.timeoutMs,
5323
- headers,
5324
- },
5325
- (res) => {
5326
- finish(res.statusCode === 200);
5327
- res.resume();
5328
- },
5329
- );
5330
- req.on("error", () => finish(false));
5331
- req.on("timeout", () => {
5332
- req.destroy();
5333
- finish(false);
5334
- });
5335
- req.end();
5336
- } catch {
5337
- finish(false);
5338
- }
5373
+ const __name = (target) => target;
5374
+ (${runHealthWorker.toString()})(request, workerData);
5339
5375
  `;
5340
5376
  var LAUNCHD_SERVICE_PATHS = [
5341
5377
  ["Library", "LaunchAgents", "ai.remnic.daemon.plist"],
@@ -5402,8 +5438,9 @@ function coerceDaemonPort(value) {
5402
5438
  const parsed = typeof value === "string" && value.trim() !== "" ? Number(value.trim()) : value;
5403
5439
  return typeof parsed === "number" && Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : void 0;
5404
5440
  }
5405
- function checkDaemonHealthSync(host, port, timeoutMs = SYNC_HEALTH_TIMEOUT_MS) {
5441
+ function checkDaemonHealthSync(host, port, timeoutMs = DEFAULT_DAEMON_HEALTH_TIMEOUT_MS) {
5406
5442
  if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) return false;
5443
+ const deadline = Date.now() + timeoutMs;
5407
5444
  let worker;
5408
5445
  try {
5409
5446
  const state = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
@@ -5414,14 +5451,15 @@ function checkDaemonHealthSync(host, port, timeoutMs = SYNC_HEALTH_TIMEOUT_MS) {
5414
5451
  workerData: {
5415
5452
  host,
5416
5453
  port,
5417
- path: LEGACY_HEALTH_PATH,
5454
+ path: LIVENESS_PATH,
5455
+ fallbackPath: LEGACY_HEALTH_PATH,
5418
5456
  token: loadDaemonAuth().token,
5419
- timeoutMs,
5457
+ deadline,
5420
5458
  state
5421
5459
  }
5422
5460
  };
5423
5461
  worker = new Worker(workerUrl, workerOptions);
5424
- Atomics.wait(view, 0, 0, timeoutMs + 250);
5462
+ Atomics.wait(view, 0, 0, Math.max(0, deadline - Date.now()));
5425
5463
  const status = Atomics.load(view, 0);
5426
5464
  if (status === 0) void worker.terminate();
5427
5465
  return status === 1;
@@ -5567,27 +5605,44 @@ function loadDaemonAuth() {
5567
5605
  }
5568
5606
  return { token: "", source: "no configured token" };
5569
5607
  }
5570
- async function checkDaemonHealth(host, port) {
5608
+ async function checkDaemonHealth(host, port, timeoutMs = DEFAULT_DAEMON_HEALTH_TIMEOUT_MS) {
5609
+ if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) return false;
5610
+ const deadline = Date.now() + timeoutMs;
5571
5611
  try {
5572
5612
  const { request } = await import("http");
5573
5613
  const token = loadDaemonAuth().token;
5574
5614
  const headers = {};
5575
5615
  if (token) headers["Authorization"] = `Bearer ${token}`;
5576
- return new Promise((resolve) => {
5616
+ const probe = (requestPath) => new Promise((resolve) => {
5617
+ const remainingMs = deadline - Date.now();
5618
+ if (remainingMs <= 0) {
5619
+ resolve(void 0);
5620
+ return;
5621
+ }
5622
+ let settled = false;
5623
+ const finish = (statusCode) => {
5624
+ if (settled) return;
5625
+ settled = true;
5626
+ resolve(statusCode);
5627
+ };
5577
5628
  const req = request(
5578
- { hostname: host, port, path: LEGACY_HEALTH_PATH, method: "GET", timeout: 2e3, headers },
5629
+ { hostname: host, port, path: requestPath, method: "GET", timeout: remainingMs, headers },
5579
5630
  (res) => {
5580
- resolve(res.statusCode === 200);
5631
+ finish(res.statusCode);
5581
5632
  res.resume();
5582
5633
  }
5583
5634
  );
5584
- req.on("error", () => resolve(false));
5635
+ req.on("error", () => finish(void 0));
5585
5636
  req.on("timeout", () => {
5586
5637
  req.destroy();
5587
- resolve(false);
5638
+ finish(void 0);
5588
5639
  });
5589
5640
  req.end();
5590
5641
  });
5642
+ const livenessStatus = await probe(LIVENESS_PATH);
5643
+ if (livenessStatus === 200) return true;
5644
+ if (livenessStatus !== 404) return false;
5645
+ return await probe(LEGACY_HEALTH_PATH) === 200;
5591
5646
  } catch {
5592
5647
  return false;
5593
5648
  }
@@ -6231,7 +6286,21 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
6231
6286
  );
6232
6287
  return true;
6233
6288
  }
6234
- if (!deps.checkHealth(bridge.daemonHost, bridge.daemonPort)) {
6289
+ let bridgeHealthTimeoutMs;
6290
+ try {
6291
+ bridgeHealthTimeoutMs = parseOpenClawBridgeConfig({
6292
+ bridgeHealthTimeoutMs: options.bridgeHealthTimeoutMs
6293
+ }).healthTimeoutMs;
6294
+ } catch (err) {
6295
+ log2.error(`${String(err)} \u2014 falling back to the embedded runtime`);
6296
+ delegateEmbeddedFallbackApis.add(api);
6297
+ return false;
6298
+ }
6299
+ if (!deps.checkHealth(
6300
+ bridge.daemonHost,
6301
+ bridge.daemonPort,
6302
+ bridgeHealthTimeoutMs
6303
+ )) {
6235
6304
  delegateEmbeddedFallbackApis.add(api);
6236
6305
  log2.error(
6237
6306
  `bridge mode delegate requested but no healthy daemon at ${bridge.daemonHost}:${bridge.daemonPort} \u2014 falling back to the embedded runtime`
@@ -7236,12 +7305,13 @@ var pluginDefinition = {
7236
7305
  const fileConfig = loadPluginConfigFromFile(serviceId);
7237
7306
  const openclawFlushPlanProcessingEnabled = resolveOpenClawFlushPlanProcessingEnabledFromConfig(fileConfig, api.pluginConfig);
7238
7307
  const runtimeSet = /* @__PURE__ */ new Set();
7308
+ const rawPluginConfig = {
7309
+ ...fileConfig,
7310
+ ...api.pluginConfig
7311
+ };
7239
7312
  const cfg = (0, config_exports.parseConfig)(
7240
7313
  {
7241
- ...fileConfig,
7242
- // File-backed fallback for runtimes that omit pluginConfig
7243
- ...api.pluginConfig,
7244
- // Runtime/plugin-supplied config must win
7314
+ ...rawPluginConfig,
7245
7315
  gatewayConfig: api.config
7246
7316
  // Pass gateway config for fallback AI
7247
7317
  },
@@ -7293,9 +7363,8 @@ var pluginDefinition = {
7293
7363
  const delegateApi = api;
7294
7364
  const delegateHandled = maybeRegisterDelegateRuntime(delegateApi, {
7295
7365
  serviceId,
7296
- // cfg.bridgeMode is parseConfig's merged file+runtime passthrough, so
7297
- // file-config-only deployments activate delegate too (round-7 High).
7298
7366
  configBridgeMode: cfg.bridgeMode,
7367
+ bridgeHealthTimeoutMs: rawPluginConfig.bridgeHealthTimeoutMs,
7299
7368
  passive: passiveMode,
7300
7369
  allowPromptInjection: coerceRawConfigBoolean(
7301
7370
  readPluginHooksPolicy(api.config, serviceId)?.allowPromptInjection