@agentchatme/openclaw 0.2.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.
@@ -0,0 +1,920 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var channelCore = require('openclaw/plugin-sdk/channel-core');
6
+ var zod = require('zod');
7
+
8
+ // src/channel.setup.ts
9
+ var reconnectConfigSchema = zod.z.object({
10
+ initialBackoffMs: zod.z.number().int().min(100).max(1e4).default(1e3),
11
+ maxBackoffMs: zod.z.number().int().min(1e3).max(3e5).default(3e4),
12
+ jitterRatio: zod.z.number().min(0).max(1).default(0.2)
13
+ }).strict();
14
+ var pingConfigSchema = zod.z.object({
15
+ intervalMs: zod.z.number().int().min(5e3).max(12e4).default(3e4),
16
+ timeoutMs: zod.z.number().int().min(1e3).max(3e4).default(1e4)
17
+ }).strict();
18
+ var outboundConfigSchema = zod.z.object({
19
+ maxInFlight: zod.z.number().int().min(1).max(1e4).default(256),
20
+ sendTimeoutMs: zod.z.number().int().min(1e3).max(6e4).default(15e3)
21
+ }).strict();
22
+ var observabilityConfigSchema = zod.z.object({
23
+ logLevel: zod.z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
24
+ redactKeys: zod.z.array(zod.z.string()).default(["apiKey", "authorization", "cookie", "set-cookie"])
25
+ }).strict();
26
+ var agentHandleSchema = zod.z.string().regex(/^[a-z0-9_.-]{3,32}$/, "handle must be 3-32 chars, lowercase alphanumeric + . _ -");
27
+ var agentchatChannelConfigSchema = zod.z.object({
28
+ apiBase: zod.z.string().url().default("https://api.agentchat.me"),
29
+ apiKey: zod.z.string().min(20, "apiKey looks too short for an AgentChat API key"),
30
+ agentHandle: agentHandleSchema.optional(),
31
+ // `.prefault({})` — Zod 4 idiom: when the key is missing from input, parse
32
+ // `{}` through the inner schema so its own per-field defaults kick in.
33
+ // Using `.default({})` here fails in Zod 4 because the output type's fields
34
+ // are non-optional once the inner schema has defaults.
35
+ reconnect: reconnectConfigSchema.prefault({}),
36
+ ping: pingConfigSchema.prefault({}),
37
+ outbound: outboundConfigSchema.prefault({}),
38
+ observability: observabilityConfigSchema.prefault({})
39
+ }).strict();
40
+ function parseChannelConfig(input) {
41
+ return agentchatChannelConfigSchema.parse(input);
42
+ }
43
+
44
+ // src/setup-client.ts
45
+ var DEFAULT_API_BASE = "https://api.agentchat.me";
46
+ var DEFAULT_TIMEOUT_MS = 1e4;
47
+ async function validateApiKey(apiKey, opts = {}) {
48
+ if (!apiKey || typeof apiKey !== "string") {
49
+ return { ok: false, reason: "unauthorized", message: "API key is empty" };
50
+ }
51
+ const base = (opts.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
52
+ const url = `${base}/v1/agents/me`;
53
+ const controller = new AbortController();
54
+ const fetchImpl = opts.fetch ?? fetch;
55
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
56
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
57
+ let res;
58
+ try {
59
+ res = await fetchImpl(url, {
60
+ method: "GET",
61
+ headers: {
62
+ Authorization: `Bearer ${apiKey}`,
63
+ Accept: "application/json"
64
+ },
65
+ signal: controller.signal
66
+ });
67
+ } catch (err) {
68
+ const message = err instanceof Error ? err.message : String(err);
69
+ const unreachable = err instanceof Error && (err.name === "AbortError" || /ECONN|ETIMEDOUT|ENOTFOUND|EAI_AGAIN/i.test(message));
70
+ return {
71
+ ok: false,
72
+ reason: unreachable ? "unreachable" : "network-error",
73
+ message: `agentchat: GET /v1/agents/me failed: ${message}`
74
+ };
75
+ } finally {
76
+ clearTimeout(timer);
77
+ }
78
+ if (res.status === 401) {
79
+ return { ok: false, reason: "unauthorized", status: 401, message: "API key is invalid or revoked" };
80
+ }
81
+ if (res.status === 403) {
82
+ return { ok: false, reason: "forbidden", status: 403, message: "API key lacks permission to read /agents/me" };
83
+ }
84
+ if (res.status === 410) {
85
+ return { ok: false, reason: "deleted", status: 410, message: "Agent account has been deleted" };
86
+ }
87
+ if (res.status >= 500) {
88
+ return { ok: false, reason: "server-error", status: res.status, message: `AgentChat API returned ${res.status}` };
89
+ }
90
+ if (!res.ok) {
91
+ return {
92
+ ok: false,
93
+ reason: "server-error",
94
+ status: res.status,
95
+ message: `AgentChat API returned ${res.status}`
96
+ };
97
+ }
98
+ const body = await res.json().catch(() => null);
99
+ const email = typeof body?.email === "string" ? body.email : typeof body?.email_masked === "string" ? body.email_masked : null;
100
+ if (!body || typeof body.handle !== "string" || email === null || typeof body.created_at !== "string") {
101
+ return {
102
+ ok: false,
103
+ reason: "unexpected-shape",
104
+ status: res.status,
105
+ message: "AgentChat /agents/me returned an unrecognized shape"
106
+ };
107
+ }
108
+ return {
109
+ ok: true,
110
+ agent: {
111
+ handle: body.handle,
112
+ displayName: typeof body.display_name === "string" ? body.display_name : null,
113
+ email,
114
+ createdAt: body.created_at
115
+ }
116
+ };
117
+ }
118
+ async function registerAgentStart(input, opts = {}) {
119
+ const res = await post("/v1/register", input, opts);
120
+ if (res.kind === "network") {
121
+ return { ok: false, reason: "network-error", message: res.message };
122
+ }
123
+ if (res.kind === "timeout") {
124
+ return { ok: false, reason: "network-error", message: "request timed out" };
125
+ }
126
+ const body = res.body ?? {};
127
+ if (res.status === 200) {
128
+ if (typeof body.pending_id !== "string") {
129
+ return {
130
+ ok: false,
131
+ reason: "server-error",
132
+ status: 200,
133
+ message: "AgentChat /register returned no pending_id"
134
+ };
135
+ }
136
+ return { ok: true, pendingId: body.pending_id };
137
+ }
138
+ const code = typeof body.code === "string" ? body.code : "";
139
+ const message = typeof body.message === "string" ? body.message : `status ${res.status}`;
140
+ if (res.status === 400 && code === "INVALID_HANDLE") return { ok: false, reason: "invalid-handle", message, status: 400 };
141
+ if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
142
+ if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
143
+ if (res.status === 409 && code === "EMAIL_TAKEN") return { ok: false, reason: "email-taken", message, status: 409 };
144
+ if (res.status === 409 && code === "EMAIL_IS_OWNER") return { ok: false, reason: "email-is-owner", message, status: 409 };
145
+ if (res.status === 409 && code === "EMAIL_EXHAUSTED") return { ok: false, reason: "email-exhausted", message, status: 409 };
146
+ if (res.status === 429) {
147
+ return {
148
+ ok: false,
149
+ reason: "rate-limited",
150
+ message,
151
+ status: 429,
152
+ retryAfterSeconds: res.retryAfterSeconds
153
+ };
154
+ }
155
+ if (res.status >= 500 || code === "OTP_FAILED") return { ok: false, reason: "otp-failed", message, status: res.status };
156
+ return { ok: false, reason: "server-error", status: res.status, message };
157
+ }
158
+ async function registerAgentVerify(input, opts = {}) {
159
+ const res = await post("/v1/register/verify", { pending_id: input.pendingId, code: input.code }, opts);
160
+ if (res.kind === "network") return { ok: false, reason: "network-error", message: res.message };
161
+ if (res.kind === "timeout") return { ok: false, reason: "network-error", message: "request timed out" };
162
+ const body = res.body ?? {};
163
+ if (res.status === 201) {
164
+ const agent = body.agent;
165
+ if (typeof body.api_key !== "string" || !agent || typeof agent.handle !== "string" || typeof agent.email !== "string" || typeof agent.created_at !== "string") {
166
+ return {
167
+ ok: false,
168
+ reason: "unexpected-shape",
169
+ status: 201,
170
+ message: "AgentChat /register/verify returned an unrecognized shape"
171
+ };
172
+ }
173
+ return {
174
+ ok: true,
175
+ apiKey: body.api_key,
176
+ agent: {
177
+ handle: agent.handle,
178
+ displayName: typeof agent.display_name === "string" ? agent.display_name : null,
179
+ email: agent.email,
180
+ createdAt: agent.created_at
181
+ }
182
+ };
183
+ }
184
+ const code = typeof body.code === "string" ? body.code : "";
185
+ const message = typeof body.message === "string" ? body.message : `status ${res.status}`;
186
+ if (res.status === 400 && code === "EXPIRED") return { ok: false, reason: "expired", message, status: 400 };
187
+ if (res.status === 400 && code === "INVALID_CODE") return { ok: false, reason: "invalid-code", message, status: 400 };
188
+ if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
189
+ if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
190
+ if (res.status === 409 && code === "EMAIL_TAKEN") return { ok: false, reason: "email-taken", message, status: 409 };
191
+ if (res.status === 409 && code === "EMAIL_IS_OWNER") return { ok: false, reason: "email-is-owner", message, status: 409 };
192
+ if (res.status === 429) {
193
+ return { ok: false, reason: "rate-limited", message, status: 429, retryAfterSeconds: res.retryAfterSeconds };
194
+ }
195
+ return { ok: false, reason: "server-error", status: res.status, message };
196
+ }
197
+ async function post(path, body, opts) {
198
+ const base = (opts.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
199
+ const url = `${base}${path}`;
200
+ const controller = new AbortController();
201
+ const fetchImpl = opts.fetch ?? fetch;
202
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
203
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
204
+ try {
205
+ const res = await fetchImpl(url, {
206
+ method: "POST",
207
+ headers: { "content-type": "application/json", accept: "application/json" },
208
+ body: JSON.stringify(body),
209
+ signal: controller.signal
210
+ });
211
+ const parsed = await res.json().catch(() => null);
212
+ const retryAfterHeader = res.headers.get("retry-after");
213
+ const retryAfterSeconds = retryAfterHeader ? Number(retryAfterHeader) : void 0;
214
+ return {
215
+ kind: "http",
216
+ status: res.status,
217
+ body: parsed,
218
+ retryAfterSeconds: Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : void 0
219
+ };
220
+ } catch (err) {
221
+ if (err instanceof Error && err.name === "AbortError") return { kind: "timeout" };
222
+ return { kind: "network", message: err instanceof Error ? err.message : String(err) };
223
+ } finally {
224
+ clearTimeout(timer);
225
+ }
226
+ }
227
+
228
+ // src/setup-wizard.ts
229
+ var HANDLE_PATTERN = /^[a-z0-9_.-]{3,32}$/;
230
+ var MIN_API_KEY_LENGTH = 20;
231
+ var OTP_ATTEMPTS = 3;
232
+ function readSection(cfg) {
233
+ const channels = cfg?.channels;
234
+ const sec = channels?.[AGENTCHAT_CHANNEL_ID];
235
+ return sec && typeof sec === "object" && !Array.isArray(sec) ? sec : void 0;
236
+ }
237
+ function readAccountRaw(cfg, accountId) {
238
+ const sec = readSection(cfg);
239
+ if (!sec) return void 0;
240
+ const accounts = sec.accounts;
241
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
242
+ const entry = accounts[accountId];
243
+ if (entry && typeof entry === "object") return entry;
244
+ }
245
+ if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID) {
246
+ const { accounts: _accounts, ...rest } = sec;
247
+ return rest;
248
+ }
249
+ return void 0;
250
+ }
251
+ function readAccountApiKey(cfg, accountId) {
252
+ const raw = readAccountRaw(cfg, accountId);
253
+ const value = raw?.apiKey;
254
+ return typeof value === "string" && value.length >= MIN_API_KEY_LENGTH ? value : void 0;
255
+ }
256
+ function readAccountApiBase(cfg, accountId) {
257
+ const raw = readAccountRaw(cfg, accountId);
258
+ const value = raw?.apiBase;
259
+ return typeof value === "string" && value.length > 0 ? value : void 0;
260
+ }
261
+ function writeAccountPatch(cfg, accountId, patch) {
262
+ const channels = {
263
+ ...cfg?.channels ?? {}
264
+ };
265
+ const existing = channels[AGENTCHAT_CHANNEL_ID];
266
+ const section = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
267
+ const clean = {};
268
+ if (patch.apiKey !== void 0) clean.apiKey = patch.apiKey;
269
+ if (patch.apiBase !== void 0) clean.apiBase = patch.apiBase;
270
+ if (patch.agentHandle !== void 0) clean.agentHandle = patch.agentHandle;
271
+ const hasAccountsMap = typeof section.accounts === "object" && section.accounts !== null && !Array.isArray(section.accounts);
272
+ if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID && !hasAccountsMap) {
273
+ Object.assign(section, clean);
274
+ } else {
275
+ const accounts = {
276
+ ...section.accounts ?? {}
277
+ };
278
+ const prev = typeof accounts[accountId] === "object" && accounts[accountId] !== null ? accounts[accountId] : {};
279
+ accounts[accountId] = { ...prev, ...clean };
280
+ section.accounts = accounts;
281
+ }
282
+ channels[AGENTCHAT_CHANNEL_ID] = section;
283
+ return { ...cfg, channels };
284
+ }
285
+ function isAccountConfigured(cfg, accountId) {
286
+ return Boolean(readAccountApiKey(cfg, accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID));
287
+ }
288
+ async function promptApiKey(prompter) {
289
+ const value = await prompter.text({
290
+ message: "Paste your AgentChat API key",
291
+ placeholder: "ac_live_...",
292
+ validate: (v) => {
293
+ const trimmed = v.trim();
294
+ if (!trimmed) return "API key is required";
295
+ if (trimmed.length < MIN_API_KEY_LENGTH)
296
+ return `Looks too short (${trimmed.length} chars \u2014 expect \u2265${MIN_API_KEY_LENGTH})`;
297
+ return void 0;
298
+ }
299
+ });
300
+ return value.trim();
301
+ }
302
+ async function promptEmail(prompter) {
303
+ const value = await prompter.text({
304
+ message: "Your email address (for OTP verification)",
305
+ placeholder: "you@example.com",
306
+ validate: (v) => {
307
+ const trimmed = v.trim();
308
+ if (!trimmed) return "Email is required";
309
+ if (!/.+@.+\..+/.test(trimmed)) return "Not a valid email address";
310
+ return void 0;
311
+ }
312
+ });
313
+ return value.trim();
314
+ }
315
+ async function promptHandle(prompter, initial) {
316
+ const value = await prompter.text({
317
+ message: "Pick a handle for your agent",
318
+ placeholder: "alice",
319
+ initialValue: initial,
320
+ validate: (v) => {
321
+ const trimmed = v.trim().toLowerCase();
322
+ if (!trimmed) return "Handle is required";
323
+ if (!HANDLE_PATTERN.test(trimmed))
324
+ return "3\u201332 chars; lowercase a-z / 0-9 / dot / underscore / dash";
325
+ return void 0;
326
+ }
327
+ });
328
+ return value.trim().toLowerCase();
329
+ }
330
+ async function promptOtp(prompter) {
331
+ const value = await prompter.text({
332
+ message: "Enter the 6-digit code we emailed you",
333
+ placeholder: "123456",
334
+ validate: (v) => /^\d{6}$/.test(v.trim()) ? void 0 : "6-digit numeric code required"
335
+ });
336
+ return value.trim();
337
+ }
338
+ async function promptOptionalText(prompter, message, placeholder) {
339
+ const value = await prompter.text({
340
+ message,
341
+ placeholder,
342
+ validate: () => void 0
343
+ });
344
+ return value.trim();
345
+ }
346
+ function describeRegisterStartFailure(r) {
347
+ switch (r.reason) {
348
+ case "invalid-handle":
349
+ return "That handle is not valid. Use 3\u201332 chars, lowercase a-z / 0-9 / . _ -.";
350
+ case "handle-taken":
351
+ return "That handle is already taken. Pick another.";
352
+ case "email-taken":
353
+ return "That email already has an agent. Sign in with the existing key instead, or use a different email.";
354
+ case "email-is-owner":
355
+ return "That email is reserved. Use a different address.";
356
+ case "email-exhausted":
357
+ return "This email has hit the per-address agent limit. Use a different email or delete an old agent.";
358
+ case "rate-limited":
359
+ return r.retryAfterSeconds ? `Too many attempts. Try again in ${r.retryAfterSeconds}s.` : "Too many attempts. Try again in a few minutes.";
360
+ case "otp-failed":
361
+ return "Could not send the verification email. Check the address and retry.";
362
+ case "network-error":
363
+ return `Network error: ${r.message}`;
364
+ case "validation":
365
+ return `Validation error: ${r.message}`;
366
+ case "server-error":
367
+ return `AgentChat API error (${r.status ?? "??"}): ${r.message}`;
368
+ }
369
+ }
370
+ function describeRegisterVerifyFailure(r) {
371
+ switch (r.reason) {
372
+ case "expired":
373
+ return "That code has expired. We'll request a new one \u2014 restart registration.";
374
+ case "invalid-code":
375
+ return "Wrong code. Check the email and try again.";
376
+ case "rate-limited":
377
+ return r.retryAfterSeconds ? `Too many attempts. Wait ${r.retryAfterSeconds}s before retrying.` : "Too many attempts. Try again in a few minutes.";
378
+ case "handle-taken":
379
+ return "Someone else just took that handle. Restart and pick another.";
380
+ case "email-taken":
381
+ return "That email was registered by someone else between these steps. Start over.";
382
+ case "email-is-owner":
383
+ return "That email is reserved.";
384
+ case "network-error":
385
+ return `Network error: ${r.message}`;
386
+ case "unexpected-shape":
387
+ return `Server returned an unexpected response (${r.status ?? "??"}). Try again shortly.`;
388
+ case "validation":
389
+ return `Validation error: ${r.message}`;
390
+ case "server-error":
391
+ return `AgentChat API error (${r.status ?? "??"}): ${r.message}`;
392
+ }
393
+ }
394
+ async function runHaveKeyFlow(cfg, accountId, prompter, apiBase) {
395
+ const apiKey = await promptApiKey(prompter);
396
+ const progress = prompter.progress("Validating key against AgentChat\u2026");
397
+ const result = await validateApiKey(apiKey, { apiBase });
398
+ if (!result.ok) {
399
+ progress.stop("Key rejected.");
400
+ await prompter.note(
401
+ [
402
+ `AgentChat rejected the key (${result.reason}):`,
403
+ ` ${result.message}`,
404
+ "",
405
+ result.reason === "unauthorized" || result.reason === "deleted" ? "The key is invalid, revoked, or the agent was deleted. Grab a fresh key from agentchat.me/dashboard." : result.reason === "unreachable" || result.reason === "network-error" ? "Could not reach the API. Check your network, then run setup again." : "Try again, or pick the register flow."
406
+ ].join("\n"),
407
+ "Validation failed"
408
+ );
409
+ return runNewAccountFlow(cfg, accountId, prompter, apiBase);
410
+ }
411
+ progress.stop(`Authenticated as @${result.agent.handle}.`);
412
+ const next = writeAccountPatch(cfg, accountId, {
413
+ apiKey,
414
+ agentHandle: result.agent.handle,
415
+ ...apiBase ? { apiBase } : {}
416
+ });
417
+ await prompter.note(
418
+ [
419
+ `Connected to AgentChat as @${result.agent.handle}.`,
420
+ `Email: ${result.agent.email}`,
421
+ result.agent.displayName ? `Display name: ${result.agent.displayName}` : void 0
422
+ ].filter((v) => Boolean(v)).join("\n"),
423
+ "AgentChat configured"
424
+ );
425
+ return { cfg: next };
426
+ }
427
+ async function runRegisterFlow(cfg, accountId, prompter, apiBase) {
428
+ await prompter.note(
429
+ [
430
+ "We will email a 6-digit code to verify ownership of this address.",
431
+ "The email is hashed server-side and used only for account recovery."
432
+ ].join("\n"),
433
+ "New-agent registration"
434
+ );
435
+ const email = await promptEmail(prompter);
436
+ const handle = await promptHandle(prompter);
437
+ const displayName = await promptOptionalText(
438
+ prompter,
439
+ "Display name (optional, press Enter to skip)",
440
+ handle
441
+ );
442
+ const description = await promptOptionalText(
443
+ prompter,
444
+ "Short description of your agent (optional)",
445
+ "Research assistant that summarises papers"
446
+ );
447
+ const startProgress = prompter.progress("Requesting OTP\u2026");
448
+ const start = await registerAgentStart(
449
+ {
450
+ email,
451
+ handle,
452
+ displayName: displayName || void 0,
453
+ description: description || void 0
454
+ },
455
+ { apiBase }
456
+ );
457
+ if (!start.ok) {
458
+ startProgress.stop("Registration could not start.");
459
+ await prompter.note(describeRegisterStartFailure(start), "Registration failed");
460
+ const retry = await prompter.confirm({
461
+ message: start.reason === "handle-taken" || start.reason === "invalid-handle" ? "Try a different handle?" : "Try again?",
462
+ initialValue: true
463
+ });
464
+ if (!retry) return { cfg };
465
+ return runRegisterFlow(cfg, accountId, prompter, apiBase);
466
+ }
467
+ startProgress.stop(`OTP sent to ${email}.`);
468
+ const pendingId = start.pendingId;
469
+ for (let attempt = 1; attempt <= OTP_ATTEMPTS; attempt += 1) {
470
+ const code = await promptOtp(prompter);
471
+ const verifyProgress = prompter.progress("Verifying code\u2026");
472
+ const verify = await registerAgentVerify({ pendingId, code }, { apiBase });
473
+ if (verify.ok) {
474
+ verifyProgress.stop(`Verified. Agent @${verify.agent.handle} created.`);
475
+ const next = writeAccountPatch(cfg, accountId, {
476
+ apiKey: verify.apiKey,
477
+ agentHandle: verify.agent.handle,
478
+ ...apiBase ? { apiBase } : {}
479
+ });
480
+ await prompter.note(
481
+ [
482
+ "Your AgentChat API key has been written to config.",
483
+ "",
484
+ ` handle: @${verify.agent.handle}`,
485
+ ` email: ${verify.agent.email}`,
486
+ "",
487
+ "Keep the key safe \u2014 it authenticates sends as this agent.",
488
+ "You can rotate it any time from agentchat.me/dashboard."
489
+ ].join("\n"),
490
+ "Registration complete"
491
+ );
492
+ return { cfg: next };
493
+ }
494
+ verifyProgress.stop(`Attempt ${attempt}/${OTP_ATTEMPTS} failed (${verify.reason}).`);
495
+ await prompter.note(describeRegisterVerifyFailure(verify), "Code rejected");
496
+ if (verify.reason === "expired") {
497
+ return runRegisterFlow(cfg, accountId, prompter, apiBase);
498
+ }
499
+ if (verify.reason !== "invalid-code") {
500
+ const retry = await prompter.confirm({
501
+ message: "Start over with different details?",
502
+ initialValue: true
503
+ });
504
+ if (!retry) return { cfg };
505
+ return runRegisterFlow(cfg, accountId, prompter, apiBase);
506
+ }
507
+ }
508
+ await prompter.note(
509
+ "Too many invalid codes. Restart setup to request a new one.",
510
+ "Registration aborted"
511
+ );
512
+ return { cfg };
513
+ }
514
+ async function runEditFlow(cfg, accountId, prompter) {
515
+ const currentKey = readAccountApiKey(cfg, accountId);
516
+ const currentBase = readAccountApiBase(cfg, accountId);
517
+ const choice = await prompter.select({
518
+ message: "AgentChat is already configured. What would you like to do?",
519
+ options: [
520
+ {
521
+ value: "validate",
522
+ label: "Re-validate the current key",
523
+ hint: "Hit /agents/me to confirm it still authenticates"
524
+ },
525
+ {
526
+ value: "rotate",
527
+ label: "Rotate to a new API key",
528
+ hint: "Paste a freshly generated key or register a new agent"
529
+ },
530
+ {
531
+ value: "change-base",
532
+ label: "Change API base URL",
533
+ hint: "Only for self-hosted AgentChat deployments"
534
+ },
535
+ { value: "skip", label: "Leave as is", hint: "No changes" }
536
+ ],
537
+ initialValue: "validate"
538
+ });
539
+ if (choice === "skip") return { cfg };
540
+ if (choice === "validate") {
541
+ if (!currentKey) {
542
+ await prompter.note("No API key is currently set.", "Nothing to validate");
543
+ return runNewAccountFlow(cfg, accountId, prompter, currentBase);
544
+ }
545
+ const progress = prompter.progress("Probing /agents/me\u2026");
546
+ const result = await validateApiKey(currentKey, { apiBase: currentBase });
547
+ if (result.ok) {
548
+ progress.stop(`Still authenticated as @${result.agent.handle}.`);
549
+ return { cfg };
550
+ }
551
+ progress.stop("Key no longer works.");
552
+ await prompter.note(`${result.message} [reason=${result.reason}]`, "Re-validation failed");
553
+ const doRotate = await prompter.confirm({
554
+ message: "Rotate to a new key now?",
555
+ initialValue: true
556
+ });
557
+ if (doRotate) return runNewAccountFlow(cfg, accountId, prompter, currentBase);
558
+ return { cfg };
559
+ }
560
+ if (choice === "rotate") {
561
+ return runNewAccountFlow(cfg, accountId, prompter, currentBase);
562
+ }
563
+ const nextBase = await promptOptionalText(
564
+ prompter,
565
+ "New API base URL (blank to reset to default)",
566
+ currentBase ?? "https://api.agentchat.me"
567
+ );
568
+ if (nextBase) {
569
+ try {
570
+ const url = new URL(nextBase);
571
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
572
+ await prompter.note("API base must use http:// or https://. Not updated.", "Ignored");
573
+ return { cfg };
574
+ }
575
+ } catch {
576
+ await prompter.note("Not a valid URL. API base not updated.", "Ignored");
577
+ return { cfg };
578
+ }
579
+ }
580
+ const next = writeAccountPatch(cfg, accountId, { apiBase: nextBase || void 0 });
581
+ await prompter.note(
582
+ nextBase ? `API base set to ${nextBase}` : "API base reset to default",
583
+ "Updated"
584
+ );
585
+ return { cfg: next };
586
+ }
587
+ async function runNewAccountFlow(cfg, accountId, prompter, apiBase) {
588
+ const choice = await prompter.select({
589
+ message: "How do you want to connect this agent?",
590
+ options: [
591
+ {
592
+ value: "register",
593
+ label: "Register a new agent (email + OTP)",
594
+ hint: "Mint a fresh API key \u2014 ~60 seconds"
595
+ },
596
+ {
597
+ value: "have-key",
598
+ label: "I already have an API key",
599
+ hint: "Paste ac_live_..."
600
+ }
601
+ ],
602
+ initialValue: "register"
603
+ });
604
+ if (choice === "have-key") return runHaveKeyFlow(cfg, accountId, prompter, apiBase);
605
+ return runRegisterFlow(cfg, accountId, prompter, apiBase);
606
+ }
607
+ var agentchatSetupWizard = {
608
+ channel: AGENTCHAT_CHANNEL_ID,
609
+ resolveShouldPromptAccountIds: () => false,
610
+ status: {
611
+ configuredLabel: "connected",
612
+ unconfiguredLabel: "needs API key",
613
+ configuredHint: "configured",
614
+ unconfiguredHint: "needs agent credentials",
615
+ configuredScore: 2,
616
+ unconfiguredScore: 0,
617
+ resolveConfigured: ({ cfg, accountId }) => isAccountConfigured(cfg, accountId),
618
+ resolveStatusLines: async ({ cfg, accountId, configured }) => {
619
+ if (!configured) return ["AgentChat: needs API key \u2014 run setup to register or paste one"];
620
+ const id = accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID;
621
+ const key = readAccountApiKey(cfg, id);
622
+ const apiBase = readAccountApiBase(cfg, id);
623
+ if (!key) return ["AgentChat: configured (no key visible)"];
624
+ try {
625
+ const result = await validateApiKey(key, { apiBase, timeoutMs: 3e3 });
626
+ if (result.ok) return [`AgentChat: connected as @${result.agent.handle}`];
627
+ return [`AgentChat: configured (live probe failed: ${result.reason})`];
628
+ } catch {
629
+ return ["AgentChat: configured (live probe unreachable)"];
630
+ }
631
+ }
632
+ },
633
+ introNote: {
634
+ title: "AgentChat",
635
+ lines: [
636
+ "A messaging platform for AI agents \u2014 handle-based routing, realtime WebSocket,",
637
+ "typed error taxonomy, idempotent sends.",
638
+ "",
639
+ "You can paste an existing API key, or register a new agent inline (email + OTP)."
640
+ ]
641
+ },
642
+ prepare: async ({ cfg, accountId, credentialValues }) => {
643
+ const flow = isAccountConfigured(cfg, accountId) ? "edit" : "new";
644
+ return { credentialValues: { ...credentialValues, _flow: flow } };
645
+ },
646
+ credentials: [],
647
+ finalize: async ({ cfg, accountId, prompter, credentialValues }) => {
648
+ const rawFlow = credentialValues._flow;
649
+ const flow = rawFlow === "edit" ? "edit" : "new";
650
+ const apiBase = readAccountApiBase(cfg, accountId);
651
+ return flow === "edit" ? await runEditFlow(cfg, accountId, prompter) : await runNewAccountFlow(cfg, accountId, prompter, apiBase);
652
+ },
653
+ completionNote: {
654
+ title: "AgentChat is ready",
655
+ lines: [
656
+ "The channel runtime will auto-connect on the next `openclaw` boot.",
657
+ "Messages addressed to your handle arrive over WebSocket; sends go out",
658
+ "over HTTPS with at-least-once + idempotent semantics."
659
+ ]
660
+ },
661
+ disable: (cfg) => {
662
+ const channels = {
663
+ ...cfg?.channels ?? {}
664
+ };
665
+ const existing = channels[AGENTCHAT_CHANNEL_ID];
666
+ const section = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
667
+ section.enabled = false;
668
+ channels[AGENTCHAT_CHANNEL_ID] = section;
669
+ return { ...cfg, channels };
670
+ }
671
+ };
672
+
673
+ // src/channel.ts
674
+ var AGENTCHAT_CHANNEL_ID = "agentchat";
675
+ var AGENTCHAT_DEFAULT_ACCOUNT_ID = "default";
676
+ var MIN_API_KEY_LENGTH2 = 20;
677
+ function readChannelSection(cfg) {
678
+ const channels = cfg?.channels;
679
+ const section = channels?.[AGENTCHAT_CHANNEL_ID];
680
+ return section && typeof section === "object" && !Array.isArray(section) ? section : void 0;
681
+ }
682
+ function readAccountRaw2(section, accountId) {
683
+ if (!section) return void 0;
684
+ const { accounts } = section;
685
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
686
+ const entry = accounts[accountId];
687
+ if (entry && typeof entry === "object") return entry;
688
+ }
689
+ if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID) {
690
+ const { accounts: _accounts, ...rest } = section;
691
+ return rest;
692
+ }
693
+ return void 0;
694
+ }
695
+ function splitEnabledFromRaw(raw) {
696
+ if (!raw) return { enabled: true, forParse: void 0 };
697
+ const { enabled, ...rest } = raw;
698
+ return { enabled: enabled !== false, forParse: rest };
699
+ }
700
+ function isApiKeyPresent(value) {
701
+ return typeof value === "string" && value.length >= MIN_API_KEY_LENGTH2;
702
+ }
703
+ var uiHints = {
704
+ apiKey: {
705
+ label: "AgentChat API key",
706
+ placeholder: "ac_live_...",
707
+ sensitive: true,
708
+ help: "Obtain from AgentChat dashboard \u2192 Settings \u2192 API keys."
709
+ },
710
+ apiBase: {
711
+ label: "API base URL",
712
+ placeholder: "https://api.agentchat.me",
713
+ help: "Override only when targeting a self-hosted AgentChat instance.",
714
+ advanced: true
715
+ },
716
+ agentHandle: {
717
+ label: "Agent handle",
718
+ placeholder: "my-agent",
719
+ help: "3\u201332 chars, lowercase alphanumeric plus . _ -"
720
+ },
721
+ reconnect: { label: "Reconnect backoff", advanced: true },
722
+ ping: { label: "WebSocket ping cadence", advanced: true },
723
+ outbound: { label: "Outbound send tuning", advanced: true },
724
+ observability: { label: "Logs & metrics", advanced: true }
725
+ };
726
+ var agentchatPlugin = {
727
+ id: AGENTCHAT_CHANNEL_ID,
728
+ meta: {
729
+ id: AGENTCHAT_CHANNEL_ID,
730
+ label: "AgentChat",
731
+ selectionLabel: "AgentChat (messaging platform for agents)",
732
+ detailLabel: "AgentChat",
733
+ docsPath: "/channels/agentchat",
734
+ docsLabel: "agentchat",
735
+ blurb: "connect your agent to the AgentChat messaging platform \u2014 handles, contacts, groups, presence, attachments.",
736
+ systemImage: "message",
737
+ markdownCapable: true,
738
+ order: 100
739
+ },
740
+ capabilities: {
741
+ chatTypes: ["direct", "group"],
742
+ media: true,
743
+ reactions: false,
744
+ edit: false,
745
+ unsend: true,
746
+ reply: true,
747
+ threads: false,
748
+ nativeCommands: false
749
+ },
750
+ reload: {
751
+ configPrefixes: [`channels.${AGENTCHAT_CHANNEL_ID}.`]
752
+ },
753
+ configSchema: channelCore.buildChannelConfigSchema(agentchatChannelConfigSchema, { uiHints }),
754
+ setupWizard: agentchatSetupWizard,
755
+ config: {
756
+ listAccountIds(cfg) {
757
+ const section = readChannelSection(cfg);
758
+ if (!section) return [];
759
+ const { accounts } = section;
760
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
761
+ const ids = Object.keys(accounts);
762
+ if (ids.length > 0) return ids;
763
+ }
764
+ const hasFlatFields = typeof section.apiKey === "string" || typeof section.apiBase === "string" || typeof section.agentHandle === "string";
765
+ return hasFlatFields ? [AGENTCHAT_DEFAULT_ACCOUNT_ID] : [];
766
+ },
767
+ resolveAccount(cfg, accountId) {
768
+ const id = accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID;
769
+ const section = readChannelSection(cfg);
770
+ const raw = readAccountRaw2(section, id);
771
+ const { enabled, forParse } = splitEnabledFromRaw(raw);
772
+ let config = null;
773
+ let parseError = null;
774
+ if (forParse && Object.keys(forParse).length > 0) {
775
+ try {
776
+ config = parseChannelConfig(forParse);
777
+ } catch (e) {
778
+ parseError = e instanceof Error ? e.message : String(e);
779
+ }
780
+ }
781
+ const configured = Boolean(config && isApiKeyPresent(config.apiKey));
782
+ return { accountId: id, enabled, configured, config, parseError };
783
+ },
784
+ defaultAccountId() {
785
+ return AGENTCHAT_DEFAULT_ACCOUNT_ID;
786
+ },
787
+ isEnabled(account) {
788
+ return account.enabled;
789
+ },
790
+ disabledReason(account) {
791
+ return account.enabled ? "" : "channel disabled via channels.agentchat.enabled=false";
792
+ },
793
+ isConfigured(account) {
794
+ return account.configured;
795
+ },
796
+ unconfiguredReason(account) {
797
+ if (account.parseError) return `config invalid: ${account.parseError}`;
798
+ if (!account.config) return "missing channels.agentchat configuration";
799
+ if (!isApiKeyPresent(account.config.apiKey)) return "missing or too-short channels.agentchat.apiKey";
800
+ return "";
801
+ },
802
+ hasConfiguredState({ cfg }) {
803
+ const section = readChannelSection(cfg);
804
+ if (!section) return false;
805
+ const { accounts } = section;
806
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
807
+ for (const entry of Object.values(accounts)) {
808
+ if (entry && typeof entry === "object") {
809
+ const candidate = entry.apiKey;
810
+ if (isApiKeyPresent(candidate)) return true;
811
+ }
812
+ }
813
+ }
814
+ return isApiKeyPresent(section.apiKey);
815
+ },
816
+ describeAccount(account) {
817
+ return {
818
+ accountId: account.accountId,
819
+ enabled: account.enabled,
820
+ configured: account.configured,
821
+ linked: account.configured
822
+ };
823
+ }
824
+ },
825
+ setup: {
826
+ /**
827
+ * Pre-write gate: cheap, sync check that the caller supplied an API key
828
+ * that's at least plausibly shaped. Real authentication happens in
829
+ * `afterAccountConfigWritten` via a live /agents/me probe.
830
+ */
831
+ validateInput({ input }) {
832
+ if (typeof input.token !== "string" || input.token.trim().length === 0) {
833
+ return "apiKey is required \u2014 pass via --token or run the register flow";
834
+ }
835
+ if (input.token.length < MIN_API_KEY_LENGTH2) {
836
+ return `apiKey looks too short (got ${input.token.length} chars, expect \u2265${MIN_API_KEY_LENGTH2})`;
837
+ }
838
+ if (typeof input.url === "string" && input.url.length > 0) {
839
+ try {
840
+ const parsed = new URL(input.url);
841
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
842
+ return "apiBase must be http(s) \u2014 e.g. https://api.agentchat.me";
843
+ }
844
+ } catch {
845
+ return "apiBase is not a valid URL";
846
+ }
847
+ }
848
+ return null;
849
+ },
850
+ applyAccountConfig({ cfg, accountId, input }) {
851
+ const channels = {
852
+ ...cfg?.channels ?? {}
853
+ };
854
+ const currentSection = channels[AGENTCHAT_CHANNEL_ID];
855
+ const section = currentSection && typeof currentSection === "object" && !Array.isArray(currentSection) ? { ...currentSection } : {};
856
+ const patch = {};
857
+ if (typeof input.token === "string" && input.token.length > 0) patch.apiKey = input.token;
858
+ if (typeof input.url === "string" && input.url.length > 0) patch.apiBase = input.url;
859
+ if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID && !section.accounts) {
860
+ Object.assign(section, patch);
861
+ } else {
862
+ const accounts = {
863
+ ...section.accounts ?? {}
864
+ };
865
+ const prevAccount = typeof accounts[accountId] === "object" && accounts[accountId] !== null ? accounts[accountId] : {};
866
+ accounts[accountId] = { ...prevAccount, ...patch };
867
+ section.accounts = accounts;
868
+ }
869
+ channels[AGENTCHAT_CHANNEL_ID] = section;
870
+ return { ...cfg, channels };
871
+ },
872
+ /**
873
+ * Post-write probe: call `GET /v1/agents/me` with the key we just wrote
874
+ * so the operator sees a clear "✓ authenticated as @handle" on success
875
+ * — or a specific failure reason (invalid, revoked, unreachable) they
876
+ * can act on *before* the runtime starts flapping reconnects in prod.
877
+ *
878
+ * Never throws: setup-time UX is "warn and proceed". If the probe can't
879
+ * reach the API (airgapped CI, corp proxy during first boot), we still
880
+ * want the config written so the user can retry without reconfiguring.
881
+ */
882
+ async afterAccountConfigWritten({ cfg, accountId, input, runtime }) {
883
+ const apiKey = typeof input.token === "string" ? input.token : void 0;
884
+ if (!apiKey) return;
885
+ const apiBase = typeof input.url === "string" && input.url.length > 0 ? input.url : void 0;
886
+ const logger = runtime?.logger;
887
+ try {
888
+ const result = await validateApiKey(apiKey, { apiBase });
889
+ if (result.ok) {
890
+ logger?.info?.(
891
+ `[agentchat:${accountId}] authenticated as @${result.agent.handle} (${result.agent.email})`
892
+ );
893
+ } else {
894
+ logger?.warn?.(
895
+ `[agentchat:${accountId}] api key did not pass live check (${result.reason}): ${result.message}`
896
+ );
897
+ }
898
+ } catch (err) {
899
+ logger?.warn?.(
900
+ `[agentchat:${accountId}] live key validation failed: ${err instanceof Error ? err.message : String(err)}`
901
+ );
902
+ }
903
+ }
904
+ }
905
+ };
906
+ channelCore.defineChannelPluginEntry({
907
+ id: AGENTCHAT_CHANNEL_ID,
908
+ name: "AgentChat",
909
+ description: "Connect OpenClaw agents to the AgentChat messaging platform.",
910
+ plugin: agentchatPlugin
911
+ });
912
+
913
+ // src/channel.setup.ts
914
+ var agentchatSetupEntry = channelCore.defineSetupPluginEntry(agentchatPlugin);
915
+
916
+ exports.agentchatSetupEntry = agentchatSetupEntry;
917
+ exports.agentchatSetupPlugin = agentchatPlugin;
918
+ exports.default = agentchatSetupEntry;
919
+ //# sourceMappingURL=setup-entry.cjs.map
920
+ //# sourceMappingURL=setup-entry.cjs.map