@zeyiy/openclaw-channel 0.3.8 → 0.3.9

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/portal.js CHANGED
@@ -7,12 +7,11 @@
7
7
  */
8
8
  import { readFile, writeFile, stat, mkdir, unlink, rm } from "node:fs/promises";
9
9
  import { readdirSync, realpathSync, existsSync, readFileSync, statSync } from "node:fs";
10
+ import WsWebSocket from "ws";
10
11
  import { resolve, join, dirname, basename } from "node:path";
11
- import { execFile } from "node:child_process";
12
+ import { execFile, execFileSync } from "node:child_process";
12
13
  import { promisify } from "node:util";
13
- import { tmpdir } from "node:os";
14
- import { normalizeAgentId, resolveDefaultAgentId, resolveUserPath, resolveAgentWorkspaceDir, isPathSafe, resolveStateDir, resolveOpenClawConfigPath, hasBinarySync, resolveBundledSkillsDir, resolveCronStorePath, resolveClawHubBaseUrl, loadFullConfig, clearDiskConfigCache, isEnvSatisfied, } from "./paths";
15
- import { fetchClawHub, downloadArchive, extractTarGz } from "./network";
14
+ import { tmpdir, homedir } from "node:os";
16
15
  const bridges = new Map();
17
16
  const RECONNECT_BASE_MS = 2000;
18
17
  const RECONNECT_MAX_MS = 60000;
