@zeyiy/openclaw-channel 0.3.5 → 0.3.6

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
@@ -14,6 +14,9 @@ Chinese documentation: [README.zh-CN.md](https://github.com/ZeyiY/openclaw-chann
14
14
  - Quote/reply message parsing for inbound context
15
15
  - Multi-account login via `channels.openim.accounts.<id>`
16
16
  - Group trigger policy with optional mention-only mode
17
+ - Auto read-receipt for direct messages
18
+ - Per-user session isolation (direct chat) / shared session (group chat)
19
+ - Agent Portal Bridge — persistent WebSocket connection to agent-portal cloud service
17
20
  - Interactive setup command: `openclaw openim setup`
18
21
 
19
22
  ## Installation
@@ -58,7 +61,9 @@ openclaw openim setup
58
61
  "enabled": true,
59
62
  "token": "your_token",
60
63
  "wsAddr": "ws://127.0.0.1:10001",
61
- "apiAddr": "http://127.0.0.1:10002"
64
+ "apiAddr": "http://127.0.0.1:10002",
65
+ "botId": "my-bot-001",
66
+ "portalWsAddr": "wss://portal.example.com/ws"
62
67
  }
63
68
  }
64
69
  }
@@ -75,6 +80,8 @@ If set, only these users can trigger processing:
75
80
  - direct messages to the account
76
81
  - group messages where they `@` the account
77
82
 
83
+ `botId` and `portalWsAddr` are optional. When both are set, the plugin establishes a WebSocket connection to the agent-portal cloud service, enabling remote management of agents, files, and models.
84
+
78
85
  Single-account fallback (without `accounts`) is supported.
79
86
 
80
87
  Environment fallback is supported for the `default` account:
@@ -113,6 +120,27 @@ Optional env overrides:
113
120
  - `name` (optional): override filename for URL input
114
121
  - `accountId` (optional): select sending account
115
122
 
123
+ ## Agent Portal Bridge
124
+
125
+ When `botId` and `portalWsAddr` are configured, the plugin connects to the agent-portal cloud service via WebSocket. The portal can remotely invoke the following methods:
126
+
127
+ | Method | Description |
128
+ |---|---|
129
+ | `bot.agent.get` | Resolve the agentId bound to the current bot |
130
+ | `models.list` | List available models from config |
131
+ | `agents.list` | List all configured agents |
132
+ | `agents.create` | Create a new agent with workspace |
133
+ | `agents.files.list` | List workspace files for an agent |
134
+ | `agents.files.get` | Read a single workspace file |
135
+ | `agents.files.set` | Write a file to agent workspace |
136
+ | `tools.catalog` | List available tools |
137
+ | `skills.status` | List installed skills/plugins status |
138
+ | `skills.search` | Search ClawHub for skills (placeholder) |
139
+ | `skills.detail` | Get detail for a specific skill |
140
+ | `cron.list` | List configured cron jobs |
141
+
142
+ The connection features automatic reconnect with exponential backoff and heartbeat keepalive.
143
+
116
144
  ## Development
117
145
 
118
146
  ```bash
package/README.zh-CN.md CHANGED
@@ -14,6 +14,9 @@ English documentation: [README.md](https://github.com/ZeyiY/openclaw-channel/blo
14
14
  - 支持引用消息解析(用于入站上下文)
15
15
  - 支持多账号并发(`channels.openim.accounts.<id>`)
16
16
  - 支持群聊仅 @ 触发
17
+ - 私聊消息自动标记已读(已读回执)
18
+ - 每用户独立会话(私聊)/ 同群共享会话(群聊)
19
+ - Agent Portal Bridge — 与 agent-portal 云服务保持 WebSocket 长连接,支持远程管理
17
20
  - 提供交互式配置命令:`openclaw openim setup`
18
21
 
19
22
  ## 安装
@@ -58,7 +61,9 @@ openclaw openim setup
58
61
  "enabled": true,
59
62
  "token": "your_token",
60
63
  "wsAddr": "ws://127.0.0.1:10001",
61
- "apiAddr": "http://127.0.0.1:10002"
64
+ "apiAddr": "http://127.0.0.1:10002",
65
+ "botId": "my-bot-001",
66
+ "portalWsAddr": "wss://portal.example.com/ws"
62
67
  }
63
68
  }
64
69
  }
@@ -74,6 +79,8 @@ openclaw openim setup
74
79
  - 给账号发单聊消息
75
80
  - 在群里 @ 账号的消息
76
81
 
82
+ `botId` 和 `portalWsAddr` 为可选项。同时配置后,插件会与 agent-portal 云服务建立 WebSocket 连接,支持远程管理 agent、文件和模型。
83
+
77
84
  支持单账号兜底写法(不使用 `accounts`)。
78
85
 
79
86
  `default` 账号支持环境变量兜底:
@@ -112,6 +119,27 @@ openclaw openim setup
112
119
  - `name`(可选):URL 输入时覆盖文件名
113
120
  - `accountId`(可选):指定发送账号
114
121
 
122
+ ## Agent Portal Bridge
123
+
124
+ 配置 `botId` 和 `portalWsAddr` 后,插件会通过 WebSocket 连接到 agent-portal 云服务。Portal 可远程调用以下方法:
125
+
126
+ | 方法 | 说明 |
127
+ |---|---|
128
+ | `bot.agent.get` | 获取当前 bot 绑定的 agentId |
129
+ | `models.list` | 列出配置中的可用模型 |
130
+ | `agents.list` | 列出所有已配置的 agent |
131
+ | `agents.create` | 创建新 agent 及工作空间 |
132
+ | `agents.files.list` | 列出 agent 工作空间文件 |
133
+ | `agents.files.get` | 读取单个工作空间文件 |
134
+ | `agents.files.set` | 写入文件到 agent 工作空间 |
135
+ | `tools.catalog` | 列出可用工具 |
136
+ | `skills.status` | 列出已安装技能/插件状态 |
137
+ | `skills.search` | 搜索 ClawHub 技能(占位) |
138
+ | `skills.detail` | 获取特定技能详情 |
139
+ | `cron.list` | 列出已配置的定时任务 |
140
+
141
+ 连接支持指数退避自动重连和心跳保活。
142
+
115
143
  ## 开发
116
144
 
117
145
  ```bash
