@coolclaw/coolclaw 0.2.9 → 0.2.10

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.
@@ -1,3 +1,485 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/binding.ts
12
+ import { mkdir, readFile, rename, writeFile, chmod } from "fs/promises";
13
+ import { homedir as homedir2 } from "os";
14
+ import path from "path";
15
+ import { fileURLToPath } from "url";
16
+ function defaultBindingFile(home = homedir2()) {
17
+ return path.join(home, ".config", "coolclaw", "agent_binding.json");
18
+ }
19
+ function defaultOpenClawConfigFile(home = homedir2()) {
20
+ return path.join(home, ".openclaw", "openclaw.json");
21
+ }
22
+ function defaultTokenFile(bindingFile, agentId) {
23
+ return path.join(path.dirname(bindingFile), `agent_token_${agentId}.txt`);
24
+ }
25
+ async function loadBinding(bindingFile) {
26
+ try {
27
+ const raw = JSON.parse(await readFile(bindingFile, "utf8"));
28
+ return {
29
+ agentId: String(raw.agentId ?? ""),
30
+ tokenRef: typeof raw.tokenRef === "string" ? raw.tokenRef : null,
31
+ runtimeType: String(raw.runtimeType ?? "unknown"),
32
+ lastAckedSeq: Number(raw.lastAckedSeq ?? 0),
33
+ bindingVersion: Number(raw.bindingVersion ?? RIDDLE_BINDING_VERSION),
34
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : void 0
35
+ };
36
+ } catch {
37
+ return {
38
+ agentId: "",
39
+ tokenRef: null,
40
+ runtimeType: "unknown",
41
+ lastAckedSeq: 0,
42
+ bindingVersion: RIDDLE_BINDING_VERSION
43
+ };
44
+ }
45
+ }
46
+ function touchBinding(binding) {
47
+ return {
48
+ ...binding,
49
+ bindingVersion: RIDDLE_BINDING_VERSION,
50
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
51
+ };
52
+ }
53
+ async function saveBinding(bindingFile, binding) {
54
+ await mkdir(path.dirname(bindingFile), { recursive: true });
55
+ const tmpFile = `${bindingFile}.${process.pid}.tmp`;
56
+ await writeFile(tmpFile, `${JSON.stringify(touchBinding(binding), null, 2)}
57
+ `, { mode: 384 });
58
+ await chmod(tmpFile, 384);
59
+ await rename(tmpFile, bindingFile);
60
+ }
61
+ async function saveAgentToken(tokenFile, token) {
62
+ await mkdir(path.dirname(tokenFile), { recursive: true });
63
+ await writeFile(tokenFile, token, { mode: 384 });
64
+ await chmod(tokenFile, 384);
65
+ }
66
+ async function readTokenRef(tokenRef) {
67
+ if (!tokenRef) return void 0;
68
+ if (tokenRef.startsWith("env:")) {
69
+ return process.env[tokenRef.slice("env:".length)] || void 0;
70
+ }
71
+ if (tokenRef.startsWith("file://")) {
72
+ const tokenPath = fileURLToPath(tokenRef);
73
+ return (await readFile(tokenPath, "utf8")).trim() || void 0;
74
+ }
75
+ return void 0;
76
+ }
77
+ var RIDDLE_BINDING_VERSION;
78
+ var init_binding = __esm({
79
+ "src/binding.ts"() {
80
+ "use strict";
81
+ RIDDLE_BINDING_VERSION = 1;
82
+ }
83
+ });
84
+
85
+ // src/config.ts
86
+ import { z } from "zod";
87
+ function normalizeGatewayUrl(value) {
88
+ return value.trim().replace(/\/+$/, "");
89
+ }
90
+ function buildWsUrl(gatewayUrl, lastAckedSeq) {
91
+ const baseUrl = normalizeGatewayUrl(gatewayUrl).replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
92
+ return `${baseUrl}/ws/channel?lastAckedSeq=${lastAckedSeq}`;
93
+ }
94
+ async function resolveAccountToken(account) {
95
+ if (account.tokenSecret) return account.tokenSecret;
96
+ return readTokenRef(account.tokenSecretRef);
97
+ }
98
+ var CoolclawConfigSchema;
99
+ var init_config = __esm({
100
+ "src/config.ts"() {
101
+ "use strict";
102
+ init_binding();
103
+ CoolclawConfigSchema = z.object({
104
+ gatewayUrl: z.string().url().optional().describe("Riddle gateway URL"),
105
+ agentId: z.string().optional().describe("Riddle agent ID"),
106
+ tokenSecretRef: z.string().optional().describe("Token secret reference (file:// or env:)"),
107
+ allowFrom: z.array(z.string()).optional().describe("Allowed sender IDs"),
108
+ dmPolicy: z.enum(["allowlist", "pairing", "open"]).optional().describe("DM access policy"),
109
+ enabled: z.boolean().optional().describe("Whether the account is enabled"),
110
+ name: z.string().optional().describe("Account display name")
111
+ });
112
+ }
113
+ });
114
+
115
+ // src/identity.ts
116
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
117
+ import path2 from "path";
118
+ async function updateRiddleIdentitySummary(summary) {
119
+ await mkdir2(summary.workspaceDir, { recursive: true });
120
+ const identityFile = path2.join(summary.workspaceDir, "IDENTITY.md");
121
+ const existing = await readExisting(identityFile);
122
+ const block = [
123
+ BEGIN_MARKER,
124
+ "## Riddle Platform",
125
+ "",
126
+ `Riddle Agent ID: ${summary.agentId}`,
127
+ `Riddle Gateway: ${normalizeGatewayUrl(summary.gatewayUrl)}`,
128
+ `Riddle Binding: ${summary.bindingFile}`,
129
+ "Messaging: handled by the CoolClaw OpenClaw channel.",
130
+ "Non-message platform actions: handled by the Riddle skill.",
131
+ END_MARKER
132
+ ].join("\n");
133
+ const next = replaceBlock(existing, block);
134
+ await writeFile2(identityFile, next.endsWith("\n") ? next : `${next}
135
+ `);
136
+ }
137
+ async function readExisting(identityFile) {
138
+ try {
139
+ return await readFile2(identityFile, "utf8");
140
+ } catch {
141
+ return "";
142
+ }
143
+ }
144
+ function replaceBlock(existing, block) {
145
+ const start = existing.indexOf(BEGIN_MARKER);
146
+ const end = existing.indexOf(END_MARKER);
147
+ if (start >= 0 && end >= start) {
148
+ return `${existing.slice(0, start).trimEnd()}
149
+
150
+ ${block}
151
+ ${existing.slice(end + END_MARKER.length).trimStart()}`;
152
+ }
153
+ return existing.trim() ? `${existing.trimEnd()}
154
+
155
+ ${block}
156
+ ` : `${block}
157
+ `;
158
+ }
159
+ var BEGIN_MARKER, END_MARKER;
160
+ var init_identity = __esm({
161
+ "src/identity.ts"() {
162
+ "use strict";
163
+ init_config();
164
+ BEGIN_MARKER = "<!-- BEGIN_RIDDLE_IDENTITY -->";
165
+ END_MARKER = "<!-- END_RIDDLE_IDENTITY -->";
166
+ }
167
+ });
168
+
169
+ // src/openclaw-config.ts
170
+ import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
171
+ import path3 from "path";
172
+ async function patchCoolclawAccountConfig(patch) {
173
+ const config = await readOpenClawConfig(patch.configPath);
174
+ const channels = ensureRecord(config, "channels");
175
+ const coolclaw = ensureRecord(channels, "coolclaw");
176
+ const accounts = ensureRecord(coolclaw, "accounts");
177
+ const account = {
178
+ name: `Riddle Agent ${patch.agentId}`,
179
+ enabled: true,
180
+ gatewayUrl: normalizeGatewayUrl(patch.gatewayUrl),
181
+ agentId: patch.agentId,
182
+ tokenSecretRef: patch.tokenSecretRef,
183
+ dmPolicy: patch.dmPolicy ?? "open"
184
+ };
185
+ const effectiveDmPolicy = account.dmPolicy;
186
+ if (patch.allowFrom && patch.allowFrom.length > 0) {
187
+ account.allowFrom = patch.allowFrom;
188
+ } else if (effectiveDmPolicy === "open") {
189
+ account.allowFrom = ["*"];
190
+ }
191
+ accounts.default = account;
192
+ await writeOpenClawConfig(patch.configPath, config);
193
+ }
194
+ async function readOpenClawConfig(configPath) {
195
+ try {
196
+ const parsed = JSON.parse(await readFile3(configPath, "utf8"));
197
+ return isRecord4(parsed) ? parsed : {};
198
+ } catch {
199
+ return {};
200
+ }
201
+ }
202
+ async function writeOpenClawConfig(configPath, config) {
203
+ await mkdir3(path3.dirname(configPath), { recursive: true });
204
+ const tmpFile = `${configPath}.${process.pid}.tmp`;
205
+ await writeFile3(tmpFile, `${JSON.stringify(config, null, 2)}
206
+ `);
207
+ await rename2(tmpFile, configPath);
208
+ }
209
+ function ensureRecord(parent, key) {
210
+ const existing = parent[key];
211
+ if (isRecord4(existing)) return existing;
212
+ const created = {};
213
+ parent[key] = created;
214
+ return created;
215
+ }
216
+ function isRecord4(value) {
217
+ return typeof value === "object" && value !== null && !Array.isArray(value);
218
+ }
219
+ var init_openclaw_config = __esm({
220
+ "src/openclaw-config.ts"() {
221
+ "use strict";
222
+ init_config();
223
+ }
224
+ });
225
+
226
+ // src/setup.ts
227
+ var setup_exports = {};
228
+ __export(setup_exports, {
229
+ registerAgent: () => registerAgent,
230
+ resolveSetupBinding: () => resolveSetupBinding,
231
+ runCoolclawSetup: () => runCoolclawSetup,
232
+ validateAgentToken: () => validateAgentToken
233
+ });
234
+ import { access } from "fs/promises";
235
+ import { readFile as readFile4 } from "fs/promises";
236
+ import path4 from "path";
237
+ import { homedir as homedir3 } from "os";
238
+ async function registerAgent(baseUrl, input) {
239
+ const res = await fetch(`${normalizeGatewayUrl(baseUrl)}/api/agent/register`, {
240
+ method: "POST",
241
+ headers: { "Content-Type": "application/json" },
242
+ body: JSON.stringify(input)
243
+ });
244
+ const body = await res.json();
245
+ if (!res.ok || body.code !== 200) {
246
+ throw new Error(body.message ?? `Agent register failed: ${res.status}`);
247
+ }
248
+ if (!body.data?.agentId || !body.data.token) {
249
+ throw new Error("Agent register response missing agentId or token");
250
+ }
251
+ return {
252
+ agentId: String(body.data.agentId),
253
+ token: String(body.data.token)
254
+ };
255
+ }
256
+ async function resolveSetupBinding(input) {
257
+ const gatewayUrl = normalizeGatewayUrl(input.gatewayUrl);
258
+ const existingAgentId = input.existingAgentId?.trim();
259
+ const existingTokenSecretRef = input.existingTokenSecretRef?.trim();
260
+ if (existingAgentId && existingTokenSecretRef) {
261
+ return {
262
+ mode: "reuse",
263
+ gatewayUrl,
264
+ agentId: existingAgentId,
265
+ tokenSecretRef: existingTokenSecretRef
266
+ };
267
+ }
268
+ if (!input.registration) {
269
+ throw new Error("CoolClaw setup requires an existing binding or registration input");
270
+ }
271
+ const registered = await registerAgent(gatewayUrl, input.registration);
272
+ return {
273
+ mode: "register",
274
+ gatewayUrl,
275
+ agentId: registered.agentId,
276
+ token: registered.token
277
+ };
278
+ }
279
+ async function validateAgentToken(baseUrl, token) {
280
+ const res = await fetch(`${normalizeGatewayUrl(baseUrl)}/api/users/me`, {
281
+ headers: { Authorization: `Bearer ${token}` }
282
+ });
283
+ if (!res.ok) return false;
284
+ const body = await res.json();
285
+ return body.code === 200 && body.data?.identityType === "AGENT";
286
+ }
287
+ async function runCoolclawSetup(options = {}) {
288
+ const gatewayUrl = normalizeGatewayUrl(options.gatewayUrl ?? "https://agits-xa.baidu.com/riddle");
289
+ const bindingFile = options.bindingFile ?? defaultBindingFile();
290
+ const openclawConfigPath = options.openclawConfigPath ?? defaultOpenClawConfigFile();
291
+ const existingBinding = await loadBinding(bindingFile);
292
+ const existingToken = await readTokenRef(existingBinding.tokenRef);
293
+ if (options.dryRun) {
294
+ return {
295
+ mode: "dry-run",
296
+ gatewayUrl,
297
+ agentId: existingBinding.agentId,
298
+ bindingFile,
299
+ openclawConfigPath,
300
+ tokenSavedTo: tokenFileFromBinding(existingBinding, bindingFile)
301
+ };
302
+ }
303
+ const canReuse = !options.forceRegister && existingBinding.agentId.length > 0 && Boolean(existingToken) && await validateAgentToken(gatewayUrl, existingToken);
304
+ const binding = canReuse ? existingBinding : await registerAndPersistBinding({
305
+ gatewayUrl,
306
+ bindingFile,
307
+ registration: await resolveRegistrationInput(
308
+ options.registration,
309
+ options.workspaceDir,
310
+ openclawConfigPath
311
+ )
312
+ });
313
+ const tokenFile = tokenFileFromBinding(binding, bindingFile);
314
+ if (!options.dryRun) {
315
+ await patchCoolclawAccountConfig({
316
+ configPath: openclawConfigPath,
317
+ gatewayUrl,
318
+ agentId: binding.agentId,
319
+ tokenSecretRef: binding.tokenRef ?? `file://${tokenFile}`,
320
+ allowFrom: options.allowFrom,
321
+ dmPolicy: options.dmPolicy
322
+ });
323
+ if (options.workspaceDir) {
324
+ await updateRiddleIdentitySummary({
325
+ workspaceDir: options.workspaceDir,
326
+ agentId: binding.agentId,
327
+ gatewayUrl,
328
+ bindingFile,
329
+ tokenRef: binding.tokenRef ?? void 0
330
+ });
331
+ }
332
+ if (options.autoRestart !== false) {
333
+ try {
334
+ const { execSync } = await import("child_process");
335
+ console.log("[coolclaw] Restarting gateway...");
336
+ execSync("openclaw gateway restart", { stdio: "inherit" });
337
+ console.log("[coolclaw] Gateway restarted.");
338
+ } catch (err) {
339
+ console.warn(
340
+ `[coolclaw] Could not auto-restart gateway. Please run: openclaw gateway restart`
341
+ );
342
+ }
343
+ }
344
+ }
345
+ return {
346
+ mode: options.dryRun ? "dry-run" : canReuse ? "reuse" : "register",
347
+ gatewayUrl,
348
+ agentId: binding.agentId,
349
+ bindingFile,
350
+ openclawConfigPath,
351
+ tokenSavedTo: tokenFile
352
+ };
353
+ }
354
+ async function registerAndPersistBinding(input) {
355
+ const registered = await registerAgent(input.gatewayUrl, input.registration);
356
+ const valid = await validateAgentToken(input.gatewayUrl, registered.token);
357
+ if (!valid) {
358
+ throw new Error("Newly registered Riddle token did not validate as an AGENT token");
359
+ }
360
+ const tokenFile = defaultTokenFile(input.bindingFile, registered.agentId);
361
+ await saveAgentToken(tokenFile, registered.token);
362
+ const binding = touchBinding({
363
+ agentId: registered.agentId,
364
+ tokenRef: `file://${tokenFile}`,
365
+ runtimeType: "openclaw",
366
+ lastAckedSeq: 0
367
+ });
368
+ await saveBinding(input.bindingFile, binding);
369
+ return binding;
370
+ }
371
+ function tokenFileFromBinding(binding, bindingFile) {
372
+ if (binding.tokenRef?.startsWith("file://")) {
373
+ return binding.tokenRef.slice("file://".length);
374
+ }
375
+ return defaultTokenFile(bindingFile, binding.agentId);
376
+ }
377
+ function parseIdentityMarkdown(content) {
378
+ const result = {};
379
+ for (const line of content.split(/\r?\n/)) {
380
+ const cleaned = line.trim().replace(/^\s*-\s*/, "");
381
+ const colonIdx = cleaned.indexOf(":");
382
+ if (colonIdx === -1) continue;
383
+ const label = cleaned.slice(0, colonIdx).replace(/[*_]/g, "").trim().toLowerCase();
384
+ const value = cleaned.slice(colonIdx + 1).replace(/^[*_]+|[*_]+$/g, "").trim();
385
+ if (!value || IDENTITY_PLACEHOLDERS.has(value.toLowerCase().replace(/[()]/g, "").trim())) continue;
386
+ if (label === "name") result.name = value;
387
+ if (label === "creature") result.creature = value;
388
+ if (label === "vibe") result.vibe = value;
389
+ if (label === "theme") result.theme = value;
390
+ }
391
+ return result;
392
+ }
393
+ async function readWorkspaceDirFromOpenclawConfig(configPath) {
394
+ try {
395
+ const content = await readFile4(configPath, "utf-8");
396
+ const config = JSON.parse(content);
397
+ const workspace = config.agents?.defaults?.workspace;
398
+ if (typeof workspace === "string" && workspace.trim()) {
399
+ return workspace.trim();
400
+ }
401
+ } catch {
402
+ }
403
+ const defaultWorkspace = path4.join(homedir3(), ".openclaw", "workspace");
404
+ try {
405
+ await access(defaultWorkspace);
406
+ return defaultWorkspace;
407
+ } catch {
408
+ return void 0;
409
+ }
410
+ }
411
+ async function readIdentityFromWorkspace(workspaceDir) {
412
+ try {
413
+ const content = await readFile4(path4.join(workspaceDir, "IDENTITY.md"), "utf-8");
414
+ return parseIdentityMarkdown(content);
415
+ } catch {
416
+ return {};
417
+ }
418
+ }
419
+ async function readIdentityFromOpenclawConfig(configPath) {
420
+ try {
421
+ const content = await readFile4(configPath, "utf-8");
422
+ const config = JSON.parse(content);
423
+ const defaultsName = config.agents?.defaults?.identity?.name || config.agents?.defaults?.name;
424
+ if (defaultsName) return { name: defaultsName };
425
+ const mainAgent = config.agents?.list?.find((a) => a.id === "main") || config.agents?.list?.[0];
426
+ if (mainAgent?.identity?.name || mainAgent?.name) {
427
+ return { name: mainAgent.identity?.name || mainAgent.name };
428
+ }
429
+ return {};
430
+ } catch {
431
+ return {};
432
+ }
433
+ }
434
+ async function resolveRegistrationInput(explicitRegistration, workspaceDir, openclawConfigPath) {
435
+ if (explicitRegistration?.name && explicitRegistration?.bio && explicitRegistration.name !== "CoolClaw Agent" && explicitRegistration.bio !== "OpenClaw agent connected through CoolClaw channel.") {
436
+ return explicitRegistration;
437
+ }
438
+ let identityName;
439
+ let identityBio;
440
+ const resolvedWorkspaceDir = workspaceDir ?? await readWorkspaceDirFromOpenclawConfig(openclawConfigPath);
441
+ if (resolvedWorkspaceDir) {
442
+ const identity = await readIdentityFromWorkspace(resolvedWorkspaceDir);
443
+ if (identity.name) {
444
+ identityName = identity.name;
445
+ }
446
+ const bioParts = [identity.creature, identity.vibe, identity.theme].filter(Boolean);
447
+ if (bioParts.length > 0) {
448
+ identityBio = bioParts.join(". ") + ".";
449
+ }
450
+ }
451
+ if (!identityName) {
452
+ const configIdentity = await readIdentityFromOpenclawConfig(openclawConfigPath);
453
+ identityName = configIdentity.name;
454
+ }
455
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(4, 12);
456
+ return {
457
+ name: explicitRegistration?.name && explicitRegistration.name !== "CoolClaw Agent" ? explicitRegistration.name : identityName ?? `RiddleAgent-${stamp}`,
458
+ bio: explicitRegistration?.bio && explicitRegistration.bio !== "OpenClaw agent connected through CoolClaw channel." ? explicitRegistration.bio : identityBio ?? "OpenClaw agent connected through CoolClaw channel.",
459
+ tags: explicitRegistration?.tags ?? JSON.stringify(["assistant", "openclaw", "coolclaw"])
460
+ };
461
+ }
462
+ var IDENTITY_PLACEHOLDERS;
463
+ var init_setup = __esm({
464
+ "src/setup.ts"() {
465
+ "use strict";
466
+ init_config();
467
+ init_binding();
468
+ init_identity();
469
+ init_openclaw_config();
470
+ IDENTITY_PLACEHOLDERS = /* @__PURE__ */ new Set([
471
+ "pick something you like",
472
+ "ai? robot? familiar? ghost in the machine? something weirder?",
473
+ "how do you come across? sharp? warm? chaotic? calm?",
474
+ "your signature - pick one that feels right",
475
+ "workspace-relative path, http(s) url, or data uri"
476
+ ]);
477
+ }
478
+ });
479
+
480
+ // setup-entry.ts
481
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
482
+
1
483
  // src/ack-store.ts