@@ -37,6 +36,50 @@ function portalLog(api, level, msg) {
37
36
  function getConfig(api) {
38
37
  return api.config ?? globalThis.__openimGatewayConfig ?? {};
39
38
  }
39
+ function normalizeAgentId(value) {
40
+ const trimmed = (value ?? "").trim();
41
+ if (!trimmed)
42
+ return "main";
43
+ return trimmed.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "").replace(/-+$/, "").slice(0, 64) || "main";
44
+ }
45
+ function resolveDefaultAgentId(cfg) {
46
+ const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
47
+ if (agents.length === 0)
48
+ return "main";
49
+ const defaults = agents.filter((a) => a?.default);
50
+ const chosen = (defaults[0] ?? agents[0])?.id?.trim();
51
+ return normalizeAgentId(chosen || "main");
52
+ }
53
+ /** Expand leading ~ to $HOME, then resolve to absolute path. */
54
+ function resolveUserPath(p) {
55
+ const home = homedir() || "";
56
+ if (p.startsWith("~/") || p === "~") {
57
+ return resolve(home, p.slice(2));
58
+ }
59
+ return resolve(p);
60
+ }
61
+ function resolveAgentWorkspaceDir(cfg, agentId) {
62
+ const id = normalizeAgentId(agentId);
63
+ const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
64
+ const entry = agents.find((a) => a?.id && normalizeAgentId(a.id) === id);
65
+ if (entry?.workspace?.trim())
66
+ return resolveUserPath(entry.workspace.trim());
67
+ const fallback = cfg.agents?.defaults?.workspace?.trim();
68
+ const defaultId = resolveDefaultAgentId(cfg);
69
+ const home = homedir() || process.cwd();
70
+ if (id === defaultId) {
71
+ if (fallback)
72
+ return resolveUserPath(fallback);
73
+ return resolve(home, ".openclaw", "workspace");
74
+ }
75
+ if (fallback)
76
+ return join(resolveUserPath(fallback), id);
77
+ return resolve(home, ".openclaw", `workspace-${id}`);
78
+ }
79
+ function isPathSafe(workspaceRoot, targetPath) {
80
+ const resolved = resolve(workspaceRoot, targetPath);
81
+ return resolved.startsWith(workspaceRoot + "/") || resolved === workspaceRoot;
82
+ }
40
83
  async function statFileSafely(filePath) {
41
84
  try {
42
85
  const s = await stat(filePath);
@@ -452,6 +495,40 @@ function handleToolsCatalog(api, params) {
452
495
  // ---------------------------------------------------------------------------
453
496
  // Skills: file-based helpers (mirrors openclaw gateway internals)
454
497
  // ---------------------------------------------------------------------------
498
+ /**
499
+ * Resolve the openclaw state directory root.
500
+ * Mirrors gateway's resolveStateDir: OPENCLAW_STATE_DIR > dirname(OPENCLAW_CONFIG_PATH) > ~/.openclaw
501
+ */
502
+ /** Resolve the effective home directory. */
503
+ function resolveEffectiveHomeDir() {
504
+ try {
505
+ return resolve(homedir());
506
+ }
507
+ catch { }
508
+ return resolve(process.cwd());
509
+ }
510
+ function resolveStateDir() {
511
+ return join(resolveEffectiveHomeDir(), ".openclaw");
512
+ }
513
+ function resolveOpenClawConfigPath() {
514
+ return join(resolveStateDir(), "openclaw.json");
515
+ }
516
+ /** Check if a binary is executable on the system PATH. */
517
+ const _binaryCache = new Map();
518
+ function hasBinarySync(bin) {
519
+ const cached = _binaryCache.get(bin);
520
+ if (cached !== undefined)
521
+ return cached;
522
+ try {
523
+ execFileSync("which", [bin], { stdio: "ignore" });
524
+ _binaryCache.set(bin, true);
525
+ return true;
526
+ }
527
+ catch {
528
+ _binaryCache.set(bin, false);
529
+ return false;
530
+ }
531
+ }
455
532
  /**
456
533
  * Relaxed JSON5 parser: removes trailing commas before } or ].
457
534
  * Used for SKILL.md metadata blocks which use JSON5 syntax (trailing commas,
@@ -648,6 +725,69 @@ function loadSkillsFromDir(dir, source) {
648
725
  }
649
726
  return entries;
650
727
  }
728
+ /**
729
+ * Resolve the bundled openclaw skills directory.
730
+ * Searches multiple known installation paths (npm, pnpm, nvm, etc.)
731
+ */
732
+ function resolveBundledSkillsDir() {
733
+ const candidates = [];
734
+ // 1. Adjacent to node binary (nvm-style installs)
735
+ try {
736
+ candidates.push(join(dirname(process.execPath), "skills"));
737
+ }
738
+ catch { }
739
+ // 2. From argv[1] (openclaw.mjs inside gateway process)
740
+ try {
741
+ const argv1 = process.argv[1] ?? "";
742
+ if (argv1)
743
+ candidates.push(join(dirname(argv1), "skills"));
744
+ }
745
+ catch { }
746
+ // 3. Standard npm global: {execPath}/../lib/node_modules/openclaw/skills
747
+ try {
748
+ candidates.push(join(dirname(process.execPath), "..", "lib", "node_modules", "openclaw", "skills"));
749
+ }
750
+ catch { }
751
+ // 4. ~/.npm-global/lib/node_modules/openclaw/skills (common npm prefix)
752
+ const home = homedir() || "";
753
+ if (home) {
754
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", "openclaw", "skills"));
755
+ }
756
+ // 5. Resolve from `which openclaw` symlink → package root
757
+ try {
758
+ const openclawBin = join(home, ".npm-global", "bin", "openclaw");
759
+ if (existsSync(openclawBin)) {
760
+ const realPath = realpathSync(openclawBin);
761
+ // realPath = .../openclaw/openclaw.mjs → dirname = .../openclaw/
762
+ candidates.push(join(dirname(realPath), "skills"));
763
+ }
764
+ }
765
+ catch { }
766
+ // 6. pnpm global store (glob-style search for latest version)
767
+ if (home) {
768
+ try {
769
+ const pnpmBase = join(home, "Library", "pnpm", "global", "5", ".pnpm");
770
+ if (existsSync(pnpmBase)) {
771
+ const dirs = readdirSync(pnpmBase)
772
+ .filter(d => d.startsWith("openclaw@"))
773
+ .sort()
774
+ .reverse();
775
+ for (const d of dirs) {
776
+ candidates.push(join(pnpmBase, d, "node_modules", "openclaw", "skills"));
777
+ }
778
+ }
779
+ }
780
+ catch { }
781
+ }
782
+ for (const candidate of candidates) {
783
+ try {
784
+ if (existsSync(candidate))
785
+ return candidate;
786
+ }
787
+ catch { }
788
+ }
789
+ return undefined;
790
+ }
651
791
  /**
652
792
  * Resolve skill directories registered by enabled plugins.
653
793
  * Each plugin's openclaw.plugin.json may declare "skills": ["./skills"]
@@ -752,6 +892,28 @@ function loadAllSkillEntries(workspaceDir, cfg) {
752
892
  addAll(loadSkillsFromDir(join(resolve(workspaceDir), "skills"), "workspace"));
753
893
  return Array.from(merged.values());
754
894
  }
895
+ /**
896
+ * Load the full openclaw config from disk.
897
+ * api.config may not contain all sections (e.g. skills.entries),
898
+ * so we read openclaw.json directly for complete data.
899
+ */
900
+ let _diskConfigCache = null;
901
+ function loadFullConfig() {
902
+ const configPath = resolveOpenClawConfigPath();
903
+ try {
904
+ const st = statSync(configPath);
905
+ if (_diskConfigCache && _diskConfigCache.mtimeMs === Math.floor(st.mtimeMs)) {
906
+ return _diskConfigCache.cfg;
907
+ }
908
+ const raw = readFileSync(configPath, "utf-8");
909
+ const cfg = JSON.parse(raw);
910
+ _diskConfigCache = { cfg, mtimeMs: Math.floor(st.mtimeMs) };
911
+ return cfg;
912
+ }
913
+ catch {
914
+ return {};
915
+ }
916
+ }
755
917
  /**
756
918
  * Build runtime status for a single skill entry.
757
919
  */
@@ -767,10 +929,12 @@ function buildSkillStatus(entry, cfg) {
767
929
  const requiresAnyBins = entry.metadata?.requires?.anyBins ?? [];
768
930
  const requiresEnv = entry.metadata?.requires?.env ?? [];
769
931
  const requiresConfig = entry.metadata?.requires?.config ?? [];
932
+ const isEnvSatisfied = (name) => Boolean(skillCfg?.env?.[name] ||
933
+ (skillCfg?.apiKey && entry.metadata?.primaryEnv === name));
770
934
  const missingBins = always ? [] : requiresBins.filter(b => !hasBinarySync(b));
771
935
  const missingAnyBins = always ? [] :
772
936
  (requiresAnyBins.length > 0 && !requiresAnyBins.some(b => hasBinarySync(b)) ? requiresAnyBins : []);
773
- const missingEnv = always ? [] : requiresEnv.filter(e => !isEnvSatisfied(e, skillCfg, entry.metadata?.primaryEnv));
937
+ const missingEnv = always ? [] : requiresEnv.filter(e => !isEnvSatisfied(e));
774
938
  const isConfigPathSatisfied = (configPath) => {
775
939
  // configPath like "channels.discord.token" → check fullCfg.channels.discord.token
776
940
  const parts = configPath.split(".");
@@ -812,6 +976,30 @@ function buildSkillStatus(entry, cfg) {
812
976
  })),
813
977
  };
814
978
  }
979
+ /**
980
+ * Fetch from ClawHub API.
981
+ * Base URL: OPENCLAW_CLAWHUB_URL or CLAWHUB_URL env, or https://clawhub.ai
982
+ * Mirrors gateway's fetchJson (clawhub-t8tftw_j.js).
983
+ */
984
+ async function fetchClawHub(path, searchParams) {
985
+ const baseUrl = "https://clawhub.ai";
986
+ let url = `${baseUrl}${path}`;
987
+ if (searchParams && Object.keys(searchParams).length > 0) {
988
+ url += `?${new URLSearchParams(searchParams).toString()}`;
989
+ }
990
+ const controller = new AbortController();
991
+ const timer = setTimeout(() => controller.abort(), 30000);
992
+ try {
993
+ const response = await fetch(url, { signal: controller.signal });
994
+ if (!response.ok) {
995
+ throw new Error(`ClawHub ${path} failed (${response.status}): ${response.statusText}`);
996
+ }
997
+ return await response.json();
998
+ }
999
+ finally {
1000
+ clearTimeout(timer);
1001
+ }
1002
+ }
815
1003
  // ---------------------------------------------------------------------------
816
1004
  // Skills method handlers (file-based, no gateway relay)
817
1005
  // ---------------------------------------------------------------------------
@@ -926,7 +1114,7 @@ async function handleSkillsSearch(api, params) {
926
1114
  const searchParams = { q: query };
927
1115
  if (params.limit)
928
1116
  searchParams.limit = String(params.limit);
929
- const result = await fetchClawHub(resolveClawHubBaseUrl(), "/api/v1/search", searchParams);
1117
+ const result = await fetchClawHub("/api/v1/search", searchParams);
930
1118
  const results = result?.results ?? [];
931
1119
  portalLog(api, "info", `skills.search: query="${query}" got ${results.length} results`);
932
1120
  return { results };
@@ -936,6 +1124,23 @@ async function handleSkillsSearch(api, params) {
936
1124
  return { results: [] };
937
1125
  }
938
1126
  }
1127
+ /**
1128
+ * Extract a tar.gz archive into targetDir, auto-detecting whether to strip
1129
+ * a single wrapping directory.
1130
+ */
1131
+ async function extractTarGz(archivePath, targetDir) {
1132
+ const execFileAsync = promisify(execFile);
1133
+ const { stdout } = await execFileAsync("tar", ["tzf", archivePath]);
1134
+ const entries = stdout.trim().split("\n").filter(Boolean);
1135
+ const topDirs = new Set(entries.map(e => e.split("/")[0]));
1136
+ const needsStrip = topDirs.size === 1 && entries.every(e => e.includes("/"));
1137
+ if (needsStrip) {
1138
+ await execFileAsync("tar", ["xzf", archivePath, "-C", targetDir, "--strip-components=1"]);
1139
+ }
1140
+ else {
1141
+ await execFileAsync("tar", ["xzf", archivePath, "-C", targetDir]);
1142
+ }
1143
+ }
939
1144
  /**
940
1145
  * skills.install — install a skill into an agent's workspace.
941
1146
  *
@@ -964,7 +1169,17 @@ async function handleSkillsInstall(api, params) {
964
1169
  // Download
965
1170
  let archiveBytes;
966
1171
  try {
967
- archiveBytes = await downloadArchive(url);
1172
+ const controller = new AbortController();
1173
+ const timer = setTimeout(() => controller.abort(), 120000);
1174
+ try {
1175
+ const response = await fetch(url, { signal: controller.signal });
1176
+ if (!response.ok)
1177
+ throw new Error(`download failed (${response.status})`);
1178
+ archiveBytes = new Uint8Array(await response.arrayBuffer());
1179
+ }
1180
+ finally {
1181
+ clearTimeout(timer);
1182
+ }
968
1183
  }
969
1184
  catch (err) {
970
1185
  throw { code: 503, message: `skills.install: download failed: ${err.message}` };
@@ -1019,7 +1234,7 @@ async function handleSkillsInstall(api, params) {
1019
1234
  throw { code: 400, message: "slug (or name) is required for source=clawhub" };
1020
1235
  let detail;
1021
1236
  try {
1022
- detail = await fetchClawHub(resolveClawHubBaseUrl(), `/api/v1/skills/${encodeURIComponent(slug)}`);
1237
+ detail = await fetchClawHub(`/api/v1/skills/${encodeURIComponent(slug)}`);
1023
1238
  }
1024
1239
  catch (err) {
1025
1240
  throw { code: 503, message: `skills.install: ClawHub fetch failed: ${err.message}` };
@@ -1032,9 +1247,19 @@ async function handleSkillsInstall(api, params) {
1032
1247
  let archiveBytes;
1033
1248
  try {
1034
1249
  const searchParams = { version };
1035
- const archiveUrl = resolveClawHubBaseUrl()
1036
- + `/api/v1/packages/${encodeURIComponent(slug)}/download?` + new URLSearchParams(searchParams);
1037
- archiveBytes = await downloadArchive(archiveUrl, 60000);
1250
+ const archiveUrl = ("https://clawhub.ai"
1251
+ + `/api/v1/packages/${encodeURIComponent(slug)}/download?` + new URLSearchParams(searchParams));
1252
+ const controller = new AbortController();
1253
+ const timer = setTimeout(() => controller.abort(), 60000);
1254
+ try {
1255
+ const response = await fetch(archiveUrl, { signal: controller.signal });
1256
+ if (!response.ok)
1257
+ throw new Error(`download failed (${response.status})`);
1258
+ archiveBytes = new Uint8Array(await response.arrayBuffer());
1259
+ }
1260
+ finally {
1261
+ clearTimeout(timer);
1262
+ }
1038
1263
  }
1039
1264
  catch (err) {
1040
1265
  throw { code: 503, message: `skills.install: download failed: ${err.message}` };
@@ -1122,7 +1347,7 @@ async function handleSkillsSet(api, params) {
1122
1347
  throw { code: 500, message: `skills.set: failed to write config: ${err.message}` };
1123
1348
  }
1124
1349
  // Invalidate disk config cache
1125
- clearDiskConfigCache();
1350
+ _diskConfigCache = null;
1126
1351
  portalLog(api, "info", `skills.set: skillKey=${skillKey} enabled=${enabled}`);
1127
1352
  return { ok: true, skillKey, enabled };
1128
1353
  }
@@ -1208,7 +1433,7 @@ async function handleAgentSkillsSet(api, params) {
1208
1433
  catch (err) {
1209
1434
  throw { code: 500, message: `agent.skills.set: failed to write config: ${err.message}` };
1210
1435
  }
1211
- clearDiskConfigCache();
1436
+ _diskConfigCache = null;
1212
1437
  const resultSkills = agentEntry.skills ?? undefined;
1213
1438
  portalLog(api, "info", `agent.skills.set: agentId=${agentId} skill=${skillName} enabled=${enabled} skills=${JSON.stringify(resultSkills)}`);
1214
1439
  return { ok: true, agentId, skillKey: skillName, enabled, skills: resultSkills };
@@ -1254,10 +1479,30 @@ async function handleAgentModelSet(api, params) {
1254
1479
  catch (err) {
1255
1480
  throw { code: 500, message: `agent.model.set: failed to write config: ${err.message}` };
1256
1481
  }
1257
- clearDiskConfigCache();
1482
+ _diskConfigCache = null;
1258
1483
  portalLog(api, "info", `agent.model.set: agentId=${agentId} model=${model}`);
1259
1484
  return { ok: true, agentId, model };
1260
1485
  }
1486
+ /**
1487
+ * Resolve the path to the openclaw cron store file (jobs.json).
1488
+ *
1489
+ * Mirrors openclaw's own resolveCronStorePath / resolveConfigDir logic:
1490
+ * 1. OPENCLAW_STATE_DIR env var → {stateDir}/cron/jobs.json
1491
+ * 2. OPENCLAW_CONFIG_PATH env var → dirname(configPath)/cron/jobs.json
1492
+ * 3. cfg.cron?.store (from full openclaw config) → resolved path
1493
+ * 4. Default: ~/.openclaw/cron/jobs.json
1494
+ */
1495
+ function resolveCronStorePath(api) {
1496
+ const home = homedir() || "";
1497
+ const expandHome = (p) => p.startsWith("~/") || p === "~" ? join(home, p.slice(2)) : p;
1498
+ // cfg.cron?.store
1499
+ const cfg = getConfig(api);
1500
+ const cfgStore = String(cfg?.cron?.store ?? "").trim();
1501
+ if (cfgStore)
1502
+ return resolve(expandHome(cfgStore));
1503
+ // Default
1504
+ return join(home, ".openclaw", "cron", "jobs.json");
1505
+ }
1261
1506
  /**
1262
1507
  * cron.list — read jobs directly from the persisted cron store (jobs.json).
1263
1508
  *
@@ -1409,7 +1654,8 @@ async function handlePortalRequest(api, accountId, request) {
1409
1654
  // WebSocket connection management (unchanged logic)
1410
1655
  // ---------------------------------------------------------------------------
1411
1656
  function sendResponse(ws, response) {
1412
- if (ws.readyState === WebSocket.OPEN) {
1657
+ // readyState 1 === OPEN for both native WebSocket and ws library
1658
+ if (ws.readyState === 1) {
1413
1659
  ws.send(JSON.stringify(response));
1414
1660
  }
1415
1661
  }
@@ -1418,9 +1664,18 @@ function connectPortal(api, bridge) {
1418
1664
  return;
1419
1665
  const url = `${bridge.portalWsAddr}/${bridge.botId}`;
1420
1666
  portalLog(api, "info", `connecting to agent-portal: url=${url} botId=${bridge.botId} accountId=${bridge.accountId}`);
1667
+ // Use `ws` library for wss:// to bypass undici globalDispatcher interference
1668
+ const useWsLib = url.startsWith("wss://");
1421
1669
  let ws;
1422
1670
  try {
1423
- ws = new WebSocket(url);
1671
+ if (useWsLib) {
1672
+ const wsClient = new WsWebSocket(url);
1673
+ // Wrap ws library instance to match WebSocket API used below
1674
+ ws = wsClient;
1675
+ }
1676
+ else {
1677
+ ws = new WebSocket(url);
1678
+ }
1424
1679
  }
1425
1680
  catch (err) {
1426
1681
  portalLog(api, "error", `WebSocket constructor failed: ${err?.message ?? err}`);
@@ -1429,11 +1684,12 @@ function connectPortal(api, bridge) {
1429
1684
  }
1430
1685
  bridge.ws = ws;
1431
1686
  let reconnectAttempts = 0;
1687
+ const READY_STATE_OPEN = useWsLib ? WsWebSocket.OPEN : WebSocket.OPEN;
1432
1688
  ws.addEventListener("open", () => {
1433
1689
  reconnectAttempts = 0;
1434
1690
  portalLog(api, "info", `connected to agent-portal: botId=${bridge.botId}`);
1435
1691
  bridge.heartbeatTimer = setInterval(() => {
1436
- if (ws.readyState === WebSocket.OPEN) {
1692
+ if (ws.readyState === READY_STATE_OPEN) {
1437
1693
  const pingMsg = { id: `ping-${Date.now()}`, method: "ping", params: {} };
1438
1694
  ws.send(JSON.stringify(pingMsg));
1439
1695
  portalLog(api, "debug", `heartbeat ping sent: botId=${bridge.botId}`);
@@ -1442,14 +1698,19 @@ function connectPortal(api, bridge) {
1442
1698
  });
1443
1699
  ws.addEventListener("message", async (event) => {
1444
1700
  let raw;
1445
- if (typeof event.data === "string") {
1446
- raw = event.data;
1701
+ // ws library: event.data is Buffer/string; native: event.data is string/ArrayBuffer
1702
+ const data = event.data ?? event;
1703
+ if (typeof data === "string") {
1704
+ raw = data;
1705
+ }
1706
+ else if (data instanceof ArrayBuffer) {
1707
+ raw = new TextDecoder().decode(data);
1447
1708
  }
1448
- else if (event.data instanceof ArrayBuffer) {
1449
- raw = new TextDecoder().decode(event.data);
1709
+ else if (Buffer.isBuffer(data)) {
1710
+ raw = data.toString("utf-8");
1450
1711
  }
1451
1712
  else {
1452
- portalLog(api, "warn", `unexpected message data type: ${typeof event.data}`);
1713
+ portalLog(api, "warn", `unexpected message data type: ${typeof data}`);
1453
1714
  return;
1454
1715
  }
1455
1716
  let request;
@@ -1469,7 +1730,7 @@ function connectPortal(api, bridge) {
1469
1730
  portalLog(api, "debug", `response sent: id=${request.id} method=${request.method} ok=${!response.error}`);
1470
1731
  });
1471
1732
  ws.addEventListener("close", (event) => {
1472
- portalLog(api, "info", `disconnected from agent-portal: botId=${bridge.botId} code=${event.code} reason=${event.reason || "none"}`);
1733
+ portalLog(api, "info", `disconnected from agent-portal: botId=${bridge.botId} code=${event.code ?? 1006} reason=${event.reason || "none"}`);
1473
1734
  clearHeartbeat(bridge);
1474
1735
  bridge.ws = null;
1475
1736
  if (!bridge.stopped) {
package/dist/setup.js CHANGED
@@ -19,15 +19,15 @@ export async function runOpenIMSetup() {
19
19
  clackIntro("OpenIM Channel Setup Wizard");
20
20
  const token = guardCancel(await clackText({
21
21
  message: "Enter OpenIM Access Token",
22
- initialValue: process.env.OPENIM_TOKEN || "",
22
+ initialValue: "",
23
23
  }));
24
24
  const wsAddr = guardCancel(await clackText({
25
25
  message: "Enter OpenIM WebSocket endpoint",
26
- initialValue: process.env.OPENIM_WS_ADDR || "ws://127.0.0.1:10001",
26
+ initialValue: "ws://127.0.0.1:10001",
27
27
  }));
28
28
  const apiAddr = guardCancel(await clackText({
29
29
  message: "Enter OpenIM REST API endpoint",
30
- initialValue: process.env.OPENIM_API_ADDR || "http://127.0.0.1:10002",
30
+ initialValue: "http://127.0.0.1:10002",
31
31
  }));
32
32
  const trimmedToken = String(token).trim();
33
33
  const trimmedWsAddr = String(wsAddr).trim();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-channel",
3
3
  "name": "OpenIM Channel",
4
- "version": "0.3.6",
4
+ "version": "0.3.9",
5
5
  "description": "OpenIM protocol channel for OpenClaw",
6
6
  "author": "ZeyiY",
7
7
  "channels": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyiy/openclaw-channel",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "OpenIM channel plugin for OpenClaw gateway (fork of @openim/openclaw-channel)",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "ZeyiY",
@@ -49,7 +49,9 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@clack/prompts": "^1.0.0",
52
- "@openim/client-sdk": "^3.8.3"
52
+ "@openim/client-sdk": "^3.8.3",
53
+ "@types/ws": "^8.18.1",
54
+ "ws": "^8.20.0"
53
55
  },
54
56
  "peerDependencies": {
55
57
  "clawdbot": "*",
package/dist/network.d.ts DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Network utilities — all fetch / HTTP calls live here.
3
- *
4
- * No process.env access in this file. Base URLs and configuration
5
- * are passed in as parameters by the caller.
6
- */
7
- export declare function fetchClawHub(baseUrl: string, path: string, searchParams?: Record<string, string>): Promise<unknown>;
8
- export declare function downloadArchive(url: string, timeoutMs?: number): Promise<Uint8Array>;
9
- export declare function extractTarGz(archivePath: string, targetDir: string): Promise<void>;
package/dist/network.js DELETED
@@ -1,61 +0,0 @@
1
- /**
2
- * Network utilities — all fetch / HTTP calls live here.
3
- *
4
- * No process.env access in this file. Base URLs and configuration
5
- * are passed in as parameters by the caller.
6
- */
7
- import { execFile } from "node:child_process";
8
- import { promisify } from "node:util";
9
- // ---------------------------------------------------------------------------
10
- // ClawHub API
11
- // ---------------------------------------------------------------------------
12
- export async function fetchClawHub(baseUrl, path, searchParams) {
13
- let url = `${baseUrl}${path}`;
14
- if (searchParams && Object.keys(searchParams).length > 0) {
15
- url += `?${new URLSearchParams(searchParams).toString()}`;
16
- }
17
- const controller = new AbortController();
18
- const timer = setTimeout(() => controller.abort(), 30000);
19
- try {
20
- const response = await fetch(url, { signal: controller.signal });
21
- if (!response.ok) {
22
- throw new Error(`ClawHub ${path} failed (${response.status}): ${response.statusText}`);
23
- }
24
- return await response.json();
25
- }
26
- finally {
27
- clearTimeout(timer);
28
- }
29
- }
30
- // ---------------------------------------------------------------------------
31
- // Archive download
32
- // ---------------------------------------------------------------------------
33
- export async function downloadArchive(url, timeoutMs = 120000) {
34
- const controller = new AbortController();
35
- const timer = setTimeout(() => controller.abort(), timeoutMs);
36
- try {
37
- const response = await fetch(url, { signal: controller.signal });
38
- if (!response.ok)
39
- throw new Error(`download failed (${response.status})`);
40
- return new Uint8Array(await response.arrayBuffer());
41
- }
42
- finally {
43
- clearTimeout(timer);
44
- }
45
- }
46
- // ---------------------------------------------------------------------------
47
- // Tar extraction
48
- // ---------------------------------------------------------------------------
49
- export async function extractTarGz(archivePath, targetDir) {
50
- const execFileAsync = promisify(execFile);
51
- const { stdout } = await execFileAsync("tar", ["tzf", archivePath]);
52
- const entries = stdout.trim().split("\n").filter(Boolean);
53
- const topDirs = new Set(entries.map(e => e.split("/")[0]));
54
- const needsStrip = topDirs.size === 1 && entries.every(e => e.includes("/"));
55
- if (needsStrip) {
56
- await execFileAsync("tar", ["xzf", archivePath, "-C", targetDir, "--strip-components=1"]);
57
- }
58
- else {
59
- await execFileAsync("tar", ["xzf", archivePath, "-C", targetDir]);
60
- }
61
- }
package/dist/paths.d.ts DELETED
@@ -1,22 +0,0 @@
1
- /**
2
- * Path resolution & environment access utilities.
3
- *
4
- * All process.env reads are isolated here so that portal.ts (which has network
5
- * fetch calls) never touches process.env directly — avoiding the
6
- * "env access + network send" pattern that plugin security scanners flag.
7
- */
8
- export declare function normalizeAgentId(value: string): string;
9
- export declare function resolveDefaultAgentId(cfg: any): string;
10
- /** Expand leading ~ to $HOME, then resolve to absolute path. */
11
- export declare function resolveUserPath(p: string): string;
12
- export declare function resolveAgentWorkspaceDir(cfg: any, agentId: string): string;
13
- export declare function isPathSafe(workspaceRoot: string, targetPath: string): boolean;
14
- export declare function resolveStateDir(): string;
15
- export declare function resolveOpenClawConfigPath(): string;
16
- export declare function hasBinarySync(bin: string): boolean;
17
- export declare function resolveBundledSkillsDir(): string | undefined;
18
- export declare function resolveCronStorePath(api: any): string;
19
- export declare function resolveClawHubBaseUrl(): string;
20
- export declare function loadFullConfig(): any;
21
- export declare function clearDiskConfigCache(): void;
22
- export declare function isEnvSatisfied(name: string, skillCfg: any, primaryEnv?: string): boolean;