package/dist/portal.js CHANGED
@@ -374,6 +374,153 @@ function handleBotAgentGet(api, accountId) {
374
374
  const defaultEntry = agents.find((a) => a?.id && normalizeAgentId(a.id) === defaultId);
375
375
  return { agentId: defaultId, ...(defaultEntry?.name ? { name: defaultEntry.name } : {}) };
376
376
  }
377
+ /**
378
+ * tools.catalog — return the runtime tool catalog from config.
379
+ * Reads tools from agents' TOOLS.md references and registered plugin tools.
380
+ */
381
+ function handleToolsCatalog(api) {
382
+ const cfg = getConfig(api);
383
+ const tools = [];
384
+ // Core tools from config.tools section
385
+ const configTools = cfg.tools;
386
+ if (configTools && typeof configTools === "object") {
387
+ for (const [name, toolCfg] of Object.entries(configTools)) {
388
+ if (!name || name.startsWith("_"))
389
+ continue;
390
+ tools.push({
391
+ name,
392
+ description: toolCfg?.description ?? "",
393
+ source: "core",
394
+ optional: Boolean(toolCfg?.optional),
395
+ });
396
+ }
397
+ }
398
+ // Plugin-registered tools (from channel registrations)
399
+ const plugins = cfg.plugins;
400
+ if (plugins && typeof plugins === "object") {
401
+ for (const [pluginId, pluginCfg] of Object.entries(plugins)) {
402
+ if (!pluginId)
403
+ continue;
404
+ const pluginTools = pluginCfg?.tools;
405
+ if (Array.isArray(pluginTools)) {
406
+ for (const t of pluginTools) {
407
+ const tName = typeof t === "string" ? t : t?.name;
408
+ if (!tName)
409
+ continue;
410
+ tools.push({
411
+ name: String(tName),
412
+ description: typeof t === "object" ? t?.description ?? "" : "",
413
+ source: "plugin",
414
+ pluginId,
415
+ optional: typeof t === "object" ? Boolean(t?.optional) : false,
416
+ });
417
+ }
418
+ }
419
+ }
420
+ }
421
+ // Also include OpenIM channel tools registered by this plugin
422
+ const openimTools = ["openim_send_text", "openim_send_image", "openim_send_file", "openim_send_video"];
423
+ for (const name of openimTools) {
424
+ tools.push({ name, description: `OpenIM: ${name.replace("openim_", "")}`, source: "plugin", pluginId: "openim" });
425
+ }
426
+ return { tools };
427
+ }
428
+ /**
429
+ * skills.status — return the visible skill list for an agent.
430
+ * Reads installed plugins/skills from config and reports their status.
431
+ */
432
+ function handleSkillsStatus(api, params) {
433
+ const cfg = getConfig(api);
434
+ const skills = [];
435
+ // Plugins as skills
436
+ const plugins = cfg.plugins;
437
+ if (plugins && typeof plugins === "object") {
438
+ for (const [pluginId, pluginCfg] of Object.entries(plugins)) {
439
+ if (!pluginId)
440
+ continue;
441
+ const enabled = pluginCfg?.enabled !== false;
442
+ skills.push({
443
+ name: pluginId,
444
+ description: pluginCfg?.description ?? "",
445
+ installed: true,
446
+ enabled,
447
+ configCheck: enabled ? "ok" : "missing",
448
+ });
449
+ }
450
+ }
451
+ // Channels as skills
452
+ const channels = cfg.channels;
453
+ if (channels && typeof channels === "object") {
454
+ for (const [chanId, chanCfg] of Object.entries(channels)) {
455
+ if (!chanId)
456
+ continue;
457
+ skills.push({
458
+ name: `channel:${chanId}`,
459
+ description: `Channel: ${chanId}`,
460
+ installed: true,
461
+ enabled: chanCfg?.enabled !== false,
462
+ configCheck: "ok",
463
+ });
464
+ }
465
+ }
466
+ return { skills };
467
+ }
468
+ /**
469
+ * skills.search — search ClawHub for discoverable skills.
470
+ * Currently returns an empty list (ClawHub integration not yet implemented locally).
471
+ */
472
+ function handleSkillsSearch(_api, params) {
473
+ // ClawHub discovery requires network access to the registry — placeholder for now
474
+ return { skills: [] };
475
+ }
476
+ /**
477
+ * skills.detail — get detail for a specific skill from ClawHub.
478
+ * Placeholder: returns basic info from local config if the skill is installed.
479
+ */
480
+ function handleSkillsDetail(api, params) {
481
+ const name = String(params.name ?? "").trim();
482
+ if (!name)
483
+ throw { code: 400, message: "name is required" };
484
+ const cfg = getConfig(api);
485
+ const plugins = cfg.plugins;
486
+ if (plugins && typeof plugins === "object" && name in plugins) {
487
+ const p = plugins[name];
488
+ return {
489
+ skill: {
490
+ name,
491
+ description: p?.description ?? "",
492
+ author: p?.author ?? undefined,
493
+ version: p?.version ?? undefined,
494
+ source: "local",
495
+ },
496
+ };
497
+ }
498
+ return { skill: null };
499
+ }
500
+ /**
501
+ * cron.list — return configured cron jobs.
502
+ */
503
+ function handleCronList(api) {
504
+ const cfg = getConfig(api);
505
+ const jobs = [];
506
+ const cronConfig = cfg.cron ?? cfg.automation?.cron;
507
+ if (Array.isArray(cronConfig)) {
508
+ for (const entry of cronConfig) {
509
+ if (!entry || typeof entry !== "object")
510
+ continue;
511
+ jobs.push({
512
+ id: String(entry.id ?? entry.name ?? `cron-${jobs.length}`),
513
+ name: entry.name ?? entry.id ?? undefined,
514
+ schedule: String(entry.schedule ?? entry.cron ?? ""),
515
+ command: entry.command ?? entry.text ?? entry.message ?? undefined,
516
+ enabled: entry.enabled !== false,
517
+ lastRun: entry.lastRun ?? undefined,
518
+ nextRun: entry.nextRun ?? undefined,
519
+ });
520
+ }
521
+ }
522
+ return { jobs };
523
+ }
377
524
  // ---------------------------------------------------------------------------
