openclaw-elys 1.6.0 → 1.7.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/README.md +16 -0
- package/dist/index.js +2 -0
- package/dist/src/channel.d.ts +0 -1
- package/dist/src/channel.js +0 -1
- package/dist/src/monitor.d.ts +1 -33
- package/dist/src/monitor.js +56 -40
- package/dist/src/runtime.d.ts +2 -0
- package/dist/src/runtime.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,22 @@ OpenClaw channel plugin for [Elys App](https://elys.ai) — connects your local
|
|
|
4
4
|
|
|
5
5
|
Elys App 的 OpenClaw 频道插件 — 将本地 OpenClaw 智能体连接到 Elys 手机应用。
|
|
6
6
|
|
|
7
|
+
## Requirements / 版本要求
|
|
8
|
+
|
|
9
|
+
OpenClaw >= **2026.2.19**(通过 `openclaw -v` 查看当前版本)
|
|
10
|
+
|
|
11
|
+
低于该版本可能导致 AI 推理无法正常工作(缺少 channelRuntime 支持)。
|
|
12
|
+
|
|
13
|
+
升级方式:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g openclaw
|
|
17
|
+
# 国内网络慢可使用镜像源:
|
|
18
|
+
npm install -g openclaw --registry=https://registry.npmmirror.com
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
其他升级方式参考:[OpenClaw 安装文档](https://docs.openclaw.ai/install/development-channels#switching-channels)
|
|
22
|
+
|
|
7
23
|
## Install & Setup / 安装与配置
|
|
8
24
|
|
|
9
25
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { elysPlugin } from "./src/channel.js";
|
|
2
|
+
import { setElysRuntime } from "./src/runtime.js";
|
|
2
3
|
export { elysPlugin } from "./src/channel.js";
|
|
3
4
|
export { monitorElysProvider } from "./src/monitor.js";
|
|
4
5
|
export { registerDevice } from "./src/register.js";
|
|
@@ -10,6 +11,7 @@ const plugin = {
|
|
|
10
11
|
description: "Elys App channel plugin — connects to Elys App via MQTT gateway",
|
|
11
12
|
configSchema: { type: "object", properties: {} },
|
|
12
13
|
register(api) {
|
|
14
|
+
setElysRuntime(api.runtime);
|
|
13
15
|
api.registerChannel({ plugin: elysPlugin });
|
|
14
16
|
},
|
|
15
17
|
};
|
package/dist/src/channel.d.ts
CHANGED
package/dist/src/channel.js
CHANGED
package/dist/src/monitor.d.ts
CHANGED
|
@@ -3,39 +3,8 @@ export interface MonitorElysOpts {
|
|
|
3
3
|
runtime?: Record<string, unknown>;
|
|
4
4
|
abortSignal?: AbortSignal;
|
|
5
5
|
accountId?: string;
|
|
6
|
-
channelRuntime?:
|
|
6
|
+
channelRuntime?: unknown;
|
|
7
7
|
}
|
|
8
|
-
/** Subset of OpenClaw's PluginRuntime["channel"] that we use */
|
|
9
|
-
type ReplyPayload = {
|
|
10
|
-
text?: string;
|
|
11
|
-
mediaUrl?: string;
|
|
12
|
-
audioAsVoice?: boolean;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
};
|
|
15
|
-
type ChannelRuntimeReply = {
|
|
16
|
-
reply?: {
|
|
17
|
-
dispatchReplyWithBufferedBlockDispatcher?: (params: {
|
|
18
|
-
ctx: Record<string, unknown>;
|
|
19
|
-
cfg: Record<string, unknown>;
|
|
20
|
-
dispatcherOptions: {
|
|
21
|
-
deliver: (payload: ReplyPayload, info: {
|
|
22
|
-
kind: "tool" | "block" | "final";
|
|
23
|
-
}) => Promise<void>;
|
|
24
|
-
onError?: (err: unknown, info: {
|
|
25
|
-
kind: string;
|
|
26
|
-
}) => void;
|
|
27
|
-
};
|
|
28
|
-
replyOptions?: Record<string, unknown>;
|
|
29
|
-
}) => Promise<{
|
|
30
|
-
queuedFinal: boolean;
|
|
31
|
-
counts: Record<string, number>;
|
|
32
|
-
}>;
|
|
33
|
-
finalizeInboundContext?: (ctx: Record<string, unknown>) => Record<string, unknown>;
|
|
34
|
-
};
|
|
35
|
-
routing?: {
|
|
36
|
-
resolveAgentRoute?: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
8
|
/**
|
|
40
9
|
* The main monitor loop for the Elys channel.
|
|
41
10
|
* Ensures device is registered, then connects to MQTT and dispatches
|
|
@@ -43,4 +12,3 @@ type ChannelRuntimeReply = {
|
|
|
43
12
|
* channelRuntime dispatch API (supports streaming).
|
|
44
13
|
*/
|
|
45
14
|
export declare function monitorElysProvider(opts: MonitorElysOpts): Promise<void>;
|
|
46
|
-
export {};
|
package/dist/src/monitor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadCredentials } from "./config.js";
|
|
2
2
|
import { registerDevice } from "./register.js";
|
|
3
3
|
import { ElysDeviceMQTTClient } from "./mqtt-client.js";
|
|
4
|
+
import { getElysRuntime } from "./runtime.js";
|
|
4
5
|
/**
|
|
5
6
|
* The main monitor loop for the Elys channel.
|
|
6
7
|
* Ensures device is registered, then connects to MQTT and dispatches
|
|
@@ -26,19 +27,18 @@ export async function monitorElysProvider(opts) {
|
|
|
26
27
|
}
|
|
27
28
|
// 2. Connect MQTT
|
|
28
29
|
const mqttClient = new ElysDeviceMQTTClient(credentials, log);
|
|
29
|
-
// 3. Set up command handler
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
30
|
+
// 3. Set up command handler using PluginRuntime (same pattern as feishu)
|
|
31
|
+
const core = getElysRuntime();
|
|
32
|
+
const dispatchReplyFromConfig = core?.channel?.reply?.dispatchReplyFromConfig;
|
|
33
|
+
const withReplyDispatcher = core?.channel?.reply?.withReplyDispatcher;
|
|
34
|
+
const finalizeCtx = core?.channel?.reply?.finalizeInboundContext;
|
|
35
|
+
log(`[elys] pluginRuntime available: ${!!core}, dispatchReplyFromConfig: ${!!dispatchReplyFromConfig}, finalizeCtx: ${!!finalizeCtx}`);
|
|
34
36
|
const commandHandler = async (cmd, signal) => {
|
|
35
37
|
log(`[elys] executing command: ${cmd.command}`, cmd.args);
|
|
36
|
-
|
|
37
|
-
if (dispatchReply && finalizeCtx) {
|
|
38
|
+
if (dispatchReplyFromConfig && finalizeCtx) {
|
|
38
39
|
try {
|
|
39
40
|
let seq = 0;
|
|
40
41
|
let fullText = "";
|
|
41
|
-
// Build inbound context following OpenClaw protocol
|
|
42
42
|
const inboundCtx = finalizeCtx({
|
|
43
43
|
Body: formatCommandAsText(cmd),
|
|
44
44
|
BodyForAgent: formatCommandAsText(cmd),
|
|
@@ -55,37 +55,53 @@ export async function monitorElysProvider(opts) {
|
|
|
55
55
|
OriginatingChannel: "elys",
|
|
56
56
|
OriginatingTo: credentials.deviceId,
|
|
57
57
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
58
|
+
// Deliver callback: stream chunks back via MQTT
|
|
59
|
+
const deliver = async (payload, info) => {
|
|
60
|
+
if (signal.aborted)
|
|
61
|
+
return;
|
|
62
|
+
if (payload.text) {
|
|
63
|
+
fullText += payload.text;
|
|
64
|
+
seq++;
|
|
65
|
+
const done = info.kind === "final";
|
|
66
|
+
mqttClient.publishStreamChunk(cmd.id, payload.text, seq, done, cmd.reply_to);
|
|
67
|
+
if (info.kind === "block") {
|
|
68
|
+
log(`[elys] stream chunk #${seq}: ${payload.text.slice(0, 80)}...`);
|
|
69
|
+
}
|
|
70
|
+
else if (info.kind === "final") {
|
|
71
|
+
log(`[elys] final reply delivered`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (info.kind === "final") {
|
|
75
|
+
seq++;
|
|
76
|
+
mqttClient.publishStreamChunk(cmd.id, "", seq, true, cmd.reply_to);
|
|
77
|
+
log(`[elys] final reply delivered (empty)`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
if (withReplyDispatcher) {
|
|
81
|
+
// Preferred path: withReplyDispatcher + dispatchReplyFromConfig (same as feishu)
|
|
82
|
+
await withReplyDispatcher({
|
|
83
|
+
ctx: inboundCtx,
|
|
84
|
+
cfg: opts.config,
|
|
85
|
+
dispatcher: { deliver },
|
|
86
|
+
fn: (dispatchCtx) => dispatchReplyFromConfig({
|
|
87
|
+
ctx: dispatchCtx,
|
|
88
|
+
cfg: opts.config,
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Direct dispatch fallback
|
|
94
|
+
await dispatchReplyFromConfig({
|
|
95
|
+
ctx: inboundCtx,
|
|
96
|
+
cfg: opts.config,
|
|
97
|
+
dispatcherOptions: {
|
|
98
|
+
deliver,
|
|
99
|
+
onError: (err, info) => {
|
|
100
|
+
log(`[elys] dispatch error (${info.kind}):`, err);
|
|
101
|
+
},
|
|
86
102
|
},
|
|
87
|
-
}
|
|
88
|
-
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
89
105
|
return {
|
|
90
106
|
id: cmd.id,
|
|
91
107
|
type: "result",
|
|
@@ -105,8 +121,8 @@ export async function monitorElysProvider(opts) {
|
|
|
105
121
|
};
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
|
-
// Fallback: echo the command back (no
|
|
109
|
-
log(`[elys] no
|
|
124
|
+
// Fallback: echo the command back (no pluginRuntime available)
|
|
125
|
+
log(`[elys] no pluginRuntime — using fallback echo handler`);
|
|
110
126
|
return {
|
|
111
127
|
id: cmd.id,
|
|
112
128
|
type: "result",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Stores the PluginRuntime passed via api.runtime during plugin registration.
|
|
2
|
+
// This is the same pattern used by the built-in feishu channel plugin.
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
let runtime = null;
|
|
5
|
+
export function setElysRuntime(next) {
|
|
6
|
+
runtime = next;
|
|
7
|
+
}
|
|
8
|
+
export function getElysRuntime() {
|
|
9
|
+
return runtime;
|
|
10
|
+
}
|