aicq-chat-plugin 3.3.1 → 3.5.1
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 +14 -9
- package/SKILL.md +17 -13
- package/{cli.js → cli.cjs} +19 -34
- package/index.js +191 -257
- package/lib/package.json +3 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +17 -12
- package/{postinstall.js → postinstall.cjs} +5 -4
- package/setup-entry.js +8 -55
- package/src/channel.js +398 -131
- package/src/ui-routes.js +545 -420
- package/index.mjs +0 -70
- package/setup-entry.mjs +0 -9
- package/src/channel.mjs +0 -254
- package/src/gateway-handlers.mjs +0 -178
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* AICQ Chat Plugin — Post-install script (v3.
|
|
3
|
+
* AICQ Chat Plugin — Post-install script (v3.2 Channel SDK)
|
|
4
4
|
*
|
|
5
5
|
* Displays setup information after npm install.
|
|
6
|
-
* v3.
|
|
6
|
+
* v3.2 uses official Channel Plugin SDK (defineChannelPluginEntry).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
console.log('');
|
|
10
10
|
console.log(' ╔══════════════════════════════════════════════╗');
|
|
11
|
-
console.log(' ║ AICQ Chat Plugin v3.
|
|
11
|
+
console.log(' ║ AICQ Chat Plugin v3.2 Installed! ║');
|
|
12
12
|
console.log(' ╠══════════════════════════════════════════════╣');
|
|
13
13
|
console.log(' ║ ║');
|
|
14
|
-
console.log(' ║ Architecture: Channel (in-process)
|
|
14
|
+
console.log(' ║ Architecture: Channel SDK (in-process) ║');
|
|
15
|
+
console.log(' ║ Uses defineChannelPluginEntry ║');
|
|
15
16
|
console.log(' ║ No independent port needed! ║');
|
|
16
17
|
console.log(' ║ ║');
|
|
17
18
|
console.log(' ║ Install via openclaw CLI: ║');
|
package/setup-entry.js
CHANGED
|
@@ -1,61 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AICQ Chat Plugin — Setup Wizard Entry Point
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
4
|
+
* Provides the setup-safe entry for OpenClaw's config / onboarding paths.
|
|
5
|
+
* Uses defineSetupPluginEntry from the official Channel Plugin SDK.
|
|
6
|
+
*
|
|
7
|
+
* This entry is loaded when the channel is disabled or unconfigured.
|
|
8
|
+
* It avoids pulling in heavy runtime code (database, transports, etc.).
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
id: 'welcome',
|
|
11
|
-
title: '欢迎使用 AICQ 加密聊天',
|
|
12
|
-
description: '本插件为您的智能体提供端到端加密即时通讯能力,基于 NaCl (X25519 + XSalsa20-Poly1305) 加密体系',
|
|
13
|
-
type: 'info',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: 'server',
|
|
17
|
-
title: 'AICQ 服务器配置',
|
|
18
|
-
type: 'form',
|
|
19
|
-
fields: [
|
|
20
|
-
{
|
|
21
|
-
name: 'serverUrl',
|
|
22
|
-
label: '服务器地址',
|
|
23
|
-
type: 'text',
|
|
24
|
-
default: 'https://aicq.online',
|
|
25
|
-
description: 'AICQ 信令服务器地址,用于 WebSocket 连接',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: 'autoAccept',
|
|
29
|
-
label: '自动接受好友请求',
|
|
30
|
-
type: 'checkbox',
|
|
31
|
-
default: true,
|
|
32
|
-
description: '是否自动接受来自其他智能体的好友请求',
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: 'complete',
|
|
38
|
-
title: '配置完成',
|
|
39
|
-
description: '您的智能体现在可以通过 AICQ 进行加密通讯了。在频道设置中管理好友列表,或使用 chat-friend 工具添加好友。',
|
|
40
|
-
type: 'info',
|
|
41
|
-
},
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
function register() {
|
|
45
|
-
return {
|
|
46
|
-
id: 'aicq-chat-setup',
|
|
47
|
-
label: 'AICQ Chat Setup',
|
|
48
|
-
version: '3.0.0',
|
|
49
|
-
steps: SETUP_STEPS,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getSteps() {
|
|
54
|
-
return SETUP_STEPS;
|
|
55
|
-
}
|
|
11
|
+
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
12
|
+
import { aicqChatPlugin } from "./src/channel.js";
|
|
56
13
|
|
|
57
|
-
|
|
58
|
-
register,
|
|
59
|
-
getSteps,
|
|
60
|
-
SETUP_STEPS,
|
|
61
|
-
};
|
|
14
|
+
export default defineSetupPluginEntry(aicqChatPlugin);
|
package/src/channel.js
CHANGED
|
@@ -1,163 +1,430 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AICQ Channel Plugin — Core Channel Logic
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Uses the official OpenClaw Channel Plugin SDK:
|
|
5
|
+
* createChatChannelPlugin + createChannelPluginBase
|
|
6
6
|
*
|
|
7
7
|
* Architecture: In-process Channel (no sidecar, no independent port)
|
|
8
|
+
*
|
|
9
|
+
* The runtime store is a mutable object populated by registerFull() in
|
|
10
|
+
* index.js. This keeps the channel-plugin object safe to import during
|
|
11
|
+
* setup-only / discovery modes without pulling in transport clients or
|
|
12
|
+
* database handles.
|
|
8
13
|
*/
|
|
9
|
-
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createChatChannelPlugin,
|
|
17
|
+
createChannelPluginBase,
|
|
18
|
+
} from "openclaw/plugin-sdk/channel-core";
|
|
19
|
+
|
|
20
|
+
// ── Mutable runtime store ────────────────────────────────────────────
|
|
21
|
+
// Populated lazily by the registerFull() callback in index.js.
|
|
22
|
+
// Adapters that need runtime state check these before acting.
|
|
23
|
+
export const runtime = {
|
|
24
|
+
db: null,
|
|
25
|
+
identity: null,
|
|
26
|
+
serverClient: null,
|
|
27
|
+
handshake: null,
|
|
28
|
+
chat: null,
|
|
29
|
+
dataDir: null,
|
|
30
|
+
serverUrl: null,
|
|
31
|
+
handleGateway: null,
|
|
32
|
+
_initialized: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ── Template variable resolver ───────────────────────────────────────
|
|
36
|
+
// OpenClaw stores accountId as-is (e.g. "{{agent.id}}") in config.
|
|
37
|
+
// Plugins must resolve template variables at runtime.
|
|
38
|
+
|
|
39
|
+
function resolveTemplateVar(cfg, value) {
|
|
40
|
+
if (typeof value !== "string") return value;
|
|
41
|
+
const match = value.match(/^\{\{(\w[\w.]*)\}\}$/);
|
|
42
|
+
if (!match) return value;
|
|
43
|
+
|
|
44
|
+
const path = match[1]; // e.g. "agent.id"
|
|
45
|
+
if (path === "agent.id") {
|
|
46
|
+
const agents = cfg.agents?.list || [];
|
|
47
|
+
if (agents.length > 0) return agents[0].id;
|
|
48
|
+
// Fallback: use the default agent ID from config
|
|
49
|
+
return cfg.agents?.defaultId || "default";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return value; // unknown template — return as-is
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Resolved account type ────────────────────────────────────────────
|
|
56
|
+
// This is the object returned by resolveAccount() and consumed by
|
|
57
|
+
// security / pairing / outbound adapters.
|
|
10
58
|
|
|
11
59
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
60
|
+
* Read the AICQ channel section from OpenClaw config and return a typed
|
|
61
|
+
* account object. This is the setup-safe resolver — no network or DB
|
|
62
|
+
* side effects.
|
|
14
63
|
*/
|
|
15
|
-
function
|
|
16
|
-
const
|
|
64
|
+
function resolveAccount(cfg, accountId) {
|
|
65
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
66
|
+
const rawAccountId = accountId || section.accountId || null;
|
|
67
|
+
|
|
68
|
+
if (!rawAccountId) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
"aicq-chat: accountId is required (set channels.aicq-chat.accountId)"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Resolve template variables like {{agent.id}}
|
|
75
|
+
const resolvedAccountId = resolveTemplateVar(cfg, rawAccountId);
|
|
17
76
|
|
|
18
77
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
78
|
+
accountId: resolvedAccountId,
|
|
79
|
+
serverUrl: section.serverUrl || "https://aicq.online",
|
|
80
|
+
autoAcceptFriends: section.autoAcceptFriends ?? true,
|
|
81
|
+
enabled: section.enabled ?? true,
|
|
82
|
+
dmPolicy: section.dmPolicy || "allowlist",
|
|
83
|
+
allowFrom: section.allowFrom ?? [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Lightweight account inspection for status / health / setup surfaces.
|
|
89
|
+
* Must not materialise secrets or start transports.
|
|
90
|
+
*/
|
|
91
|
+
function inspectAccount(cfg, accountId) {
|
|
92
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
93
|
+
const hasAccountId = Boolean(section.accountId || accountId);
|
|
94
|
+
return {
|
|
95
|
+
enabled: hasAccountId && section.enabled !== false,
|
|
96
|
+
configured: hasAccountId,
|
|
97
|
+
accountStatus: hasAccountId ? "available" : "missing",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Build the channel plugin ─────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const _plugin = createChatChannelPlugin({
|
|
104
|
+
base: createChannelPluginBase({
|
|
105
|
+
id: "aicq-chat",
|
|
106
|
+
|
|
107
|
+
setup: {
|
|
108
|
+
resolveAccount,
|
|
109
|
+
inspectAccount,
|
|
35
110
|
},
|
|
36
111
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
112
|
+
// Gateway method descriptors — these are the method names the plugin
|
|
113
|
+
// will register via registerFull(). Declaring them here lets OpenClaw
|
|
114
|
+
// surface them in discovery / status surfaces before full activation.
|
|
115
|
+
gatewayMethodDescriptors: [
|
|
116
|
+
"aicq.status",
|
|
117
|
+
"aicq.friends.list",
|
|
118
|
+
"aicq.friends.add",
|
|
119
|
+
"aicq.friends.remove",
|
|
120
|
+
"aicq.friends.requests",
|
|
121
|
+
"aicq.friends.acceptRequest",
|
|
122
|
+
"aicq.friends.rejectRequest",
|
|
123
|
+
"aicq.identity.info",
|
|
124
|
+
"aicq.agent.create",
|
|
125
|
+
"aicq.agent.delete",
|
|
126
|
+
"aicq.chat.send",
|
|
127
|
+
"aicq.chat.history",
|
|
128
|
+
"aicq.chat.delete",
|
|
129
|
+
"aicq.chat.streamChunk",
|
|
130
|
+
"aicq.chat.streamEnd",
|
|
131
|
+
"aicq.groups.list",
|
|
132
|
+
"aicq.groups.create",
|
|
133
|
+
"aicq.groups.join",
|
|
134
|
+
"aicq.groups.messages",
|
|
135
|
+
"aicq.groups.silent",
|
|
136
|
+
"aicq.sessions.list",
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
|
|
140
|
+
// ── DM Security ──────────────────────────────────────────────────
|
|
141
|
+
security: {
|
|
142
|
+
dm: {
|
|
143
|
+
channelKey: "aicq-chat",
|
|
144
|
+
resolvePolicy: (account) => account.dmPolicy,
|
|
145
|
+
resolveAllowFrom: (account) => account.allowFrom,
|
|
146
|
+
defaultPolicy: "allowlist",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// ── Pairing ──────────────────────────────────────────────────────
|
|
151
|
+
pairing: {
|
|
152
|
+
text: {
|
|
153
|
+
idLabel: "AICQ Friend Code",
|
|
154
|
+
message: "Share this pairing code with the other party:",
|
|
155
|
+
notify: async ({ target, code }) => {
|
|
156
|
+
// AICQ pairing codes are shared out-of-band by the operator.
|
|
157
|
+
// No automatic notification is sent to the peer.
|
|
44
158
|
},
|
|
45
159
|
},
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// ── Threading ────────────────────────────────────────────────────
|
|
163
|
+
threading: {
|
|
164
|
+
topLevelReplyToMode: "reply",
|
|
165
|
+
},
|
|
46
166
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
code,
|
|
56
|
-
instructions: `Share this pairing code with the other party: ${code}. They can add you using the chat-friend tool's add action.`,
|
|
57
|
-
};
|
|
58
|
-
} catch (e) {
|
|
59
|
-
// Fallback: use public key prefix as pairing code
|
|
60
|
-
const info = identity.getInfo(accountId);
|
|
61
|
-
const code = info ? info.exchange_public_key.slice(0, 16) : 'error';
|
|
62
|
-
return {
|
|
63
|
-
code,
|
|
64
|
-
instructions: `Share this pairing code with the other party: ${code}`,
|
|
65
|
-
};
|
|
167
|
+
// ── Outbound ─────────────────────────────────────────────────────
|
|
168
|
+
outbound: {
|
|
169
|
+
attachedResults: {
|
|
170
|
+
channel: "aicq-chat",
|
|
171
|
+
|
|
172
|
+
sendText: async (params) => {
|
|
173
|
+
if (!runtime.chat) {
|
|
174
|
+
throw new Error("AICQ runtime not initialized — cannot send text");
|
|
66
175
|
}
|
|
176
|
+
const fromId =
|
|
177
|
+
params.from ||
|
|
178
|
+
params.accountId ||
|
|
179
|
+
(runtime.identity && runtime.identity.listAgents()[0]?.agent_id);
|
|
180
|
+
const result = await runtime.chat.sendMessage(
|
|
181
|
+
fromId,
|
|
182
|
+
params.to,
|
|
183
|
+
params.text,
|
|
184
|
+
{ isGroup: false }
|
|
185
|
+
);
|
|
186
|
+
return { messageId: result?.message_id || result?.id || "sent" };
|
|
67
187
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
base: {
|
|
191
|
+
sendMedia: async (params) => {
|
|
192
|
+
if (!runtime.chat) {
|
|
193
|
+
throw new Error("AICQ runtime not initialized — cannot send media");
|
|
74
194
|
}
|
|
195
|
+
const fromId =
|
|
196
|
+
params.from ||
|
|
197
|
+
params.accountId ||
|
|
198
|
+
(runtime.identity && runtime.identity.listAgents()[0]?.agent_id);
|
|
199
|
+
await runtime.chat.sendMessage(
|
|
200
|
+
fromId,
|
|
201
|
+
params.to,
|
|
202
|
+
params.mediaUrl || params.filePath,
|
|
203
|
+
{ type: params.mediaType || "file", isGroup: false }
|
|
204
|
+
);
|
|
75
205
|
},
|
|
76
206
|
},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// ── Gateway adapter: startAccount / stopAccount ───────────────────────
|
|
211
|
+
// OpenClaw calls startAccount when the channel is activated (on startup
|
|
212
|
+
// or when re-enabled). This is where we initialise the runtime, connect
|
|
213
|
+
// to the AICQ signalling server, and wire up inbound message delivery
|
|
214
|
+
// via the channelRuntime helpers.
|
|
215
|
+
|
|
216
|
+
_plugin.gateway = {
|
|
217
|
+
/**
|
|
218
|
+
* Start the channel account — connect to the AICQ server and begin
|
|
219
|
+
* listening for inbound messages.
|
|
220
|
+
*/
|
|
221
|
+
async startAccount(ctx) {
|
|
222
|
+
const { cfg, accountId, account, setStatus } = ctx;
|
|
223
|
+
|
|
224
|
+
console.log(`[AICQ Channel] startAccount called for ${accountId}`);
|
|
225
|
+
|
|
226
|
+
// Ensure the runtime (DB, identity, transport) is initialised
|
|
227
|
+
if (runtime.handleGateway) {
|
|
228
|
+
// Runtime already initialised via registerFull — just verify
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Resolve the agent ID from OpenClaw config
|
|
232
|
+
const agents = cfg.agents?.list || [];
|
|
233
|
+
const agentId = agents.length > 0 ? agents[0].id : accountId;
|
|
77
234
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
235
|
+
// Ensure we have an identity in the plugin DB
|
|
236
|
+
if (runtime.identity) {
|
|
237
|
+
const existing = runtime.identity.listAgents();
|
|
238
|
+
if (existing.length === 0) {
|
|
239
|
+
runtime.identity.createAgent(agentId, agents[0]?.name || "AICQ Agent");
|
|
240
|
+
console.log(`[AICQ Channel] Created agent identity: ${agentId}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Connect to the AICQ server
|
|
245
|
+
if (runtime.serverClient) {
|
|
246
|
+
try {
|
|
247
|
+
await runtime.serverClient.ensureAuth(agentId);
|
|
248
|
+
console.log(`[AICQ Channel] Authenticated as ${agentId}`);
|
|
82
249
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
250
|
+
// Connect WebSocket for real-time messages
|
|
251
|
+
if (typeof runtime.serverClient.connect === "function") {
|
|
252
|
+
await runtime.serverClient.connect(agentId);
|
|
253
|
+
console.log("[AICQ Channel] WebSocket connected");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Sync friends and groups from server
|
|
257
|
+
if (runtime.handleGateway) {
|
|
87
258
|
try {
|
|
88
|
-
|
|
259
|
+
await runtime.handleGateway("aicq.friends.list", {});
|
|
260
|
+
await runtime.handleGateway("aicq.groups.list", {});
|
|
89
261
|
} catch (e) {
|
|
90
|
-
|
|
262
|
+
console.warn("[AICQ Channel] Initial sync failed:", e.message);
|
|
91
263
|
}
|
|
92
264
|
}
|
|
265
|
+
} catch (e) {
|
|
266
|
+
console.error("[AICQ Channel] Failed to connect:", e.message);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
93
269
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
onMedia: async (message) => {
|
|
103
|
-
const keys = identity.loadAgent(message.toAccountId);
|
|
104
|
-
let content = message.encryptedContent || message.content || '';
|
|
105
|
-
const session = db.loadSession(message.toAccountId, message.fromPeerId);
|
|
106
|
-
if (session && session.session_key && typeof content === 'string') {
|
|
107
|
-
try {
|
|
108
|
-
content = decryptMessage(content, session.session_key);
|
|
109
|
-
} catch (e) {}
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
mediaUrl: content,
|
|
113
|
-
mediaType: message.mediaType || 'file',
|
|
114
|
-
metadata: { fromPeerId: message.fromPeerId },
|
|
115
|
-
};
|
|
116
|
-
},
|
|
117
|
-
},
|
|
270
|
+
// Wire up inbound message handling via channelRuntime if available
|
|
271
|
+
if (ctx.channelRuntime) {
|
|
272
|
+
const { reply, routing } = ctx.channelRuntime;
|
|
273
|
+
if (reply && routing) {
|
|
274
|
+
console.log("[AICQ Channel] channelRuntime available — AI dispatch enabled");
|
|
118
275
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return result;
|
|
131
|
-
},
|
|
132
|
-
},
|
|
276
|
+
// Register inbound message handler on the serverClient
|
|
277
|
+
if (runtime.serverClient && typeof runtime.serverClient.onMessage === "function") {
|
|
278
|
+
runtime.serverClient.onMessage(async (msg) => {
|
|
279
|
+
try {
|
|
280
|
+
const resolvedAgentId = agents.length > 0 ? agents[0].id : accountId;
|
|
281
|
+
const routeResult = await routing.resolveAgentRoute({
|
|
282
|
+
channelId: "aicq-chat",
|
|
283
|
+
accountId,
|
|
284
|
+
fromId: msg.from || msg.sender_id,
|
|
285
|
+
chatType: msg.isGroup ? "group" : "dm",
|
|
286
|
+
});
|
|
133
287
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
288
|
+
if (routeResult?.agentId) {
|
|
289
|
+
await reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
290
|
+
ctx: {
|
|
291
|
+
channelId: "aicq-chat",
|
|
292
|
+
accountId,
|
|
293
|
+
fromId: msg.from || msg.sender_id,
|
|
294
|
+
text: msg.content || msg.text || "",
|
|
295
|
+
chatType: msg.isGroup ? "group" : "dm",
|
|
296
|
+
},
|
|
297
|
+
cfg,
|
|
298
|
+
dispatcherOptions: {
|
|
299
|
+
deliver: async (payload) => {
|
|
300
|
+
// Send AI reply back through AICQ
|
|
301
|
+
if (runtime.chat && payload.text) {
|
|
302
|
+
await runtime.chat.sendMessage(
|
|
303
|
+
resolvedAgentId,
|
|
304
|
+
msg.from || msg.sender_id,
|
|
305
|
+
payload.text,
|
|
306
|
+
{ isGroup: !!msg.isGroup }
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.error("[AICQ Channel] Inbound message handling error:", e.message);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
145
317
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
}
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
console.log("[AICQ Channel] channelRuntime not available — running in standalone mode");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Update health status
|
|
324
|
+
setStatus({
|
|
325
|
+
accountId,
|
|
326
|
+
enabled: true,
|
|
327
|
+
configured: true,
|
|
328
|
+
running: true,
|
|
329
|
+
lastStartAt: Date.now(),
|
|
330
|
+
lastError: null,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
console.log(`[AICQ Channel] Account ${accountId} started successfully`);
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Stop the channel account — disconnect and clean up.
|
|
338
|
+
*/
|
|
339
|
+
async stopAccount(ctx) {
|
|
340
|
+
const { accountId } = ctx;
|
|
341
|
+
console.log(`[AICQ Channel] stopAccount called for ${accountId}`);
|
|
342
|
+
|
|
343
|
+
if (runtime.serverClient && typeof runtime.serverClient.disconnect === "function") {
|
|
344
|
+
try {
|
|
345
|
+
runtime.serverClient.disconnect();
|
|
346
|
+
console.log("[AICQ Channel] WebSocket disconnected");
|
|
347
|
+
} catch (e) {
|
|
348
|
+
console.warn("[AICQ Channel] Disconnect error:", e.message);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// ── Add config helpers (required by OpenClaw channel loader) ──────────
|
|
355
|
+
// createChatChannelPlugin does not auto-attach config helpers,
|
|
356
|
+
// but the OpenClaw loader requires plugin.config.listAccountIds
|
|
357
|
+
// and plugin.config.resolveAccount for channel registration.
|
|
358
|
+
|
|
359
|
+
// resolveTemplateVar is defined at the top of this file.
|
|
360
|
+
|
|
361
|
+
_plugin.config = {
|
|
362
|
+
/**
|
|
363
|
+
* List all account IDs configured for this channel.
|
|
364
|
+
* Resolves template variables like {{agent.id}}.
|
|
365
|
+
*/
|
|
366
|
+
listAccountIds(cfg) {
|
|
367
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
368
|
+
if (section.accountId) {
|
|
369
|
+
const resolved = resolveTemplateVar(cfg, section.accountId);
|
|
370
|
+
return [resolved];
|
|
371
|
+
}
|
|
372
|
+
return [];
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Resolve an account from config. Reuses the setup resolver.
|
|
377
|
+
*/
|
|
378
|
+
resolveAccount,
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Lightweight account inspection.
|
|
382
|
+
*/
|
|
383
|
+
inspectAccount,
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Default account ID for this channel.
|
|
387
|
+
* Resolves {{agent.id}} to the actual agent ID.
|
|
388
|
+
*/
|
|
389
|
+
defaultAccountId(cfg) {
|
|
390
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
391
|
+
if (section.accountId) {
|
|
392
|
+
return resolveTemplateVar(cfg, section.accountId);
|
|
393
|
+
}
|
|
394
|
+
return "default";
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Check if the channel is configured.
|
|
399
|
+
*/
|
|
400
|
+
isConfigured(cfg) {
|
|
401
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
402
|
+
return Boolean(section.accountId);
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Return the reason the channel is not configured.
|
|
407
|
+
*/
|
|
408
|
+
unconfiguredReason(cfg) {
|
|
409
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
410
|
+
if (!section.accountId) {
|
|
411
|
+
return "accountId is required — set channels.aicq-chat.accountId in openclaw.json";
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Describe the account for status surfaces.
|
|
418
|
+
*/
|
|
419
|
+
describeAccount(cfg, accountId) {
|
|
420
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
421
|
+
const rawId = accountId || section.accountId || null;
|
|
422
|
+
return {
|
|
423
|
+
accountId: rawId ? resolveTemplateVar(cfg, rawId) : null,
|
|
424
|
+
label: "AICQ Encrypted Chat",
|
|
425
|
+
enabled: section.enabled !== false,
|
|
426
|
+
};
|
|
427
|
+
},
|
|
428
|
+
};
|
|
162
429
|
|
|
163
|
-
|
|
430
|
+
export const aicqChatPlugin = _plugin;
|