378
525
  // Request dispatch
379
526
  // ---------------------------------------------------------------------------
@@ -404,6 +551,21 @@ async function handlePortalRequest(api, accountId, request) {
404
551
  case "agents.create":
405
552
  result = await handleAgentsCreate(api, params ?? {});
406
553
  break;
554
+ case "tools.catalog":
555
+ result = handleToolsCatalog(api);
556
+ break;
557
+ case "skills.status":
558
+ result = handleSkillsStatus(api, params ?? {});
559
+ break;
560
+ case "skills.search":
561
+ result = handleSkillsSearch(api, params ?? {});
562
+ break;
563
+ case "skills.detail":
564
+ result = handleSkillsDetail(api, params ?? {});
565
+ break;
566
+ case "cron.list":
567
+ result = handleCronList(api);
568
+ break;
407
569
  case "ping":
408
570
  result = { pong: true };
409
571
  break;
package/dist/types.d.ts CHANGED
@@ -39,7 +39,38 @@ export interface InboundBodyResult {
39
39
  kind: "text" | "image" | "video" | "file" | "mixed" | "unknown";
40
40
  media?: InboundMediaItem[];
41
41
  }
42
- export type PortalMethod = "bot.agent.get" | "models.list" | "agents.list" | "agents.files.list" | "agents.files.get" | "agents.files.set" | "agents.create" | "ping";
42
+ export type PortalMethod = "bot.agent.get" | "models.list" | "agents.list" | "agents.files.list" | "agents.files.get" | "agents.files.set" | "agents.create" | "tools.catalog" | "skills.status" | "skills.search" | "skills.detail" | "cron.list" | "ping";
43
+ export interface ToolCatalogEntry {
44
+ name: string;
45
+ description?: string;
46
+ source: "core" | "plugin";
47
+ pluginId?: string;
48
+ optional?: boolean;
49
+ }
50
+ export interface SkillStatusEntry {
51
+ name: string;
52
+ description?: string;
53
+ installed: boolean;
54
+ enabled: boolean;
55
+ configCheck?: "ok" | "missing" | "invalid";
56
+ missingRequirements?: string[];
57
+ }
58
+ export interface SkillSearchEntry {
59
+ name: string;
60
+ description?: string;
61
+ author?: string;
62
+ version?: string;
63
+ source: string;
64
+ }
65
+ export interface CronJobEntry {
66
+ id: string;
67
+ name?: string;
68
+ schedule: string;
69
+ command?: string;
70
+ enabled: boolean;
71
+ lastRun?: number;
72
+ nextRun?: number;
73
+ }
43
74
  export interface PortalRequest {
44
75
  id: string;
45
76
  method: PortalMethod;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-channel",
3
3
  "name": "OpenIM Channel",
4
- "version": "0.3.5",
4
+ "version": "0.3.6",
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.5",
3
+ "version": "0.3.6",
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",