2
484
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
485
  import { join } from "path";
@@ -68,151 +550,8 @@ var FileAckStore = class {
68
550
  }
69
551
  };
70
552
 
71
- // src/binding.ts
72
- import { mkdir, readFile, rename, writeFile, chmod } from "fs/promises";
73
- import { homedir as homedir2 } from "os";
74
- import path from "path";
75
- import { fileURLToPath } from "url";
76
- async function readTokenRef(tokenRef) {
77
- if (!tokenRef) return void 0;
78
- if (tokenRef.startsWith("env:")) {
79
- return process.env[tokenRef.slice("env:".length)] || void 0;
80
- }
81
- if (tokenRef.startsWith("file://")) {
82
- const tokenPath = fileURLToPath(tokenRef);
83
- return (await readFile(tokenPath, "utf8")).trim() || void 0;
84
- }
85
- return void 0;
86
- }
87
-
88
- // src/config.ts
89
- function normalizeGatewayUrl(value) {
90
- return value.trim().replace(/\/+$/, "");
91
- }
92
- function buildWsUrl(gatewayUrl, lastAckedSeq) {
93
- const baseUrl = normalizeGatewayUrl(gatewayUrl).replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
94
- return `${baseUrl}/ws/channel?lastAckedSeq=${lastAckedSeq}`;
95
- }
96
- function resolveAccountConfig(source, env = process.env) {
97
- const account = readDefaultAccount(source);
98
- if (account) {
99
- return finalizeAccount(account, "config");
100
- }
101
- const envAccount = readEnvAccount(env);
102
- if (envAccount) {
103
- return finalizeAccount(envAccount, "env");
104
- }
105
- return {
106
- configured: false,
107
- source: "none",
108
- reasons: ["Missing CoolClaw account config"]
109
- };
110
- }
111
- function inspectAccount(account) {
112
- const config = account.config;
113
- return JSON.stringify({
114
- configured: account.configured,
115
- source: account.source,
116
- gatewayUrl: config?.gatewayUrl,
117
- agentId: config?.agentId,
118
- tokenConfigured: Boolean(config?.tokenSecretRef),
119
- tokenSecretRef: maskSecretRef(config?.tokenSecretRef),
120
- allowFromCount: config?.allowFrom?.length ?? 0,
121
- dmPolicy: config?.dmPolicy,
122
- reasons: account.reasons
123
- });
124
- }
125
- async function resolveAccountToken(account) {
126
- if (account.tokenSecret) return account.tokenSecret;
127
- return readTokenRef(account.tokenSecretRef);
128
- }
129
- function readDefaultAccount(source) {
130
- if (!isRecord(source)) return void 0;
131
- const channels = source.channels;
132
- if (!isRecord(channels)) return void 0;
133
- const coolclaw = channels.coolclaw;
134
- if (!isRecord(coolclaw)) return void 0;
135
- const accounts = coolclaw.accounts;
136
- if (!isRecord(accounts)) return void 0;
137
- const defaultAccount = accounts.default;
138
- return isRecord(defaultAccount) ? coerceRawAccount(defaultAccount) : void 0;
139
- }
140
- function readEnvAccount(env) {
141
- if (!env.COOLCLAW_GATEWAY_URL && !env.COOLCLAW_AGENT_ID && !env.COOLCLAW_AGENT_TOKEN && !env.COOLCLAW_AGENT_TOKEN_SECRET_REF) {
142
- return void 0;
143
- }
144
- return {
145
- gatewayUrl: env.COOLCLAW_GATEWAY_URL,
146
- agentId: env.COOLCLAW_AGENT_ID,
147
- tokenSecretRef: env.COOLCLAW_AGENT_TOKEN_SECRET_REF ?? (env.COOLCLAW_AGENT_TOKEN ? "env:COOLCLAW_AGENT_TOKEN" : void 0),
148
- allowFrom: splitCsv(env.COOLCLAW_ALLOW_FROM),
149
- dmPolicy: coerceDmPolicy(env.COOLCLAW_DM_POLICY)
150
- };
151
- }
152
- function finalizeAccount(account, source) {
153
- const gatewayUrl = typeof account.gatewayUrl === "string" ? normalizeGatewayUrl(account.gatewayUrl) : "";
154
- const agentId = typeof account.agentId === "string" ? account.agentId.trim() : "";
155
- const tokenSecretRef = typeof account.tokenSecretRef === "string" ? account.tokenSecretRef.trim() : account.tokenSecret?.trim();
156
- const allowFrom = normalizeStringArray(account.allowFrom);
157
- const dmPolicy = coerceDmPolicy(account.dmPolicy) ?? "open";
158
- const reasons = [];
159
- if (!gatewayUrl) reasons.push("Missing gateway URL");
160
- if (!agentId) reasons.push("Missing Agent ID");
161
- if (!tokenSecretRef) reasons.push("Missing token secret reference");
162
- const config = {
163
- gatewayUrl,
164
- agentId,
165
- tokenSecretRef,
166
- allowFrom,
167
- dmPolicy
168
- };
169
- if (reasons.length > 0) {
170
- return {
171
- configured: false,
172
- source,
173
- config,
174
- reasons
175
- };
176
- }
177
- return {
178
- configured: true,
179
- source,
180
- config,
181
- reasons: []
182
- };
183
- }
184
- function coerceRawAccount(value) {
185
- return {
186
- gatewayUrl: typeof value.gatewayUrl === "string" ? value.gatewayUrl : void 0,
187
- agentId: typeof value.agentId === "string" ? value.agentId : void 0,
188
- tokenSecretRef: typeof value.tokenSecretRef === "string" ? value.tokenSecretRef : void 0,
189
- tokenSecret: typeof value.tokenSecret === "string" ? value.tokenSecret : void 0,
190
- allowFrom: normalizeStringArray(value.allowFrom),
191
- dmPolicy: coerceDmPolicy(value.dmPolicy)
192
- };
193
- }
194
- function normalizeStringArray(value) {
195
- if (!Array.isArray(value)) return void 0;
196
- const items = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
197
- return items.length > 0 ? items : void 0;
198
- }
199
- function splitCsv(value) {
200
- if (!value) return void 0;
201
- const items = value.split(",").map((item) => item.trim()).filter(Boolean);
202
- return items.length > 0 ? items : void 0;
203
- }
204
- function coerceDmPolicy(value) {
205
- return value === "pairing" || value === "allowlist" || value === "open" ? value : void 0;
206
- }
207
- function maskSecretRef(value) {
208
- if (!value) return void 0;
209
- if (value.startsWith("env:")) return value;
210
- const tail = value.slice(-4);
211
- return tail ? `***${tail}` : "***";
212
- }
213
- function isRecord(value) {
214
- return typeof value === "object" && value !== null;
215
- }
553
+ // src/channel.ts
554
+ init_config();
216
555
 
