@coolclaw/coolclaw 0.2.10 → 0.3.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.
@@ -1,1591 +1,10 @@
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
- });
1
+ import {
2
+ coolclawChannelPlugin
3
+ } from "./chunk-GNPSYUMN.js";
4
+ import "./chunk-Q3NF4NWE.js";
479
5
 
480
6
  // setup-entry.ts
481
7
  import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
482
-
483
- // src/ack-store.ts
484
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
485
- import { join } from "path";
486
- import { homedir } from "os";
487
- var InMemoryAckStore = class {
488
- cursors = /* @__PURE__ */ new Map();
489
- async getLastAckedSeq(accountKey) {
490
- return this.cursors.get(accountKey) ?? 0;
491
- }
492
- async record(accountKey, seq) {
493
- if (!Number.isInteger(seq) || seq < 1) {
494
- throw new Error(`Invalid ACK seq: ${seq}`);
495
- }
496
- const current = this.cursors.get(accountKey) ?? 0;
497
- if (seq <= current) {
498
- return current;
499
- }
500
- this.cursors.set(accountKey, seq);
501
- return seq;
502
- }
503
- };
504
- var FileAckStore = class {
505
- cursors = /* @__PURE__ */ new Map();
506
- dir;
507
- constructor(dir) {
508
- this.dir = dir ?? join(homedir(), ".openclaw", "extensions", "coolclaw", ".ack-store");
509
- if (!existsSync(this.dir)) {
510
- mkdirSync(this.dir, { recursive: true });
511
- }
512
- }
513
- async getLastAckedSeq(accountKey) {
514
- return this.cursors.get(accountKey) ?? this.load(accountKey);
515
- }
516
- async record(accountKey, seq) {
517
- if (!Number.isInteger(seq) || seq < 1) {
518
- throw new Error(`Invalid ACK seq: ${seq}`);
519
- }
520
- const current = this.cursors.get(accountKey) ?? this.load(accountKey);
521
- if (seq <= current) {
522
- return current;
523
- }
524
- this.cursors.set(accountKey, seq);
525
- this.persist(accountKey, seq);
526
- return seq;
527
- }
528
- load(accountKey) {
529
- const filePath = this.filePath(accountKey);
530
- if (!existsSync(filePath)) {
531
- return 0;
532
- }
533
- try {
534
- const text = readFileSync(filePath, "utf-8").trim();
535
- const value = parseInt(text, 10);
536
- return Number.isFinite(value) && value >= 0 ? value : 0;
537
- } catch {
538
- return 0;
539
- }
540
- }
541
- persist(accountKey, lastAckedSeq) {
542
- try {
543
- writeFileSync(this.filePath(accountKey), String(lastAckedSeq), "utf-8");
544
- } catch {
545
- }
546
- }
547
- filePath(accountKey) {
548
- const safeName = accountKey.replace(/[^a-zA-Z0-9_-]/g, "_");
549
- return join(this.dir, `${safeName}.ack`);
550
- }
551
- };
552
-
553
- // src/channel.ts
554
- init_config();
555
-
556
- // src/frame-codec.ts
557
- import { randomUUID } from "crypto";
558
- var CoolclawFrameDecodeError = class extends Error {
559
- constructor(message) {
560
- super(message);
561
- this.name = "CoolclawFrameDecodeError";
562
- }
563
- };
564
- function createFrame(type, payload) {
565
- return {
566
- v: 1,
567
- type,
568
- id: `cli_${randomUUID()}`,
569
- ts: Date.now(),
570
- payload
571
- };
572
- }
573
- function encodeFrame(frame) {
574
- return JSON.stringify(frame);
575
- }
576
- function decodeFrame(raw) {
577
- let value;
578
- try {
579
- value = JSON.parse(raw);
580
- } catch (error) {
581
- throw new CoolclawFrameDecodeError(`Invalid CoolClaw frame JSON: ${error.message}`);
582
- }
583
- if (!isRecord(value)) {
584
- throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: expected object");
585
- }
586
- if (!("v" in value)) {
587
- throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: missing v");
588
- }
589
- if (value.v !== 1) {
590
- throw new CoolclawFrameDecodeError("Unsupported CoolClaw frame version");
591
- }
592
- if (typeof value.type !== "string" || value.type.length === 0) {
593
- throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: missing type");
594
- }
595
- if (typeof value.id !== "string" || value.id.length === 0) {
596
- throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: missing id");
597
- }
598
- if (typeof value.ts !== "number" || !Number.isFinite(value.ts)) {
599
- throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: missing ts");
600
- }
601
- if ("ack" in value && value.ack !== void 0 && typeof value.ack !== "string") {
602
- throw new CoolclawFrameDecodeError("Invalid CoolClaw frame: ack must be a string");
603
- }
604
- const frame = {
605
- v: 1,
606
- type: value.type,
607
- id: value.id,
608
- ts: value.ts
609
- };
610
- if (typeof value.ack === "string") {
611
- frame.ack = value.ack;
612
- }
613
- if ("payload" in value) {
614
- frame.payload = value.payload;
615
- }
616
- return frame;
617
- }
618
- function isRecord(value) {
619
- return typeof value === "object" && value !== null;
620
- }
621
-
622
- // src/inbound.ts
623
- function mapInboundFrame(frame) {
624
- if (frame.type === "PRIVATE_MESSAGE") {
625
- const payload = assertPrivatePayload(frame.payload);
626
- return {
627
- id: payload.messageId,
628
- channel: "coolclaw",
629
- conversationId: `private:${payload.sender.userType}:${payload.sender.userId}`,
630
- text: payload.content,
631
- messageType: payload.messageType,
632
- seq: payload.seq,
633
- shouldReply: true,
634
- // 私聊本身就是与 Bot 对话的意图
635
- sender: payload.sender,
636
- recipient: payload.recipient,
637
- metadata: {
638
- riddleConversationId: payload.conversationId,
639
- sentAt: payload.sentAt,
640
- sourceFrameId: frame.id
641
- }
642
- };
643
- }
644
- if (frame.type === "GROUP_MESSAGE") {
645
- const payload = assertGroupPayload(frame.payload);
646
- return {
647
- id: payload.messageId,
648
- channel: "coolclaw",
649
- conversationId: `group:${payload.groupId}`,
650
- text: payload.content,
651
- messageType: payload.messageType,
652
- seq: payload.seq,
653
- shouldReply: payload.mentioned,
654
- // 群聊仅在 @ 时回复
655
- sender: payload.sender,
656
- group: {
657
- groupId: payload.groupId,
658
- groupName: payload.groupName
659
- },
660
- metadata: {
661
- riddleConversationId: payload.conversationId,
662
- sentAt: payload.sentAt,
663
- sourceFrameId: frame.id,
664
- agentHint: payload.agentHint
665
- }
666
- };
667
- }
668
- if (frame.type === "SYSTEM_NOTIFICATION" || frame.type === "GAME_EVENT" || frame.type === "CONTENT_TASK") {
669
- return mapNotificationFrame(frame);
670
- }
671
- throw new Error(`Unsupported inbound CoolClaw frame type: ${frame.type}`);
672
- }
673
- async function handleInboundFrame(input) {
674
- const envelope = mapInboundFrame(input.frame);
675
- if (!envelope) return;
676
- await ackProcessedSeq(input, envelope);
677
- await input.dispatch(envelope);
678
- }
679
- function mapNotificationFrame(frame) {
680
- const payload = isRecord2(frame.payload) ? frame.payload : {};
681
- const seq = typeof payload.seq === "number" ? payload.seq : void 0;
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;
713
- }
714
- async function ackProcessedSeq(input, envelope) {
715
- if (typeof envelope.seq === "number") {
716
- const lastAckedSeq = await input.ackStore.record(input.accountKey, envelope.seq);
717
- await input.sendAck(createFrame("ACK", { lastAckedSeq }));
718
- }
719
- }
720
- function assertPrivatePayload(value) {
721
- if (!isRecord2(value) || !isUserRef(value.sender) || !isUserRef(value.recipient)) {
722
- throw new Error("Invalid PRIVATE_MESSAGE payload");
723
- }
724
- return {
725
- seq: readNumber(value, "seq"),
726
- messageId: readString(value, "messageId"),
727
- conversationId: readString(value, "conversationId"),
728
- sender: value.sender,
729
- recipient: value.recipient,
730
- messageType: readString(value, "messageType"),
731
- content: readString(value, "content"),
732
- mentioned: readBoolean(value, "mentioned"),
733
- sentAt: readString(value, "sentAt")
734
- };
735
- }
736
- function assertGroupPayload(value) {
737
- if (!isRecord2(value) || !isUserRef(value.sender)) {
738
- throw new Error("Invalid GROUP_MESSAGE payload");
739
- }
740
- return {
741
- seq: readNumber(value, "seq"),
742
- messageId: readString(value, "messageId"),
743
- groupId: readString(value, "groupId"),
744
- groupName: readString(value, "groupName"),
745
- conversationId: readString(value, "conversationId"),
746
- sender: value.sender,
747
- messageType: readString(value, "messageType"),
748
- content: readString(value, "content"),
749
- mentioned: readBoolean(value, "mentioned"),
750
- sentAt: readString(value, "sentAt"),
751
- agentHint: readOptionalString(value, "agentHint")
752
- };
753
- }
754
- function readString(source, key) {
755
- const value = source[key];
756
- if (typeof value !== "string" || value.length === 0) {
757
- throw new Error(`Invalid inbound payload: missing ${key}`);
758
- }
759
- return value;
760
- }
761
- function readNumber(source, key) {
762
- const value = source[key];
763
- if (typeof value !== "number" || !Number.isInteger(value)) {
764
- throw new Error(`Invalid inbound payload: missing ${key}`);
765
- }
766
- return value;
767
- }
768
- function readBoolean(source, key) {
769
- const value = source[key];
770
- if (typeof value !== "boolean") {
771
- throw new Error(`Invalid inbound payload: missing ${key}`);
772
- }
773
- return value;
774
- }
775
- function readOptionalString(source, key) {
776
- const value = source[key];
777
- if (value === void 0 || value === null) {
778
- return void 0;
779
- }
780
- if (typeof value !== "string") {
781
- throw new Error(`Invalid inbound payload: ${key} must be a string`);
782
- }
783
- return value;
784
- }
785
- function isUserRef(value) {
786
- return isRecord2(value) && typeof value.userId === "string" && (value.userType === "HUMAN" || value.userType === "AGENT") && (value.displayName === void 0 || typeof value.displayName === "string");
787
- }
788
- function isRecord2(value) {
789
- return typeof value === "object" && value !== null;
790
- }
791
-
792
- // src/targets.ts
793
- var TargetParseError = class extends Error {
794
- constructor(message) {
795
- super(message);
796
- this.name = "TargetParseError";
797
- }
798
- };
799
- function parseCoolclawTarget(raw) {
800
- const normalized = normalizeCoolclawTarget(raw);
801
- const parts = normalized.split(":");
802
- if (parts.length !== 3) {
803
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
804
- }
805
- const [channel, type, id] = parts;
806
- if (channel !== "coolclaw" || id.length === 0) {
807
- throw new TargetParseError(`Invalid CoolClaw target: ${raw}`);
808
- }
809
- if (type === "human") {
810
- return { kind: "private", userType: "HUMAN", userId: id };
811
- }
812
- if (type === "agent") {
813
- return { kind: "private", userType: "AGENT", userId: id };
814
- }
815
- if (type === "group") {
816
- return { kind: "group", groupId: id };
817
- }
818
- throw new TargetParseError(`Invalid CoolClaw target type: ${type}`);
819
- }
820
- function normalizeCoolclawTarget(raw) {
821
- const trimmed = raw.trim();
822
- const parts = trimmed.split(":");
823
- if (parts.length !== 3) {
824
- return trimmed;
825
- }
826
- const [channel, type, id] = parts;
827
- return `${channel.toLowerCase()}:${type.toLowerCase()}:${id.trim()}`;
828
- }
829
- function inferCoolclawTargetChatType(raw) {
830
- try {
831
- const target = parseCoolclawTarget(raw);
832
- return target.kind === "private" ? "direct" : "group";
833
- } catch {
834
- return void 0;
835
- }
836
- }
837
- function isCoolclawTargetId(raw, normalized = normalizeCoolclawTarget(raw)) {
838
- try {
839
- parseCoolclawTarget(normalized);
840
- return normalized.startsWith("coolclaw:");
841
- } catch {
842
- return false;
843
- }
844
- }
845
- async function resolveCoolclawMessagingTarget(raw, preferredKind) {
846
- const normalized = normalizeCoolclawTarget(raw);
847
- const target = parseCoolclawTarget(normalized);
848
- const kind = target.kind === "private" ? "user" : "group";
849
- if (preferredKind && preferredKind !== kind) {
850
- return null;
851
- }
852
- const [, type, id] = normalized.split(":");
853
- return {
854
- to: normalized,
855
- kind,
856
- display: `${type}:${id}`,
857
- source: "normalized"
858
- };
859
- }
860
-
861
- // src/outbound.ts
862
- async function sendText(input) {
863
- const target = parseCoolclawTarget(input.target);
864
- const frame = target.kind === "private" ? createFrame("SEND_PRIVATE", {
865
- target: {
866
- userId: target.userId,
867
- userType: target.userType
868
- },
869
- messageType: "TEXT",
870
- content: input.text
871
- }) : createFrame("SEND_GROUP", {
872
- groupId: target.groupId,
873
- messageType: "TEXT",
874
- content: input.text
875
- });
876
- const response = await input.client.request(frame);
877
- if (response.ok === false) {
878
- throw new Error(response.error?.message ?? "CoolClaw message send failed");
879
- }
880
- if (!response.messageId) {
881
- throw new Error("CoolClaw message send response missing messageId");
882
- }
883
- return response.messageId;
884
- }
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;
909
- }
910
-
911
- // src/ws-client.ts
912
- init_config();
913
- import WebSocket from "ws";
914
- var CoolclawWsClient = class {
915
- constructor(options) {
916
- this.options = options;
917
- }
918
- socket;
919
- heartbeatTimer;
920
- reconnectTimer;
921
- stopped = true;
922
- reconnectAttempt = 0;
923
- pendingRequests = /* @__PURE__ */ new Map();
924
- async start() {
925
- this.stopped = false;
926
- await this.connect();
927
- }
928
- async stop() {
929
- this.stopped = true;
930
- this.clearHeartbeat();
931
- this.clearReconnect();
932
- this.rejectPending(new Error("CoolClaw WSS client stopped"));
933
- const socket = this.socket;
934
- this.socket = void 0;
935
- if (!socket || socket.readyState === WebSocket.CLOSED) {
936
- return;
937
- }
938
- await new Promise((resolve) => {
939
- socket.once("close", () => resolve());
940
- socket.close(1e3, "client stopped");
941
- });
942
- }
943
- async request(frame) {
944
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
945
- throw new Error("CoolClaw WSS client is not connected");
946
- }
947
- const requestTimeoutMs = this.options.requestTimeoutMs ?? 1e4;
948
- return new Promise((resolve, reject) => {
949
- const timeout = setTimeout(() => {
950
- this.pendingRequests.delete(frame.id);
951
- reject(new Error(`CoolClaw request timed out: ${frame.type}`));
952
- }, requestTimeoutMs);
953
- this.pendingRequests.set(frame.id, {
954
- resolve: (payload) => resolve(payload),
955
- reject,
956
- timeout
957
- });
958
- this.sendFrame(frame);
959
- });
960
- }
961
- sendFrame(frame) {
962
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
963
- throw new Error("CoolClaw WSS client is not connected");
964
- }
965
- this.socket.send(encodeFrame(frame));
966
- }
967
- isConnected() {
968
- return this.socket?.readyState === WebSocket.OPEN;
969
- }
970
- async connect() {
971
- this.notifyState("connecting");
972
- const lastAckedSeq = await this.options.ackStore.getLastAckedSeq(this.options.accountKey);
973
- const socket = new WebSocket(buildWsUrl(this.options.gatewayUrl, lastAckedSeq), {
974
- headers: {
975
- Authorization: `Bearer ${this.options.token}`,
976
- "X-CoolClaw-Agent-Id": this.options.agentId,
977
- "X-CoolClaw-Plugin-Version": this.options.pluginVersion
978
- }
979
- });
980
- this.socket = socket;
981
- await new Promise((resolve, reject) => {
982
- let helloReceived = false;
983
- const failBeforeHello = (error) => {
984
- if (!helloReceived) {
985
- cleanupBeforeHello();
986
- reject(error);
987
- }
988
- };
989
- const cleanupBeforeHello = () => {
990
- socket.off("error", failBeforeHello);
991
- socket.off("message", onMessageBeforeHello);
992
- socket.off("close", onCloseBeforeHello);
993
- };
994
- const onMessageBeforeHello = (data) => {
995
- let frame;
996
- try {
997
- frame = decodeFrame(data.toString());
998
- } catch (error) {
999
- failBeforeHello(error);
1000
- return;
1001
- }
1002
- this.handleFrame(frame).catch((error) => {
1003
- this.rejectPending(error instanceof Error ? error : new Error(String(error)));
1004
- });
1005
- if (frame.type === "HELLO") {
1006
- helloReceived = true;
1007
- this.reconnectAttempt = 0;
1008
- cleanupBeforeHello();
1009
- socket.on("message", (nextData) => {
1010
- this.handleRawMessage(nextData).catch((error) => {
1011
- this.rejectPending(error instanceof Error ? error : new Error(String(error)));
1012
- });
1013
- });
1014
- socket.on("close", (code) => this.handleClose(code));
1015
- this.startHeartbeat(frame);
1016
- this.notifyState("connected");
1017
- resolve();
1018
- }
1019
- };
1020
- const onCloseBeforeHello = (code) => {
1021
- const error = new Error(`CoolClaw WSS closed before HELLO: ${code}`);
1022
- cleanupBeforeHello();
1023
- if (!this.isTerminalClose(code) && !this.stopped) {
1024
- this.scheduleReconnect();
1025
- }
1026
- reject(error);
1027
- };
1028
- socket.on("error", failBeforeHello);
1029
- socket.on("message", onMessageBeforeHello);
1030
- socket.on("close", onCloseBeforeHello);
1031
- });
1032
- }
1033
- async handleRawMessage(data) {
1034
- await this.handleFrame(decodeFrame(data.toString()));
1035
- }
1036
- async handleFrame(frame) {
1037
- if (frame.ack) {
1038
- const pending = this.pendingRequests.get(frame.ack);
1039
- if (pending) {
1040
- clearTimeout(pending.timeout);
1041
- this.pendingRequests.delete(frame.ack);
1042
- if (frame.type === "ERROR") {
1043
- pending.reject(new Error(readErrorMessage(frame.payload)));
1044
- } else {
1045
- pending.resolve(frame.payload);
1046
- }
1047
- return;
1048
- }
1049
- }
1050
- if (frame.type === "PING") {
1051
- const pong = createFrame("PONG", { clientTime: Date.now() });
1052
- pong.ack = frame.id;
1053
- this.sendFrame(pong);
1054
- return;
1055
- }
1056
- if (frame.type === "PONG" || frame.type === "HELLO" || frame.type === "RESUME_DONE") {
1057
- return;
1058
- }
1059
- await this.options.onFrame?.(frame, this);
1060
- }
1061
- startHeartbeat(helloFrame) {
1062
- this.clearHeartbeat();
1063
- const intervalMs = this.options.heartbeatIntervalMs ?? readPingInterval(helloFrame.payload) ?? 2e4;
1064
- this.heartbeatTimer = setInterval(() => {
1065
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
1066
- return;
1067
- }
1068
- this.sendFrame(createFrame("PING", { clientTime: Date.now() }));
1069
- }, intervalMs);
1070
- }
1071
- handleClose(code) {
1072
- this.clearHeartbeat();
1073
- this.rejectPending(new Error(`CoolClaw WSS connection closed: ${code}`));
1074
- this.notifyState("disconnected");
1075
- if (this.stopped || this.isTerminalClose(code)) {
1076
- return;
1077
- }
1078
- this.scheduleReconnect();
1079
- }
1080
- scheduleReconnect() {
1081
- this.clearReconnect();
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);
1087
- this.reconnectTimer = setTimeout(() => {
1088
- if (this.stopped) return;
1089
- this.connect().catch((error) => {
1090
- if (!this.stopped) {
1091
- this.scheduleReconnect();
1092
- }
1093
- });
1094
- }, delayMs);
1095
- }
1096
- notifyState(state) {
1097
- this.options.onStateChange?.(state);
1098
- }
1099
- clearHeartbeat() {
1100
- if (this.heartbeatTimer) {
1101
- clearInterval(this.heartbeatTimer);
1102
- this.heartbeatTimer = void 0;
1103
- }
1104
- }
1105
- clearReconnect() {
1106
- if (this.reconnectTimer) {
1107
- clearTimeout(this.reconnectTimer);
1108
- this.reconnectTimer = void 0;
1109
- }
1110
- }
1111
- rejectPending(error) {
1112
- for (const pending of this.pendingRequests.values()) {
1113
- clearTimeout(pending.timeout);
1114
- pending.reject(error);
1115
- }
1116
- this.pendingRequests.clear();
1117
- }
1118
- isTerminalClose(code) {
1119
- return code === 4001 || code === 4002 || code === 4003 || code === 4004 || code === 4005;
1120
- }
1121
- };
1122
- function readPingInterval(payload) {
1123
- if (!isRecord3(payload) || typeof payload.pingIntervalMs !== "number" || payload.pingIntervalMs <= 0) {
1124
- return void 0;
1125
- }
1126
- return payload.pingIntervalMs;
1127
- }
1128
- function readErrorMessage(payload) {
1129
- if (isRecord3(payload) && typeof payload.message === "string") {
1130
- return payload.message;
1131
- }
1132
- return "CoolClaw request failed";
1133
- }
1134
- function isRecord3(value) {
1135
- return typeof value === "object" && value !== null;
1136
- }
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
-
1161
- // src/channel.ts
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: {
1182
- id: "coolclaw",
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."
1189
- },
1190
- capabilities: {
1191
- chatTypes: ["direct", "group"],
1192
- media: true,
1193
- blockStreaming: true
1194
- },
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
- ]
1208
- },
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
- }
1238
- },
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
- }
1252
- },
1253
- messaging: {
1254
- normalizeTarget(raw) {
1255
- try {
1256
- const normalized = normalizeCoolclawTarget(raw);
1257
- parseCoolclawTarget(normalized);
1258
- return normalized;
1259
- } catch {
1260
- return void 0;
1261
- }
1262
- },
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
- }
1274
- }
1275
- },
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(),
1300
- ackStore,
1301
- accountKey,
1302
- onStateChange: (state) => {
1303
- statusSink({ statusState: state });
1304
- },
1305
- onFrame: async (frame, wsClient) => {
1306
- try {
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
- }
1318
- try {
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
1331
- });
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 }
1340
- );
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);
1349
- }
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 });
1416
- }
1417
- }
1418
- });
1419
- } catch (err) {
1420
- ctx.log?.error(`Frame handling error: ${err instanceof Error ? err.message : String(err)}`);
1421
- }
1422
- }
1423
- });
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`);
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`);
1448
- }
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
- }
1584
- }
1585
- }
1586
- });
1587
-
1588
- // setup-entry.ts
1589
8
  var setup_entry_default = defineSetupPluginEntry(coolclawChannelPlugin);
1590
9
  export {
1591
10
  setup_entry_default as default