agentlife 1.5.3 → 1.5.4

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 (2) hide show
  1. package/dist/index.js +321 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // index.ts
5
- import { homedir as homedir11 } from "node:os";
6
- import * as path15 from "node:path";
5
+ import { homedir as homedir13 } from "node:os";
6
+ import * as path17 from "node:path";
7
7
  import { existsSync as existsSync6 } from "node:fs";
8
8
 
9
9
  // db.ts
@@ -2238,6 +2238,10 @@ async function transition(state, runtime, log, trigger) {
2238
2238
  if (visionSurfaceCount(state) >= 1) {
2239
2239
  return enterPhase(state, runtime, log, "READY");
2240
2240
  }
2241
+ if (lastText.length === 0) {
2242
+ log(`[cold-start] agentlife-vision replied empty in SYNTHESIZING — model error or silent fail`);
2243
+ return enterFailed(state, log, "vision_agent_empty_reply");
2244
+ }
2241
2245
  return cur;
2242
2246
  }
2243
2247
  if (cur.phase === "AWAITING_BASELINE") {
@@ -2979,6 +2983,7 @@ function computeEnhancedMetrics(db, agentId, since) {
2979
2983
 
2980
2984
  // followup.ts
2981
2985
  var pollerInterval = null;
2986
+ var startupPollTimeout = null;
2982
2987
  var sweepCounter = 0;
2983
2988
  function initFollowupSystem(api, state) {
2984
2989
  if (!state.runCommand) {
@@ -3006,14 +3011,26 @@ function initFollowupSystem(api, state) {
3006
3011
  CREATE INDEX IF NOT EXISTS idx_followups_status ON followups(status);
3007
3012
  `);
3008
3013
  pollerInterval = setInterval(() => pollFollowups(state), 60000);
3009
- setTimeout(() => pollFollowups(state), 5000);
3014
+ startupPollTimeout = setTimeout(() => {
3015
+ startupPollTimeout = null;
3016
+ pollFollowups(state);
3017
+ }, 5000);
3010
3018
  console.log("[agentlife:followup] system initialized — polling every 60s");
3011
3019
  }
3012
- function stopFollowupSystem() {
3020
+ function stopFollowupSystem(state) {
3013
3021
  if (pollerInterval) {
3014
3022
  clearInterval(pollerInterval);
3015
3023
  pollerInterval = null;
3016
3024
  }
3025
+ if (startupPollTimeout) {
3026
+ clearTimeout(startupPollTimeout);
3027
+ startupPollTimeout = null;
3028
+ }
3029
+ if (state) {
3030
+ for (const handle of state.pendingFollowups.values())
3031
+ clearTimeout(handle);
3032
+ state.pendingFollowups.clear();
3033
+ }
3017
3034
  }
3018
3035
  function parseFollowup(raw) {
3019
3036
  const match = raw.match(/^(\+\d+(?:ms|s|m|h|d))\s+(.+)/);
@@ -3035,7 +3052,15 @@ function scheduleFollowup(state, surfaceId, followupRaw, agentId) {
3035
3052
  clearTimeout(existing);
3036
3053
  state.pendingFollowups.set(surfaceId, setTimeout(() => {
3037
3054
  state.pendingFollowups.delete(surfaceId);
3038
- executeSchedule(state, surfaceId, parsed, agentId);
3055
+ if (state.disabled)
3056
+ return;
3057
+ try {
3058
+ executeSchedule(state, surfaceId, parsed, agentId);
3059
+ } catch (err) {
3060
+ if (err?.code === "ERR_INVALID_STATE")
3061
+ return;
3062
+ console.warn("[agentlife:followup] executeSchedule threw: %s", err?.message ?? err);
3063
+ }
3039
3064
  }, 2000));
3040
3065
  }
3041
3066
  function removeFollowup(state, surfaceId) {
@@ -7645,14 +7670,13 @@ function registerAdminGateway(api, state2) {
7645
7670
  } catch {
7646
7671
  cleaned.push("maxPingPongTurns already at default (no backup found)");
7647
7672
  }
7648
- stopFollowupSystem();
7673
+ stopFollowupSystem(state2);
7649
7674
  if (state2.surfaceDb)
7650
7675
  state2.surfaceDb.clear();
7651
7676
  closeAllDbs(state2);
7652
7677
  state2.agentRegistry.clear();
7653
7678
  state2.usageAccumulator.clear();
7654
7679
  state2.sessionBootstrapSnapshots.clear();
7655
- state2.pendingFollowups.clear();
7656
7680
  state2.surfaceDb = null;
7657
7681
  state2.disabled = true;
7658
7682
  cleaned.push("stopped runtime (poller, DBs, in-memory state)");
@@ -7933,17 +7957,17 @@ function parseOffset2(offset) {
7933
7957
  }
7934
7958
 
7935
7959
  // gateway/providers.ts
7936
- import * as fs11 from "node:fs";
7937
- import * as os9 from "node:os";
7938
- import * as path14 from "node:path";
7939
7960
  import {
7940
7961
  upsertApiKeyProfile,
7962
+ upsertAuthProfile,
7963
+ buildApiKeyCredential,
7941
7964
  applyAuthProfileConfig
7942
7965
  } from "openclaw/plugin-sdk/provider-auth-api-key";
7943
- var catalogCache = null;
7944
- var catalogPromise = null;
7945
- var modelsCache = null;
7946
- var modelsPromise = null;
7966
+
7967
+ // gateway/config-utils.ts
7968
+ import * as fs11 from "node:fs";
7969
+ import * as os9 from "node:os";
7970
+ import * as path14 from "node:path";
7947
7971
  function configPath() {
7948
7972
  return path14.join(os9.homedir(), ".openclaw", "openclaw.json");
7949
7973
  }
@@ -7958,6 +7982,35 @@ function writeConfig(cfg) {
7958
7982
  fs11.writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + `
7959
7983
  `, "utf-8");
7960
7984
  }
7985
+
7986
+ // gateway/providers.ts
7987
+ import * as fs12 from "node:fs";
7988
+ import * as os10 from "node:os";
7989
+ import * as path15 from "node:path";
7990
+ function siblingAgentDirs() {
7991
+ const root = path15.join(os10.homedir(), ".openclaw", "agents");
7992
+ let entries;
7993
+ try {
7994
+ entries = fs12.readdirSync(root, { withFileTypes: true });
7995
+ } catch {
7996
+ return [];
7997
+ }
7998
+ const out = [];
7999
+ for (const entry of entries) {
8000
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
8001
+ continue;
8002
+ const dir = path15.join(root, entry.name, "agent");
8003
+ try {
8004
+ if (fs12.statSync(dir).isDirectory())
8005
+ out.push(dir);
8006
+ } catch {}
8007
+ }
8008
+ return out;
8009
+ }
8010
+ var catalogCache = null;
8011
+ var catalogPromise = null;
8012
+ var modelsCache = null;
8013
+ var modelsPromise = null;
7961
8014
  async function fetchCatalog(run) {
7962
8015
  const result = await run(["openclaw", "infer", "model", "providers", "--json"], { timeoutMs: 60000 });
7963
8016
  if ((result?.code ?? 0) !== 0)
@@ -8016,6 +8069,37 @@ async function ensureModels(run) {
8016
8069
  }
8017
8070
  return modelsPromise;
8018
8071
  }
8072
+ function readOAuthExpiries() {
8073
+ const result = new Map;
8074
+ const fs13 = __require("node:fs");
8075
+ const path16 = __require("node:path");
8076
+ const os11 = __require("node:os");
8077
+ const agentsDir = path16.join(os11.homedir(), ".openclaw", "agents");
8078
+ let entries;
8079
+ try {
8080
+ entries = fs13.readdirSync(agentsDir);
8081
+ } catch {
8082
+ return result;
8083
+ }
8084
+ for (const name of entries) {
8085
+ const filePath = path16.join(agentsDir, name, "agent", "auth-profiles.json");
8086
+ try {
8087
+ const raw = fs13.readFileSync(filePath, "utf-8");
8088
+ const data = JSON.parse(raw);
8089
+ const profiles = data?.profiles ?? {};
8090
+ for (const entry of Object.values(profiles)) {
8091
+ const p = entry;
8092
+ if (p?.type === "oauth" && typeof p.provider === "string" && typeof p.expires === "number") {
8093
+ const prev = result.get(p.provider);
8094
+ if (!prev || p.expires > prev.expiresAt) {
8095
+ result.set(p.provider, { expiresAt: p.expires });
8096
+ }
8097
+ }
8098
+ }
8099
+ } catch {}
8100
+ }
8101
+ return result;
8102
+ }
8019
8103
  function authenticatedProviders() {
8020
8104
  const cfg = readConfig();
8021
8105
  const profiles = cfg?.auth?.profiles ?? {};
@@ -8135,6 +8219,12 @@ function registerProvidersGateway(api, state2) {
8135
8219
  } catch {}
8136
8220
  try {
8137
8221
  const profileId = upsertApiKeyProfile({ provider, input: apiKey });
8222
+ const credential = buildApiKeyCredential(provider, apiKey);
8223
+ for (const agentDir of siblingAgentDirs()) {
8224
+ try {
8225
+ upsertAuthProfile({ profileId, credential, agentDir });
8226
+ } catch {}
8227
+ }
8138
8228
  const cfg = readConfig();
8139
8229
  const nextCfg = applyAuthProfileConfig(cfg, {
8140
8230
  profileId,
@@ -8165,6 +8255,29 @@ function registerProvidersGateway(api, state2) {
8165
8255
  delete cfg.auth.order[provider];
8166
8256
  }
8167
8257
  writeConfig(cfg);
8258
+ for (const agentDir of siblingAgentDirs()) {
8259
+ const file = path15.join(agentDir, "auth-profiles.json");
8260
+ try {
8261
+ const raw = fs12.readFileSync(file, "utf-8");
8262
+ const data = JSON.parse(raw);
8263
+ const agentProfiles = data?.profiles;
8264
+ if (!agentProfiles || typeof agentProfiles !== "object")
8265
+ continue;
8266
+ let changed = false;
8267
+ for (const [id, entry] of Object.entries(agentProfiles)) {
8268
+ if (entry?.provider === provider) {
8269
+ delete agentProfiles[id];
8270
+ changed = true;
8271
+ }
8272
+ }
8273
+ if (data?.order?.[provider]) {
8274
+ delete data.order[provider];
8275
+ changed = true;
8276
+ }
8277
+ if (changed)
8278
+ fs12.writeFileSync(file, JSON.stringify(data, null, 2), "utf-8");
8279
+ } catch {}
8280
+ }
8168
8281
  respond(true, { provider, configured: false });
8169
8282
  } catch (err) {
8170
8283
  respond(false, { error: err?.message ?? String(err) });
@@ -8203,6 +8316,36 @@ function registerProvidersGateway(api, state2) {
8203
8316
  respond(false, { error: err?.message ?? String(err) });
8204
8317
  }
8205
8318
  }, { scope: "operator.read" });
8319
+ api.registerGatewayMethod("agentlife.providers.health", ({ respond }) => {
8320
+ try {
8321
+ const cfg = readConfig();
8322
+ const profiles = cfg?.auth?.profiles ?? {};
8323
+ const oauthInfo = readOAuthExpiries();
8324
+ const probes = [];
8325
+ for (const entry of Object.values(profiles)) {
8326
+ const p = entry;
8327
+ if (typeof p?.provider !== "string")
8328
+ continue;
8329
+ const provider = p.provider;
8330
+ const probe2 = {
8331
+ provider,
8332
+ configured: true,
8333
+ supported: true
8334
+ };
8335
+ if (p.mode === "oauth") {
8336
+ const info = oauthInfo.get(provider);
8337
+ if (info?.expiresAt) {
8338
+ probe2.expiresAt = info.expiresAt;
8339
+ probe2.oauthExpired = info.expiresAt < Date.now();
8340
+ }
8341
+ }
8342
+ probes.push(probe2);
8343
+ }
8344
+ respond(true, { probes });
8345
+ } catch (err) {
8346
+ respond(false, { error: err?.message ?? String(err) });
8347
+ }
8348
+ }, { scope: "operator.read" });
8206
8349
  api.registerGatewayMethod("agentlife.providers.loginCodex", async ({ respond }) => {
8207
8350
  if (!run)
8208
8351
  return respond(false, { error: "runCommand unavailable" });
@@ -8221,6 +8364,159 @@ ${result?.stderr ?? ""}`;
8221
8364
  }, { scope: "operator.write" });
8222
8365
  }
8223
8366
 
8367
+ // gateway/models-config.ts
8368
+ import * as fs13 from "node:fs";
8369
+ import * as os11 from "node:os";
8370
+ import * as path16 from "node:path";
8371
+ function pluginConfigPath2() {
8372
+ return path16.join(os11.homedir(), ".openclaw", "agentlife", "plugin-config.json");
8373
+ }
8374
+ function readPluginConfig2() {
8375
+ try {
8376
+ return JSON.parse(fs13.readFileSync(pluginConfigPath2(), "utf-8"));
8377
+ } catch {
8378
+ return {};
8379
+ }
8380
+ }
8381
+ function writePluginConfig2(cfg) {
8382
+ fs13.mkdirSync(path16.dirname(pluginConfigPath2()), { recursive: true });
8383
+ fs13.writeFileSync(pluginConfigPath2(), JSON.stringify(cfg, null, 2) + `
8384
+ `, "utf-8");
8385
+ }
8386
+ function registerModelsConfigGateway(api) {
8387
+ api.registerGatewayMethod("agentlife.models.defaults.get", ({ respond }) => {
8388
+ try {
8389
+ const cfg = readConfig();
8390
+ const m = cfg?.agents?.defaults?.model ?? {};
8391
+ const pluginCfg = readPluginConfig2();
8392
+ respond(true, {
8393
+ primary: typeof m.primary === "string" ? m.primary : "",
8394
+ internal: typeof pluginCfg.internalModel === "string" ? pluginCfg.internalModel : "",
8395
+ fallbacks: Array.isArray(m.fallbacks) ? m.fallbacks.filter((x) => typeof x === "string") : []
8396
+ });
8397
+ } catch (err) {
8398
+ respond(false, { error: err?.message ?? String(err) });
8399
+ }
8400
+ }, { scope: "operator.read" });
8401
+ api.registerGatewayMethod("agentlife.models.defaults.setPrimary", ({ params, respond }) => {
8402
+ const model = typeof params?.model === "string" ? params.model.trim() : "";
8403
+ if (!model)
8404
+ return respond(false, { error: "model is required" });
8405
+ try {
8406
+ const cfg = readConfig();
8407
+ cfg.agents = cfg.agents ?? {};
8408
+ cfg.agents.defaults = cfg.agents.defaults ?? {};
8409
+ cfg.agents.defaults.model = cfg.agents.defaults.model ?? {};
8410
+ cfg.agents.defaults.model.primary = model;
8411
+ writeConfig(cfg);
8412
+ respond(true, { ok: true });
8413
+ } catch (err) {
8414
+ respond(false, { error: err?.message ?? String(err) });
8415
+ }
8416
+ }, { scope: "operator.write" });
8417
+ api.registerGatewayMethod("agentlife.models.defaults.setInternal", ({ params, respond }) => {
8418
+ const model = typeof params?.model === "string" ? params.model.trim() : "";
8419
+ if (!model)
8420
+ return respond(false, { error: "model is required" });
8421
+ try {
8422
+ const cfg = readPluginConfig2();
8423
+ cfg.internalModel = model;
8424
+ writePluginConfig2(cfg);
8425
+ respond(true, { ok: true });
8426
+ } catch (err) {
8427
+ respond(false, { error: err?.message ?? String(err) });
8428
+ }
8429
+ }, { scope: "operator.write" });
8430
+ api.registerGatewayMethod("agentlife.models.fallbacks.set", ({ params, respond }) => {
8431
+ const models = Array.isArray(params?.models) ? params.models.filter((x) => typeof x === "string") : null;
8432
+ if (!models)
8433
+ return respond(false, { error: "models must be an array of strings" });
8434
+ try {
8435
+ const cfg = readConfig();
8436
+ cfg.agents = cfg.agents ?? {};
8437
+ cfg.agents.defaults = cfg.agents.defaults ?? {};
8438
+ cfg.agents.defaults.model = cfg.agents.defaults.model ?? {};
8439
+ cfg.agents.defaults.model.fallbacks = models;
8440
+ writeConfig(cfg);
8441
+ respond(true, { ok: true });
8442
+ } catch (err) {
8443
+ respond(false, { error: err?.message ?? String(err) });
8444
+ }
8445
+ }, { scope: "operator.write" });
8446
+ api.registerGatewayMethod("agentlife.agents.list", ({ respond }) => {
8447
+ try {
8448
+ const cfg = readConfig();
8449
+ const list = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
8450
+ const agents = list.map((a) => ({
8451
+ id: typeof a?.id === "string" ? a.id : null,
8452
+ name: typeof a?.name === "string" ? a.name : null,
8453
+ modelOverride: typeof a?.model === "string" ? a.model : null
8454
+ })).filter((a) => a.id !== null && a.name !== null);
8455
+ respond(true, { agents });
8456
+ } catch (err) {
8457
+ respond(false, { error: err?.message ?? String(err) });
8458
+ }
8459
+ }, { scope: "operator.read" });
8460
+ api.registerGatewayMethod("agentlife.agents.setModel", ({ params, respond }) => {
8461
+ const agentId = typeof params?.agentId === "string" ? params.agentId.trim() : "";
8462
+ if (!agentId)
8463
+ return respond(false, { error: "agentId is required" });
8464
+ const model = params?.model;
8465
+ if (model !== null && typeof model !== "string") {
8466
+ return respond(false, { error: "model must be string or null" });
8467
+ }
8468
+ try {
8469
+ const cfg = readConfig();
8470
+ const list = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
8471
+ const idx = list.findIndex((a) => a?.id === agentId);
8472
+ if (idx < 0)
8473
+ return respond(false, { error: `agent not found: ${agentId}` });
8474
+ if (model === null) {
8475
+ delete list[idx].model;
8476
+ } else {
8477
+ list[idx].model = model.trim();
8478
+ }
8479
+ writeConfig(cfg);
8480
+ respond(true, { ok: true });
8481
+ } catch (err) {
8482
+ respond(false, { error: err?.message ?? String(err) });
8483
+ }
8484
+ }, { scope: "operator.write" });
8485
+ api.registerGatewayMethod("agentlife.agents.setModel.bulk", ({ params, respond }) => {
8486
+ const updates = Array.isArray(params?.updates) ? params.updates : null;
8487
+ if (!updates)
8488
+ return respond(false, { error: "updates must be an array" });
8489
+ for (const u of updates) {
8490
+ if (!u || typeof u.agentId !== "string" || !u.agentId.trim()) {
8491
+ return respond(false, { error: "each update needs agentId (string)" });
8492
+ }
8493
+ if (u.model !== null && typeof u.model !== "string") {
8494
+ return respond(false, { error: "each update model must be string or null" });
8495
+ }
8496
+ }
8497
+ try {
8498
+ const cfg = readConfig();
8499
+ const list = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
8500
+ let applied = 0;
8501
+ for (const u of updates) {
8502
+ const idx = list.findIndex((a) => a?.id === u.agentId);
8503
+ if (idx < 0)
8504
+ continue;
8505
+ if (u.model === null) {
8506
+ delete list[idx].model;
8507
+ } else {
8508
+ list[idx].model = u.model.trim();
8509
+ }
8510
+ applied++;
8511
+ }
8512
+ writeConfig(cfg);
8513
+ respond(true, { ok: true, applied });
8514
+ } catch (err) {
8515
+ respond(false, { error: err?.message ?? String(err) });
8516
+ }
8517
+ }, { scope: "operator.write" });
8518
+ }
8519
+
8224
8520
  // index.ts
8225
8521
  var currentState = null;
8226
8522
  var registered = false;
@@ -8230,7 +8526,7 @@ var stopQualityCheckPoller = null;
8230
8526
  var stopCloudflared = null;
8231
8527
  function resolveInternalModel(api) {
8232
8528
  try {
8233
- const pluginCfgPath = path15.join(homedir11(), ".openclaw", "agentlife", "plugin-config.json");
8529
+ const pluginCfgPath = path17.join(homedir13(), ".openclaw", "agentlife", "plugin-config.json");
8234
8530
  try {
8235
8531
  const raw = __require("node:fs").readFileSync(pluginCfgPath, "utf-8");
8236
8532
  const pluginCfg = JSON.parse(raw);
@@ -8266,7 +8562,7 @@ function register(api) {
8266
8562
  return;
8267
8563
  }
8268
8564
  registered = true;
8269
- const fallbackDir = path15.join(homedir11(), ".openclaw", "agentlife");
8565
+ const fallbackDir = path17.join(homedir13(), ".openclaw", "agentlife");
8270
8566
  const state2 = {
8271
8567
  surfaceDb: null,
8272
8568
  agentRegistry: new Map,
@@ -8276,9 +8572,9 @@ function register(api) {
8276
8572
  agentDbs: new Map,
8277
8573
  historyDb: null,
8278
8574
  agentlifeStateDir: fallbackDir,
8279
- registryFilePath: path15.join(fallbackDir, "agent-registry.json"),
8280
- dbBaseDir: path15.join(fallbackDir, "db"),
8281
- historyDbPath: path15.join(fallbackDir, "agentlife.db"),
8575
+ registryFilePath: path17.join(fallbackDir, "agent-registry.json"),
8576
+ dbBaseDir: path17.join(fallbackDir, "db"),
8577
+ historyDbPath: path17.join(fallbackDir, "agentlife.db"),
8282
8578
  runCommand: api.runtime.system?.runCommandWithTimeout ?? null,
8283
8579
  enqueueSystemEvent: null,
8284
8580
  requestHeartbeatNow: null,
@@ -8319,7 +8615,7 @@ function register(api) {
8319
8615
  id: "agentlife-shutdown",
8320
8616
  start: () => {},
8321
8617
  stop: () => {
8322
- stopFollowupSystem();
8618
+ stopFollowupSystem(state2);
8323
8619
  if (stopObservability) {
8324
8620
  stopObservability();
8325
8621
  stopObservability = null;
@@ -8354,8 +8650,9 @@ function register(api) {
8354
8650
  registerFollowupsGateway(api, state2);
8355
8651
  registerAdminGateway(api, state2);
8356
8652
  registerProvidersGateway(api, state2);
8653
+ registerModelsConfigGateway(api);
8357
8654
  registerWebApp(api);
8358
- const notifyConfigPath = path15.join(fallbackDir, "notification-config.json");
8655
+ const notifyConfigPath = path17.join(fallbackDir, "notification-config.json");
8359
8656
  api.registerGatewayMethod("agentlife.notifications.register", ({ params, respond }) => {
8360
8657
  const serverUrl = typeof params?.serverUrl === "string" ? params.serverUrl.trim() : "";
8361
8658
  const apiKey = typeof params?.apiKey === "string" ? params.apiKey.trim() : "";
@@ -8363,9 +8660,9 @@ function register(api) {
8363
8660
  return respond(false, { error: "missing serverUrl or apiKey" });
8364
8661
  }
8365
8662
  try {
8366
- const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync6 } = __require("node:fs");
8367
- mkdirSync6(path15.dirname(notifyConfigPath), { recursive: true });
8368
- writeFileSync8(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
8663
+ const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync7 } = __require("node:fs");
8664
+ mkdirSync7(path17.dirname(notifyConfigPath), { recursive: true });
8665
+ writeFileSync10(notifyConfigPath, JSON.stringify({ serverUrl, apiKey }));
8369
8666
  } catch {}
8370
8667
  respond(true, { registered: true });
8371
8668
  }, { scope: "operator.write" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",