217
556
  // src/frame-codec.ts
218
557
  import { randomUUID } from "crypto";
@@ -241,7 +580,7 @@ function decodeFrame(raw) {
241
580
  } catch (error) {
242
581
  throw new CoolclawFrameDecodeError(`Invalid CoolClaw frame JSON: ${error.message}`);
243
582
  }
244
- if (!isRecord2(value)) {
583
+ if (!isRecord(value)) {
245
584
  throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: expected object");
246
585
  }
247
586
  if (!("v" in value)) {
@@ -276,67 +615,10 @@ function decodeFrame(raw) {
276
615
  }
277
616
  return frame;
278
617
  }
279
- function isRecord2(value) {
618
+ function isRecord(value) {
280
619
  return typeof value === "object" && value !== null;
281
620
  }
282
621
 
283
- // src/security.ts
284
- function applyInboundSecurityPolicy(envelope, config) {
285
- if (!isPrivateConversation(envelope)) {
286
- return {
287
- accepted: true,
288
- envelope: {
289
- ...envelope,
290
- shouldReply: envelope.group ? envelope.shouldReply === true : false
291
- }
292
- };
293
- }
294
- const senderKey = envelope.sender ? toIdentityKey(envelope.sender.userType, envelope.sender.userId) : void 0;
295
- const allowFrom = new Set((config.allowFrom ?? []).map(normalizeIdentityKey));
296
- if (senderKey && allowFrom.has(senderKey)) {
297
- return { accepted: true, envelope };
298
- }
299
- if ((config.dmPolicy ?? "allowlist") === "open") {
300
- return { accepted: true, envelope };
301
- }
302
- if ((config.dmPolicy ?? "allowlist") === "pairing" && envelope.sender) {
303
- return {
304
- accepted: true,
305
- envelope: {
306
- ...envelope,
307
- conversationId: `pairing:${senderKey}`,
308
- shouldReply: false,
309
- metadata: {
310
- ...envelope.metadata,
311
- pairingRequired: true,
312
- originalConversationId: envelope.conversationId
313
- }
314
- }
315
- };
316
- }
317
- return {
318
- accepted: false,
319
- reason: "DM sender is not allowlisted",
320
- envelope: {
321
- ...envelope,
322
- shouldReply: false,
323
- metadata: {
324
- ...envelope.metadata,
325
- blockedByPolicy: true
326
- }
327
- }
328
- };
329
- }
330
- function isPrivateConversation(envelope) {
331
- return envelope.conversationId.startsWith("private:");
332
- }
333
- function toIdentityKey(userType, userId) {
334
- return `${userType.toLowerCase()}:${userId}`;
335
- }
336
- function normalizeIdentityKey(value) {
337
- return value.trim().toLowerCase();
338
- }
339
-
340
622
  // src/inbound.ts
341
623
  function mapInboundFrame(frame) {
342
624
  if (frame.type === "PRIVATE_MESSAGE") {
@@ -348,7 +630,8 @@ function mapInboundFrame(frame) {
348
630
  text: payload.content,
349
631
  messageType: payload.messageType,
350
632
  seq: payload.seq,
351
- shouldReply: payload.mentioned,
633
+ shouldReply: true,
634
+ // 私聊本身就是与 Bot 对话的意图
352
635
  sender: payload.sender,
353
636
  recipient: payload.recipient,
354
637
  metadata: {
@@ -368,6 +651,7 @@ function mapInboundFrame(frame) {
368
651
  messageType: payload.messageType,
369
652
  seq: payload.seq,
370
653
  shouldReply: payload.mentioned,
654
+ // 群聊仅在 @ 时回复
371
655
  sender: payload.sender,
372
656
  group: {
373
657
  groupId: payload.groupId,
@@ -387,31 +671,45 @@ function mapInboundFrame(frame) {
387
671
  throw new Error(`Unsupported inbound CoolClaw frame type: ${frame.type}`);
388
672
  }
389
673
  async function handleInboundFrame(input) {
390
- const mappedEnvelope = mapInboundFrame(input.frame);
391
- const decision = input.accountConfig ? applyInboundSecurityPolicy(mappedEnvelope, input.accountConfig) : { accepted: true, envelope: mappedEnvelope };
392
- const envelope = decision.envelope;
674
+ const envelope = mapInboundFrame(input.frame);
675
+ if (!envelope) return;
393
676
  await ackProcessedSeq(input, envelope);
394
- if (!decision.accepted) {
395
- return;
396
- }
397
677
  await input.dispatch(envelope);
398
678
  }
399
679
  function mapNotificationFrame(frame) {
400
- const payload = isRecord3(frame.payload) ? frame.payload : {};
680
+ const payload = isRecord2(frame.payload) ? frame.payload : {};
401
681
  const seq = typeof payload.seq === "number" ? payload.seq : void 0;
402
- return {
403
- id: frame.id,
404
- channel: "coolclaw",
405
- conversationId: frame.type === "SYSTEM_NOTIFICATION" ? "notification:system" : `notification:${frame.type.toLowerCase()}`,
406
- text: JSON.stringify(frame.payload ?? {}),
407
- messageType: frame.type,
408
- seq,
409
- shouldReply: false,
410
- metadata: {
411
- sourceFrameId: frame.id,
412
- payload: frame.payload
413
- }
414
- };
682
+ if (frame.type === "SYSTEM_NOTIFICATION") {
683
+ const title = typeof payload.title === "string" ? payload.title : "System";
684
+ const content = typeof payload.content === "string" ? payload.content : JSON.stringify(payload);
685
+ return {
686
+ id: frame.id,
687
+ channel: "coolclaw",
688
+ conversationId: "notification:system",
689
+ text: `[\u7CFB\u7EDF\u901A\u77E5] ${title}: ${content}`,
690
+ messageType: frame.type,
691
+ seq,
692
+ shouldReply: false,
693
+ metadata: { sourceFrameId: frame.id, payload: frame.payload }
694
+ };
695
+ }
696
+ if (frame.type === "GAME_EVENT") {
697
+ return null;
698
+ }
699
+ if (frame.type === "CONTENT_TASK") {
700
+ const taskType = typeof payload.taskType === "string" ? payload.taskType : "unknown";
701
+ return {
702
+ id: frame.id,
703
+ channel: "coolclaw",
704
+ conversationId: "notification:content_task",
705
+ text: `[\u5185\u5BB9\u4EFB\u52A1] ${taskType}: ${JSON.stringify(payload)}`,
706
+ messageType: frame.type,
707
+ seq,
708
+ shouldReply: false,
709
+ metadata: { sourceFrameId: frame.id, payload: frame.payload }
710
+ };
711
+ }
712
+ return null;
415
713
  }
416
714
  async function ackProcessedSeq(input, envelope) {
417
715
  if (typeof envelope.seq === "number") {
@@ -420,7 +718,7 @@ async function ackProcessedSeq(input, envelope) {
420
718
  }
421
719
  }
422
720
  function assertPrivatePayload(value) {
423
- if (!isRecord3(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
721
+ if (!isRecord2(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
424
722
  throw new Error("Invalid PRIVATE_MESSAGE payload");
425
723
  }
426
724
  return {
@@ -436,7 +734,7 @@ function assertPrivatePayload(value) {
436
734
  };
437
735
  }
438
736
  function assertGroupPayload(value) {
439
- if (!isRecord3(value) || !isUserRef(value.sender)) {
737
+ if (!isRecord2(value) || !isUserRef(value.sender)) {
440
738
  throw new Error("Invalid GROUP_MESSAGE payload");
441
739
  }
442
740
  return {
@@ -485,9 +783,9 @@ function readOptionalString(source, key) {
485
783
  return value;
486
784
  }
487
785
  function isUserRef(value) {
488
- return isRecord3(value) && typeof value.userId === "string" && (value.userType === "HUMAN" || value.userType === "AGENT") && (value.displayName === void 0 || typeof value.displayName === "string");
786
+ return isRecord2(value) && typeof value.userId === "string" && (value.userType === "HUMAN" || value.userType === "AGENT") && (value.displayName === void 0 || typeof value.displayName === "string");
489
787
  }
490
- function isRecord3(value) {
788
+ function isRecord2(value) {
491
789
  return typeof value === "object" && value !== null;
492
790
  }
493
791
 
@@ -584,21 +882,34 @@ async function sendText(input) {
584
882
  }
585
883
  return response.messageId;
586
884
  }
587
-
588
- // src/status.ts
589
- function getCoolclawStatus(source) {
590
- return createStatus(resolveAccountConfig(source ?? {}));
591
- }
592
- function createStatus(account) {
593
- return {
594
- configured: account.configured,
595
- connected: false,
596
- message: account.configured ? "CoolClaw account is configured." : account.reasons.join("; "),
597
- diagnostics: inspectAccount(account)
598
- };
885
+ async function sendMedia(input) {
886
+ const target = parseCoolclawTarget(input.target);
887
+ const fs = await import("fs/promises");
888
+ const fileBuffer = await fs.readFile(input.filePath);
889
+ const base64Content = fileBuffer.toString("base64");
890
+ const frame = target.kind === "private" ? createFrame("SEND_PRIVATE_MEDIA", {
891
+ target: { userId: target.userId, userType: target.userType },
892
+ messageType: "IMAGE",
893
+ content: base64Content,
894
+ mimeType: input.mimeType ?? "image/png"
895
+ }) : createFrame("SEND_GROUP_MEDIA", {
896
+ groupId: target.groupId,
897
+ messageType: "IMAGE",
898
+ content: base64Content,
899
+ mimeType: input.mimeType ?? "image/png"
900
+ });
901
+ const response = await input.client.request(frame);
902
+ if (response.ok === false) {
903
+ throw new Error(response.error?.message ?? "CoolClaw media send failed");
904
+ }
905
+ if (!response.messageId) {
906
+ throw new Error("CoolClaw media send response missing messageId");
907
+ }
908
+ return response.messageId;
599
909
  }
600
910
 
601
911
  // src/ws-client.ts
912
+ init_config();
602
913
  import WebSocket from "ws";
603
914
  var CoolclawWsClient = class {
604
915
  constructor(options) {
@@ -608,6 +919,7 @@ var CoolclawWsClient = class {
608
919
  heartbeatTimer;
609
920
  reconnectTimer;
610
921
  stopped = true;
922
+ reconnectAttempt = 0;
611
923
  pendingRequests = /* @__PURE__ */ new Map();
612
924
  async start() {
613
925
  this.stopped = false;
@@ -652,7 +964,11 @@ var CoolclawWsClient = class {
652
964
  }
653
965
  this.socket.send(encodeFrame(frame));
654
966
  }
967
+ isConnected() {
968
+ return this.socket?.readyState === WebSocket.OPEN;
969
+ }
655
970
  async connect() {
971
+ this.notifyState("connecting");
656
972
  const lastAckedSeq = await this.options.ackStore.getLastAckedSeq(this.options.accountKey);
657
973
  const socket = new WebSocket(buildWsUrl(this.options.gatewayUrl, lastAckedSeq), {
658
974
  headers: {
@@ -688,6 +1004,7 @@ var CoolclawWsClient = class {
688
1004
  });
689
1005
  if (frame.type === "HELLO") {
690
1006
  helloReceived = true;
1007
+ this.reconnectAttempt = 0;
691
1008
  cleanupBeforeHello();
692
1009
  socket.on("message", (nextData) => {
693
1010
  this.handleRawMessage(nextData).catch((error) => {
@@ -696,6 +1013,7 @@ var CoolclawWsClient = class {
696
1013
  });
697
1014
  socket.on("close", (code) => this.handleClose(code));
698
1015
  this.startHeartbeat(frame);
1016
+ this.notifyState("connected");
699
1017
  resolve();
700
1018
  }
701
1019
  };
@@ -753,6 +1071,7 @@ var CoolclawWsClient = class {
753
1071
  handleClose(code) {
754
1072
  this.clearHeartbeat();
755
1073
  this.rejectPending(new Error(`CoolClaw WSS connection closed: ${code}`));
1074
+ this.notifyState("disconnected");
756
1075
  if (this.stopped || this.isTerminalClose(code)) {
757
1076
  return;
758
1077
  }
@@ -760,17 +1079,23 @@ var CoolclawWsClient = class {
760
1079
  }
761
1080
  scheduleReconnect() {
762
1081
  this.clearReconnect();
763
- const delayMs = this.options.reconnectDelayMs ?? 1e3;
1082
+ this.notifyState("reconnecting");
1083
+ const baseDelay = this.options.reconnectDelayMs ?? 1e3;
1084
+ const maxDelay = 6e4;
1085
+ const attempt = this.reconnectAttempt++;
1086
+ const delayMs = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
764
1087
  this.reconnectTimer = setTimeout(() => {
765
1088
  if (this.stopped) return;
766
1089
  this.connect().catch((error) => {
767
1090
  if (!this.stopped) {
768
- this.rejectPending(error instanceof Error ? error : new Error(String(error)));
769
1091
  this.scheduleReconnect();
770
1092
  }
771
1093
  });
772
1094
  }, delayMs);
773
1095
  }
1096
+ notifyState(state) {
1097
+ this.options.onStateChange?.(state);
1098
+ }
774
1099
  clearHeartbeat() {
775
1100
  if (this.heartbeatTimer) {
776
1101
  clearInterval(this.heartbeatTimer);
@@ -795,321 +1120,473 @@ var CoolclawWsClient = class {
795
1120
  }
796
1121
  };
797
1122
  function readPingInterval(payload) {
798
- if (!isRecord4(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1123
+ if (!isRecord3(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
799
1124
  return void 0;
800
1125
  }
801
1126
  return payload.pingIntervalMs;
802
1127
  }
803
1128
  function readErrorMessage(payload) {
804
- if (isRecord4(payload) && typeof payload.message === "string") {
1129
+ if (isRecord3(payload) && typeof payload.message === "string") {
805
1130
  return payload.message;
806
1131
  }
807
1132
  return "CoolClaw request failed";
808
1133
  }
809
- function isRecord4(value) {
1134
+ function isRecord3(value) {
810
1135
  return typeof value === "object" && value !== null;
811
1136
  }
812
1137
 
1138
+ // src/runtime.ts
1139
+ var _runtime;
1140
+ function getCoolclawRuntime() {
1141
+ return _runtime;
1142
+ }
1143
+
1144
+ // src/version.ts
1145
+ import { readFileSync as readFileSync2 } from "fs";
1146
+ import { join as join2, dirname } from "path";
1147
+ import { fileURLToPath as fileURLToPath2 } from "url";
1148
+ var _version;
1149
+ function getPluginVersion() {
1150
+ if (_version) return _version;
1151
+ try {
1152
+ const pkgPath = join2(dirname(fileURLToPath2(import.meta.url)), "..", "package.json");
1153
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
1154
+ _version = pkg.version ?? "0.0.0";
1155
+ } catch {
1156
+ _version = "0.0.0";
1157
+ }
1158
+ return _version;
1159
+ }
1160
+
813
1161
  // src/channel.ts
814
- var coolclawChannelPlugin = {
815
- id: "coolclaw",
816
- meta: {
817
- id: "coolclaw",
818
- label: "CoolClaw",
819
- selectionLabel: "CoolClaw",
820
- docsPath: "/plugins/coolclaw",
821
- blurb: "Connect OpenClaw to the CoolClaw/Riddle chat platform."
822
- },
823
- channels: ["coolclaw"],
824
- channel: {
1162
+ import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
1163
+ import { createAccountStatusSink, runPassiveAccountLifecycle } from "openclaw/plugin-sdk/channel-lifecycle";
1164
+ import { logInboundDrop, logAckFailure } from "openclaw/plugin-sdk/channel-logging";
1165
+ var runtimeClients = /* @__PURE__ */ new Map();
1166
+ function setRuntimeClient(accountKey, client) {
1167
+ runtimeClients.set(accountKey, client);
1168
+ }
1169
+ function getRuntimeClient(accountKey) {
1170
+ return runtimeClients.get(accountKey);
1171
+ }
1172
+ function clearRuntimeClient(accountKey) {
1173
+ runtimeClients.delete(accountKey);
1174
+ }
1175
+ function extractAccountFromConfig(cfg, accountId) {
1176
+ const coolclawSection = cfg.channels?.coolclaw;
1177
+ const accounts = coolclawSection?.accounts;
1178
+ return accounts?.[accountId ?? "default"] ?? {};
1179
+ }
1180
+ var coolclawChannelPlugin = createChatChannelPlugin({
1181
+ base: {
825
1182
  id: "coolclaw",
826
- label: "CoolClaw",
827
- docsPath: "/plugins/coolclaw",
828
- blurb: "Connect OpenClaw to the CoolClaw/Riddle chat platform."
829
- },
830
- capabilities: {
831
- chatTypes: ["direct", "group"]
832
- },
833
- config: {
834
- listAccountIds(cfg) {
835
- return Object.keys(cfg.channels?.coolclaw?.accounts ?? {});
1183
+ meta: {
1184
+ id: "coolclaw",
1185
+ label: "CoolClaw",
1186
+ selectionLabel: "CoolClaw",
1187
+ docsPath: "/plugins/coolclaw",
1188
+ blurb: "Connect OpenClaw to the CoolClaw/Riddle chat platform."
836
1189
  },
837
- resolveAccount(cfg, accountId = "default") {
838
- return cfg.channels?.coolclaw?.accounts?.[accountId ?? "default"] ?? {};
1190
+ capabilities: {
1191
+ chatTypes: ["direct", "group"],
1192
+ media: true,
1193
+ blockStreaming: true
839
1194
  },
840
- defaultAccountId() {
841
- return "default";
1195
+ streaming: {
1196
+ blockStreamingCoalesceDefaults: {
1197
+ minChars: 200,
1198
+ idleMs: 3e3
1199
+ }
1200
+ },
1201
+ agentPrompt: {
1202
+ messageToolHints: () => [
1203
+ "To send a message on CoolClaw/Riddle, use the message tool with action='send' and set 'to' to a CoolClaw target like 'coolclaw:human:<userId>', 'coolclaw:agent:<agentId>', or 'coolclaw:group:<groupId>'.",
1204
+ "To send an image or file, use the message tool with action='send' and set 'media' to a local file path or a remote URL.",
1205
+ "When sending a message to a CoolClaw group, the agent will only reply if it was mentioned in the group message.",
1206
+ "When creating a cron job for CoolClaw, set delivery.to to the target CoolClaw ID and delivery.accountId to the current accountId."
1207
+ ]
842
1208
  },
843
- isConfigured(account) {
844
- return Boolean(account.gatewayUrl && account.agentId && (account.tokenSecretRef || "tokenSecret" in account));
1209
+ config: {
1210
+ listAccountIds(cfg) {
1211
+ return Object.keys(cfg.channels?.coolclaw?.accounts ?? {});
1212
+ },
1213
+ resolveAccount(cfg, accountId) {
1214
+ return extractAccountFromConfig(cfg, accountId);
1215
+ },
1216
+ defaultAccountId() {
1217
+ return "default";
1218
+ },
1219
+ isConfigured(account) {
1220
+ return Boolean(account.gatewayUrl && account.agentId && (account.tokenSecretRef || "tokenSecret" in account));
1221
+ },
1222
+ isEnabled(account) {
1223
+ return account?.enabled !== false;
1224
+ },
1225
+ describeAccount(account) {
1226
+ return {
1227
+ accountId: "default",
1228
+ name: account.name,
1229
+ enabled: account.enabled !== false,
1230
+ configured: Boolean(account.gatewayUrl && account.agentId && (account.tokenSecretRef || "tokenSecret" in account)),
1231
+ gatewayUrl: account.gatewayUrl,
1232
+ agentId: account.agentId,
1233
+ tokenConfigured: Boolean(account.tokenSecretRef || "tokenSecret" in account),
1234
+ allowFromCount: account.allowFrom?.length ?? 0,
1235
+ dmPolicy: account.dmPolicy ?? "allowlist"
1236
+ };
1237
+ }
845
1238
  },
846
- isEnabled(account) {
847
- return account?.enabled !== false;
1239
+ resolver: {
1240
+ async resolveTargets({ inputs }) {
1241
+ return inputs.map((input) => {
1242
+ try {
1243
+ const normalized = normalizeCoolclawTarget(input);
1244
+ const [, type, id] = normalized.split(":");
1245
+ parseCoolclawTarget(normalized);
1246
+ return { input, resolved: true, id: normalized, name: `${type}:${id}` };
1247
+ } catch (error) {
1248
+ return { input, resolved: false, note: error instanceof Error ? error.message : String(error) };
1249
+ }
1250
+ });
1251
+ }
848
1252
  },
849
- describeAccount(account) {
850
- return {
851
- accountId: "default",
852
- name: account.name,
853
- enabled: account.enabled !== false,
854
- configured: Boolean(account.gatewayUrl && account.agentId && (account.tokenSecretRef || "tokenSecret" in account)),
855
- gatewayUrl: account.gatewayUrl,
856
- agentId: account.agentId,
857
- tokenConfigured: Boolean(account.tokenSecretRef || "tokenSecret" in account),
858
- allowFromCount: account.allowFrom?.length ?? 0,
859
- dmPolicy: account.dmPolicy ?? "allowlist"
860
- };
861
- }
862
- },
863
- resolver: {
864
- async resolveTargets({ inputs }) {
865
- return inputs.map((input) => {
1253
+ messaging: {
1254
+ normalizeTarget(raw) {
866
1255
  try {
867
- const normalized = normalizeCoolclawTarget(input);
868
- const [, type, id] = normalized.split(":");
1256
+ const normalized = normalizeCoolclawTarget(raw);
869
1257
  parseCoolclawTarget(normalized);
870
- return { input, resolved: true, id: normalized, name: `${type}:${id}` };
871
- } catch (error) {
872
- return { input, resolved: false, note: error instanceof Error ? error.message : String(error) };
1258
+ return normalized;
1259
+ } catch {
1260
+ return void 0;
873
1261
  }
874
- });
875
- }
876
- },
877
- messaging: {
878
- normalizeTarget(raw) {
879
- try {
880
- const normalized = normalizeCoolclawTarget(raw);
881
- parseCoolclawTarget(normalized);
882
- return normalized;
883
- } catch {
884
- return void 0;
885
- }
886
- },
887
- inferTargetChatType({ to }) {
888
- return inferCoolclawTargetChatType(to);
889
- },
890
- targetResolver: {
891
- hint: "Use coolclaw:human:<id>, coolclaw:agent:<id>, or coolclaw:group:<id>.",
892
- looksLikeId(raw, normalized) {
893
- return isCoolclawTargetId(raw, normalized);
894
1262
  },
895
- resolveTarget({ input, normalized, preferredKind }) {
896
- return resolveCoolclawMessagingTarget(normalized || input, preferredKind);
897
- }
898
- }
899
- },
900
- outbound: {
901
- deliveryMode: "direct",
902
- resolveTarget({ to }) {
903
- if (!to) {
904
- return { ok: false, error: new Error("CoolClaw target is required") };
905
- }
906
- try {
907
- const normalized = normalizeCoolclawTarget(to);
908
- parseCoolclawTarget(normalized);
909
- return { ok: true, to: normalized };
910
- } catch (error) {
911
- return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
1263
+ inferTargetChatType({ to }) {
1264
+ return inferCoolclawTargetChatType(to);
1265
+ },
1266
+ targetResolver: {
1267
+ hint: "Use coolclaw:human:<id>, coolclaw:agent:<id>, or coolclaw:group:<id>.",
1268
+ looksLikeId(raw, normalized) {
1269
+ return isCoolclawTargetId(raw, normalized);
1270
+ },
1271
+ resolveTarget({ input, normalized, preferredKind }) {
1272
+ return resolveCoolclawMessagingTarget(normalized || input, preferredKind);
1273
+ }
912
1274
  }
913
1275
  },
914
- async sendText(ctx) {
915
- const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
916
- const token = await resolveAccountToken(account);
917
- if (!account.gatewayUrl || !account.agentId || !token) {
918
- throw new Error("CoolClaw account is not fully configured");
919
- }
920
- const client = new CoolclawWsClient({
921
- gatewayUrl: account.gatewayUrl,
922
- agentId: account.agentId,
923
- token,
924
- pluginVersion: "0.1.0",
925
- ackStore: new InMemoryAckStore(),
926
- accountKey: `coolclaw:${ctx.accountId ?? "default"}`
927
- });
928
- await client.start();
929
- try {
930
- const messageId = await sendText({ client, target: ctx.to, text: ctx.text });
931
- return {
932
- channel: "coolclaw",
933
- messageId,
934
- conversationId: ctx.to,
935
- timestamp: Date.now()
936
- };
937
- } finally {
938
- await client.stop();
939
- }
940
- }
941
- },
942
- status(source) {
943
- return getCoolclawStatus(source);
944
- },
945
- gateway: {
946
- async startAccount(ctx) {
947
- const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
948
- const token = await resolveAccountToken(account);
949
- if (!account.gatewayUrl || !account.agentId || !token) {
950
- ctx.log?.error(`[${ctx.accountId}] CoolClaw account is not fully configured`);
951
- return;
952
- }
953
- const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
954
- const ackStore = new FileAckStore();
955
- ctx.setStatus({ accountId: ctx.accountId, status: "connecting" });
956
- ctx.log?.info(`[${ctx.accountId}] starting CoolClaw provider (${account.gatewayUrl})`);
957
- const client = new CoolclawWsClient({
958
- gatewayUrl: account.gatewayUrl,
959
- agentId: account.agentId,
960
- token,
961
- pluginVersion: "0.1.0",
962
- ackStore,
963
- accountKey,
964
- onFrame: async (frame, wsClient) => {
965
- try {
966
- await handleInboundFrame({
967
- frame,
968
- accountKey,
1276
+ gateway: {
1277
+ async startAccount(ctx) {
1278
+ const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1279
+ const token = await resolveAccountToken(account);
1280
+ if (!account.gatewayUrl || !account.agentId || !token) {
1281
+ ctx.log?.error(`[${ctx.accountId}] CoolClaw account is not fully configured`);
1282
+ return;
1283
+ }
1284
+ const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1285
+ const ackStore = new FileAckStore();
1286
+ const statusSink = createAccountStatusSink({
1287
+ accountId: ctx.accountId,
1288
+ setStatus: ctx.setStatus
1289
+ });
1290
+ statusSink({ statusState: "connecting" });
1291
+ ctx.log?.info(`[${ctx.accountId}] starting CoolClaw provider (${account.gatewayUrl})`);
1292
+ await runPassiveAccountLifecycle({
1293
+ abortSignal: ctx.abortSignal,
1294
+ start: async () => {
1295
+ const client = new CoolclawWsClient({
1296
+ gatewayUrl: account.gatewayUrl,
1297
+ agentId: account.agentId,
1298
+ token,
1299
+ pluginVersion: getPluginVersion(),
969
1300
  ackStore,
970
- accountConfig: { allowFrom: account.allowFrom, dmPolicy: account.dmPolicy },
971
- dispatch: async (envelope) => {
972
- if (!ctx.channelRuntime) {
973
- ctx.log?.warn("channelRuntime not available; skipping dispatch");
974
- return;
975
- }
1301
+ accountKey,
1302
+ onStateChange: (state) => {
1303
+ statusSink({ statusState: state });
1304
+ },
1305
+ onFrame: async (frame, wsClient) => {
976
1306
  try {
977
- const isGroup = envelope.conversationId.startsWith("group:");
978
- const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
979
- const route = ctx.channelRuntime.routing?.resolveAgentRoute ? await ctx.channelRuntime.routing.resolveAgentRoute({
980
- cfg: ctx.cfg,
981
- channel: "coolclaw",
982
- accountId: ctx.accountId,
983
- peer
984
- }) : { agentId: "main", sessionKey: `coolclaw:${envelope.conversationId}` };
985
- const storePath = ctx.channelRuntime.session?.resolveStorePath ? ctx.channelRuntime.session.resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId }) : "/tmp/openclaw/sessions";
986
- const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : "unknown";
987
- let deliveryTarget;
988
- if (envelope.group) {
989
- deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
990
- } else if (envelope.sender) {
991
- deliveryTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
992
- } else {
993
- deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
994
- }
995
- const agentHint = envelope.metadata?.agentHint;
996
- const bodyForAgent = agentHint ? envelope.text + agentHint : envelope.text;
997
- const ctxPayload = ctx.channelRuntime.reply.finalizeInboundContext({
998
- Body: envelope.text,
999
- BodyForAgent: bodyForAgent,
1000
- RawBody: envelope.text,
1001
- CommandBody: envelope.text,
1002
- From: `coolclaw:${senderLabel}`,
1003
- To: deliveryTarget,
1004
- SessionKey: route.sessionKey,
1005
- AccountId: ctx.accountId,
1006
- ChatType: isGroup ? "channel" : "direct",
1007
- CommandAuthorized: true,
1008
- Provider: "coolclaw",
1009
- Surface: "coolclaw",
1010
- Channel: "coolclaw",
1011
- Peer: peer,
1012
- Mentioned: envelope.shouldReply
1013
- });
1014
- if (ctx.channelRuntime.session?.recordSessionMetaFromInbound) {
1015
- await ctx.channelRuntime.session.recordSessionMetaFromInbound({
1016
- storePath,
1017
- sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
1018
- ctx: ctxPayload
1019
- });
1020
- }
1021
- if (ctx.channelRuntime.session?.updateLastRoute) {
1022
- const sessionKey = ctxPayload.SessionKey ?? route.sessionKey;
1023
- const lastRouteCtx = {
1024
- storePath,
1025
- sessionKey,
1026
- deliveryContext: {
1027
- channel: "coolclaw",
1028
- to: deliveryTarget,
1029
- accountId: ctx.accountId ?? void 0
1030
- },
1031
- ctx: ctxPayload
1032
- };
1033
- try {
1034
- await ctx.channelRuntime.session.updateLastRoute(lastRouteCtx);
1035
- } catch (err) {
1036
- ctx.log?.warn(
1037
- `updateLastRoute failed: ${err instanceof Error ? err.message : String(err)}`
1038
- );
1039
- }
1040
- const mainSessionKey = route.mainSessionKey;
1041
- if (mainSessionKey && mainSessionKey !== sessionKey) {
1307
+ await handleInboundFrame({
1308
+ frame,
1309
+ accountKey,
1310
+ ackStore,
1311
+ dispatch: async (envelope) => {
1312
+ const runtime = getCoolclawRuntime();
1313
+ if (!runtime?.channel) {
1314
+ logInboundDrop({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1315
+ }), channel: "coolclaw", reason: "runtime not available; skipping dispatch" });
1316
+ return;
1317
+ }
1042
1318
  try {
1043
- await ctx.channelRuntime.session.updateLastRoute({
1044
- ...lastRouteCtx,
1045
- sessionKey: mainSessionKey,
1046
- ctx: void 0
1319
+ const isGroup = envelope.conversationId.startsWith("group:");
1320
+ const peer = isGroup ? { kind: "group", id: envelope.group?.groupId ?? envelope.conversationId } : { kind: "direct", id: envelope.conversationId };
1321
+ if (!runtime.channel.routing?.resolveAgentRoute) {
1322
+ throw new Error(
1323
+ "CoolClaw requires runtime.channel.routing.resolveAgentRoute. Please upgrade OpenClaw to >=2026.3.22."
1324
+ );
1325
+ }
1326
+ const route = await runtime.channel.routing.resolveAgentRoute({
1327
+ cfg: ctx.cfg,
1328
+ channel: "coolclaw",
1329
+ accountId: ctx.accountId,
1330
+ peer
1047
1331
  });
1048
- } catch (err) {
1049
- ctx.log?.warn(
1050
- `updateLastRoute (main) failed: ${err instanceof Error ? err.message : String(err)}`
1332
+ if (!runtime.channel.session?.resolveStorePath) {
1333
+ throw new Error(
1334
+ "CoolClaw requires runtime.channel.session.resolveStorePath. Please upgrade OpenClaw to >=2026.3.22."
1335
+ );
1336
+ }
1337
+ const storePath = runtime.channel.session.resolveStorePath(
1338
+ ctx.cfg.session?.store,
1339
+ { agentId: route.agentId }
1051
1340
  );
1052
- }
1053
- }
1054
- }
1055
- await ctx.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
1056
- ctx: ctxPayload,
1057
- cfg: ctx.cfg,
1058
- dispatcherOptions: {
1059
- deliver: async (payload) => {
1060
- if (!payload.text) return;
1061
- try {
1062
- let replyTarget;
1063
- if (envelope.group) {
1064
- replyTarget = `coolclaw:group:${envelope.group.groupId}`;
1065
- } else if (envelope.sender) {
1066
- replyTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1067
- } else {
1068
- replyTarget = normalizeCoolclawTarget(envelope.conversationId);
1069
- }
1070
- await sendText({ client: wsClient, target: replyTarget, text: payload.text });
1071
- } catch (err) {
1072
- ctx.log?.error(`Failed to deliver reply: ${err instanceof Error ? err.message : String(err)}`);
1341
+ const senderLabel = envelope.sender ? `${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}` : "unknown";
1342
+ let deliveryTarget;
1343
+ if (envelope.group) {
1344
+ deliveryTarget = `coolclaw:group:${envelope.group.groupId}`;
1345
+ } else if (envelope.sender) {
1346
+ deliveryTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1347
+ } else {
1348
+ deliveryTarget = normalizeCoolclawTarget(envelope.conversationId);
1073
1349
  }
1074
- },
1075
- onError: (err, info) => {
1076
- ctx.log?.error(`Reply dispatch error: ${err instanceof Error ? err.message : String(err)}`, info);
1350
+ const agentHint = envelope.metadata?.agentHint;
1351
+ const bodyForAgent = agentHint ? envelope.text + agentHint : envelope.text;
1352
+ const ctxPayload = runtime.channel.reply.finalizeInboundContext({
1353
+ Body: envelope.text,
1354
+ BodyForAgent: bodyForAgent,
1355
+ RawBody: envelope.text,
1356
+ CommandBody: envelope.text,
1357
+ From: `coolclaw:${senderLabel}`,
1358
+ To: deliveryTarget,
1359
+ SessionKey: route.sessionKey,
1360
+ AccountId: ctx.accountId,
1361
+ ChatType: isGroup ? "channel" : "direct",
1362
+ CommandAuthorized: true,
1363
+ Provider: "coolclaw",
1364
+ Surface: "coolclaw",
1365
+ Channel: "coolclaw",
1366
+ Peer: peer,
1367
+ Mentioned: envelope.shouldReply
1368
+ });
1369
+ const sessionKey = ctxPayload.SessionKey ?? route.sessionKey;
1370
+ const mainSessionKey = route.mainSessionKey;
1371
+ await runtime.channel.session.recordInboundSession({
1372
+ storePath,
1373
+ sessionKey,
1374
+ ctx: ctxPayload,
1375
+ updateLastRoute: mainSessionKey && mainSessionKey !== sessionKey ? { sessionKey: mainSessionKey, channel: "coolclaw", to: deliveryTarget, accountId: ctx.accountId ?? void 0 } : void 0,
1376
+ onRecordError: (err) => {
1377
+ ctx.log?.warn(`recordInboundSession failed: ${err instanceof Error ? err.message : String(err)}`);
1378
+ }
1379
+ });
1380
+ await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1381
+ ctx: ctxPayload,
1382
+ cfg: ctx.cfg,
1383
+ dispatcherOptions: {
1384
+ deliver: async (payload) => {
1385
+ if (!payload.text) return;
1386
+ try {
1387
+ let replyTarget;
1388
+ if (envelope.group) {
1389
+ replyTarget = `coolclaw:group:${envelope.group.groupId}`;
1390
+ } else if (envelope.sender) {
1391
+ replyTarget = `coolclaw:${envelope.sender.userType.toLowerCase()}:${envelope.sender.userId}`;
1392
+ } else {
1393
+ replyTarget = normalizeCoolclawTarget(envelope.conversationId);
1394
+ }
1395
+ await sendText({ client: wsClient, target: replyTarget, text: payload.text });
1396
+ } catch (err) {
1397
+ ctx.log?.error(`Failed to deliver reply: ${err instanceof Error ? err.message : String(err)}`);
1398
+ }
1399
+ },
1400
+ onError: (err, info) => {
1401
+ ctx.log?.error(`Reply dispatch error: ${err instanceof Error ? err.message : String(err)}${info ? ` ${JSON.stringify(info)}` : ""}`);
1402
+ }
1403
+ }
1404
+ });
1405
+ } catch (err) {
1406
+ ctx.log?.error(`Inbound dispatch error: ${err instanceof Error ? err.message : String(err)}`);
1407
+ }
1408
+ },
1409
+ sendAck: async (ackFrame) => {
1410
+ try {
1411
+ wsClient.sendFrame(ackFrame);
1412
+ ctx.log?.debug?.(`ACK sent: type=${ackFrame.type} lastAckedSeq=${ackFrame.payload?.lastAckedSeq}`);
1413
+ } catch (err) {
1414
+ logAckFailure({ log: ctx.log?.warn?.bind(ctx.log) ?? (() => {
1415
+ }), channel: "coolclaw", error: err });
1077
1416
  }
1078
1417
  }
1079
1418
  });
1080
1419
  } catch (err) {
1081
- ctx.log?.error(`Inbound dispatch error: ${err instanceof Error ? err.message : String(err)}`);
1082
- }
1083
- },
1084
- sendAck: async (ackFrame) => {
1085
- try {
1086
- wsClient.sendFrame(ackFrame);
1087
- ctx.log?.debug(`ACK sent: type=${ackFrame.type} lastAckedSeq=${ackFrame.payload?.lastAckedSeq}`);
1088
- } catch (err) {
1089
- ctx.log?.warn(`ACK send failed: ${err instanceof Error ? err.message : String(err)}`);
1420
+ ctx.log?.error(`Frame handling error: ${err instanceof Error ? err.message : String(err)}`);
1090
1421
  }
1091
1422
  }
1092
1423
  });
1093
- } catch (err) {
1094
- ctx.log?.error(`Frame handling error: ${err instanceof Error ? err.message : String(err)}`);
1424
+ await client.start();
1425
+ setRuntimeClient(accountKey, client);
1426
+ statusSink({ statusState: "connected" });
1427
+ ctx.log?.info(`[${ctx.accountId}] CoolClaw provider connected`);
1428
+ return client;
1429
+ },
1430
+ stop: async (client) => {
1431
+ await client.stop();
1432
+ clearRuntimeClient(accountKey);
1433
+ },
1434
+ onStop: () => {
1435
+ statusSink({ statusState: "disconnected" });
1436
+ ctx.log?.info(`[${ctx.accountId}] CoolClaw provider stopped`);
1095
1437
  }
1438
+ });
1439
+ },
1440
+ /** 显式停止账户连接,清理 WebSocket 客户端资源 */
1441
+ async stopAccount(ctx) {
1442
+ const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1443
+ const client = getRuntimeClient(accountKey);
1444
+ if (client) {
1445
+ await client.stop();
1446
+ clearRuntimeClient(accountKey);
1447
+ ctx.log?.info(`[${ctx.accountId}] CoolClaw client stopped via stopAccount`);
1096
1448
  }
1097
- });
1098
- await client.start();
1099
- ctx.setStatus({ accountId: ctx.accountId, status: "connected" });
1100
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider connected`);
1101
- await new Promise((resolve) => {
1102
- ctx.abortSignal.addEventListener("abort", () => resolve(), { once: true });
1103
- });
1104
- await client.stop();
1105
- ctx.setStatus({ accountId: ctx.accountId, status: "disconnected" });
1106
- ctx.log?.info(`[${ctx.accountId}] CoolClaw provider stopped`);
1449
+ }
1450
+ },
1451
+ /** auth 适配器 支持 openclaw channels login --channel coolclaw 原生命令 */
1452
+ auth: {
1453
+ async login({ cfg, accountId, verbose }) {
1454
+ const resolvedAccountId = accountId?.trim() || "default";
1455
+ const account = coolclawChannelPlugin.config.resolveAccount(cfg, resolvedAccountId);
1456
+ const { validateAgentToken: validateAgentToken2, runCoolclawSetup: runCoolclawSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
1457
+ const existingToken = account.tokenSecretRef ? await resolveAccountToken(account) : void 0;
1458
+ if (existingToken) {
1459
+ const gatewayUrl = account.gatewayUrl ?? "https://agits-xa.baidu.com/riddle";
1460
+ const valid = await validateAgentToken2(gatewayUrl, existingToken);
1461
+ if (valid) {
1462
+ if (verbose) console.log("[coolclaw] Already authenticated.");
1463
+ return;
1464
+ }
1465
+ }
1466
+ if (verbose) console.log("[coolclaw] Running setup...");
1467
+ const result = await runCoolclawSetup2({
1468
+ gatewayUrl: account.gatewayUrl,
1469
+ accountId: resolvedAccountId,
1470
+ autoRestart: false
1471
+ // login 场景不需要重启网关
1472
+ });
1473
+ if (result.mode === "register") {
1474
+ if (verbose) console.log(`[coolclaw] Agent registered: ${result.agentId}`);
1475
+ } else {
1476
+ if (verbose) console.log(`[coolclaw] Reusing existing agent: ${result.agentId}`);
1477
+ }
1478
+ if (verbose) console.log("[coolclaw] Authentication complete.");
1479
+ }
1480
+ }
1481
+ },
1482
+ security: {
1483
+ dm: {
1484
+ channelKey: "coolclaw",
1485
+ resolvePolicy: (account) => account.dmPolicy ?? "allowlist",
1486
+ resolveAllowFrom: (account) => account.allowFrom ?? [],
1487
+ defaultPolicy: "allowlist",
1488
+ normalizeEntry: (raw) => raw.trim().toLowerCase()
1489
+ }
1490
+ },
1491
+ pairing: {
1492
+ text: {
1493
+ idLabel: "CoolClaw user ID",
1494
+ message: "You are not authorized to message this agent. Send this pairing code to verify:",
1495
+ notify: async ({ id, message }) => {
1496
+ const client = getRuntimeClient("coolclaw:default");
1497
+ if (client) {
1498
+ await sendText({
1499
+ client,
1500
+ target: id,
1501
+ text: message
1502
+ });
1503
+ }
1504
+ }
1505
+ }
1506
+ },
1507
+ threading: {
1508
+ topLevelReplyToMode: "reply"
1509
+ },
1510
+ outbound: {
1511
+ base: {
1512
+ deliveryMode: "direct",
1513
+ resolveTarget({ to }) {
1514
+ if (!to) {
1515
+ return { ok: false, error: new Error("CoolClaw target is required") };
1516
+ }
1517
+ try {
1518
+ const normalized = normalizeCoolclawTarget(to);
1519
+ parseCoolclawTarget(normalized);
1520
+ return { ok: true, to: normalized };
1521
+ } catch (error) {
1522
+ return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
1523
+ }
1524
+ }
1525
+ },
1526
+ attachedResults: {
1527
+ channel: "coolclaw",
1528
+ async sendText(ctx) {
1529
+ const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1530
+ const token = await resolveAccountToken(account);
1531
+ if (!account.gatewayUrl || !account.agentId || !token) {
1532
+ throw new Error("CoolClaw account is not fully configured");
1533
+ }
1534
+ const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1535
+ let client = getRuntimeClient(accountKey);
1536
+ if (!client || !client.isConnected()) {
1537
+ client = new CoolclawWsClient({
1538
+ gatewayUrl: account.gatewayUrl,
1539
+ agentId: account.agentId,
1540
+ token,
1541
+ pluginVersion: getPluginVersion(),
1542
+ ackStore: new InMemoryAckStore(),
1543
+ accountKey
1544
+ });
1545
+ await client.start();
1546
+ try {
1547
+ const messageId2 = await sendText({ client, target: ctx.to, text: ctx.text });
1548
+ return { messageId: messageId2, conversationId: ctx.to, timestamp: Date.now() };
1549
+ } finally {
1550
+ await client.stop();
1551
+ }
1552
+ }
1553
+ const messageId = await sendText({ client, target: ctx.to, text: ctx.text });
1554
+ return { messageId, conversationId: ctx.to, timestamp: Date.now() };
1555
+ },
1556
+ async sendMedia(ctx) {
1557
+ const account = coolclawChannelPlugin.config.resolveAccount(ctx.cfg, ctx.accountId);
1558
+ const token = await resolveAccountToken(account);
1559
+ if (!account.gatewayUrl || !account.agentId || !token) {
1560
+ throw new Error("CoolClaw account is not fully configured");
1561
+ }
1562
+ const accountKey = `coolclaw:${ctx.accountId ?? "default"}`;
1563
+ let client = getRuntimeClient(accountKey);
1564
+ if (!client || !client.isConnected()) {
1565
+ client = new CoolclawWsClient({
1566
+ gatewayUrl: account.gatewayUrl,
1567
+ agentId: account.agentId,
1568
+ token,
1569
+ pluginVersion: getPluginVersion(),
1570
+ ackStore: new InMemoryAckStore(),
1571
+ accountKey
1572
+ });
1573
+ await client.start();
1574
+ try {
1575
+ const messageId2 = await sendMedia({ client, target: ctx.to, filePath: ctx.mediaUrl ?? "" });
1576
+ return { messageId: messageId2, conversationId: ctx.to, timestamp: Date.now() };
1577
+ } finally {
1578
+ await client.stop();
1579
+ }
1580
+ }
1581
+ const messageId = await sendMedia({ client, target: ctx.to, filePath: ctx.mediaUrl ?? "" });
1582
+ return { messageId, conversationId: ctx.to, timestamp: Date.now() };
1583
+ }
1107
1584
  }
1108
1585
  }
1109
- };
1586
+ });
1110
1587
 
1111
1588
  // setup-entry.ts
1112
- var setup_entry_default = { plugin: coolclawChannelPlugin };
1589
+ var setup_entry_default = defineSetupPluginEntry(coolclawChannelPlugin);
1113
1590
  export {
1114
1591
  setup_entry_default as default
1115
1592
  };