@agentchatme/openclaw 0.2.0 → 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.
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var channelCore = require('openclaw/plugin-sdk/channel-core');
6
+ var setup = require('openclaw/plugin-sdk/setup');
6
7
  var zod = require('zod');
7
8
  var pino = require('pino');
8
9
  var ws = require('ws');
@@ -12,39 +13,62 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
  var pino__default = /*#__PURE__*/_interopDefault(pino);
13
14
 
14
15
  // src/channel.ts
15
- var reconnectConfigSchema = zod.z.object({
16
- initialBackoffMs: zod.z.number().int().min(100).max(1e4).default(1e3),
17
- maxBackoffMs: zod.z.number().int().min(1e3).max(3e5).default(3e4),
18
- jitterRatio: zod.z.number().min(0).max(1).default(0.2)
19
- }).strict();
20
- var pingConfigSchema = zod.z.object({
21
- intervalMs: zod.z.number().int().min(5e3).max(12e4).default(3e4),
22
- timeoutMs: zod.z.number().int().min(1e3).max(3e4).default(1e4)
23
- }).strict();
24
- var outboundConfigSchema = zod.z.object({
25
- maxInFlight: zod.z.number().int().min(1).max(1e4).default(256),
26
- sendTimeoutMs: zod.z.number().int().min(1e3).max(6e4).default(15e3)
27
- }).strict();
28
- var observabilityConfigSchema = zod.z.object({
29
- logLevel: zod.z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
30
- redactKeys: zod.z.array(zod.z.string()).default(["apiKey", "authorization", "cookie", "set-cookie"])
31
- }).strict();
32
- var agentHandleSchema = zod.z.string().regex(/^[a-z0-9_.-]{3,32}$/, "handle must be 3-32 chars, lowercase alphanumeric + . _ -");
33
- var agentchatChannelConfigSchema = zod.z.object({
34
- apiBase: zod.z.string().url().default("https://api.agentchat.me"),
35
- apiKey: zod.z.string().min(20, "apiKey looks too short for an AgentChat API key"),
36
- agentHandle: agentHandleSchema.optional(),
37
- // `.prefault({})` — Zod 4 idiom: when the key is missing from input, parse
38
- // `{}` through the inner schema so its own per-field defaults kick in.
39
- // Using `.default({})` here fails in Zod 4 because the output type's fields
40
- // are non-optional once the inner schema has defaults.
41
- reconnect: reconnectConfigSchema.prefault({}),
42
- ping: pingConfigSchema.prefault({}),
43
- outbound: outboundConfigSchema.prefault({}),
44
- observability: observabilityConfigSchema.prefault({})
45
- }).strict();
46
- function parseChannelConfig(input) {
47
- return agentchatChannelConfigSchema.parse(input);
16
+
17
+ // src/channel-account.ts
18
+ var AGENTCHAT_CHANNEL_ID = "agentchat";
19
+ var AGENTCHAT_DEFAULT_ACCOUNT_ID = "default";
20
+ var MIN_API_KEY_LENGTH = 20;
21
+ function readChannelSection(cfg) {
22
+ const channels = cfg?.channels;
23
+ const section = channels?.[AGENTCHAT_CHANNEL_ID];
24
+ return section && typeof section === "object" && !Array.isArray(section) ? section : void 0;
25
+ }
26
+ function readAccountRaw(section, accountId) {
27
+ if (!section) return void 0;
28
+ const { accounts } = section;
29
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
30
+ const entry = accounts[accountId];
31
+ if (entry && typeof entry === "object") return entry;
32
+ }
33
+ if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID) {
34
+ const { accounts: _accounts, ...rest } = section;
35
+ return rest;
36
+ }
37
+ return void 0;
38
+ }
39
+ function splitEnabledFromRaw(raw) {
40
+ if (!raw) return { enabled: true, forParse: void 0 };
41
+ const { enabled, ...rest } = raw;
42
+ return { enabled: enabled !== false, forParse: rest };
43
+ }
44
+ function isApiKeyPresent(value) {
45
+ return typeof value === "string" && value.length >= MIN_API_KEY_LENGTH;
46
+ }
47
+ function readAgentchatConfigField(cfg, accountId, field) {
48
+ const section = readChannelSection(cfg);
49
+ const raw = readAccountRaw(section, accountId);
50
+ if (!raw) return void 0;
51
+ const value = raw[field];
52
+ return typeof value === "string" && value.length > 0 ? value : void 0;
53
+ }
54
+ function applyAgentchatAccountPatch(cfg, accountId, patch) {
55
+ const channels = {
56
+ ...cfg?.channels ?? {}
57
+ };
58
+ const currentSection = channels[AGENTCHAT_CHANNEL_ID];
59
+ const section = currentSection && typeof currentSection === "object" && !Array.isArray(currentSection) ? { ...currentSection } : {};
60
+ if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID && !section.accounts) {
61
+ Object.assign(section, patch);
62
+ } else {
63
+ const accounts = {
64
+ ...section.accounts ?? {}
65
+ };
66
+ const prevAccount = typeof accounts[accountId] === "object" && accounts[accountId] !== null ? accounts[accountId] : {};
67
+ accounts[accountId] = { ...prevAccount, ...patch };
68
+ section.accounts = accounts;
69
+ }
70
+ channels[AGENTCHAT_CHANNEL_ID] = section;
71
+ return { ...cfg ?? {}, channels };
48
72
  }
49
73
 
50
74
  // src/errors.ts
@@ -188,7 +212,6 @@ async function registerAgentStart(input, opts = {}) {
188
212
  if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
189
213
  if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
190
214
  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
215
  if (res.status === 409 && code === "EMAIL_EXHAUSTED") return { ok: false, reason: "email-exhausted", message, status: 409 };
193
216
  if (res.status === 429) {
194
217
  return {
@@ -235,7 +258,6 @@ async function registerAgentVerify(input, opts = {}) {
235
258
  if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
236
259
  if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
237
260
  if (res.status === 409 && code === "EMAIL_TAKEN") return { ok: false, reason: "email-taken", message, status: 409 };
238
- if (res.status === 409 && code === "EMAIL_IS_OWNER") return { ok: false, reason: "email-is-owner", message, status: 409 };
239
261
  if (res.status === 429) {
240
262
  return { ok: false, reason: "rate-limited", message, status: 429, retryAfterSeconds: res.retryAfterSeconds };
241
263
  }
@@ -280,487 +302,551 @@ async function assertApiKeyValid(apiKey, opts = {}) {
280
302
  });
281
303
  }
282
304
 
283
- // src/setup-wizard.ts
284
- var HANDLE_PATTERN = /^[a-z0-9_.-]{3,32}$/;
285
- var MIN_API_KEY_LENGTH = 20;
286
- var OTP_ATTEMPTS = 3;
287
- function readSection(cfg) {
288
- const channels = cfg?.channels;
289
- const sec = channels?.[AGENTCHAT_CHANNEL_ID];
290
- return sec && typeof sec === "object" && !Array.isArray(sec) ? sec : void 0;
291
- }
292
- function readAccountRaw(cfg, accountId) {
293
- const sec = readSection(cfg);
294
- if (!sec) return void 0;
295
- const accounts = sec.accounts;
296
- if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
297
- const entry = accounts[accountId];
298
- if (entry && typeof entry === "object") return entry;
299
- }
300
- if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID) {
301
- const { accounts: _accounts, ...rest } = sec;
302
- return rest;
303
- }
304
- return void 0;
305
- }
306
- function readAccountApiKey(cfg, accountId) {
307
- const raw = readAccountRaw(cfg, accountId);
308
- const value = raw?.apiKey;
309
- return typeof value === "string" && value.length >= MIN_API_KEY_LENGTH ? value : void 0;
305
+ // src/channel.wizard.ts
306
+ var JUST_REGISTERED_SENTINEL = "_agentchatJustRegistered";
307
+ var HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
308
+ var HANDLE_MIN_LENGTH = 3;
309
+ var HANDLE_MAX_LENGTH = 30;
310
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
311
+ function isValidHandleShape(value) {
312
+ return value.length >= HANDLE_MIN_LENGTH && value.length <= HANDLE_MAX_LENGTH && HANDLE_PATTERN.test(value);
310
313
  }
311
- function readAccountApiBase(cfg, accountId) {
312
- const raw = readAccountRaw(cfg, accountId);
313
- const value = raw?.apiBase;
314
- return typeof value === "string" && value.length > 0 ? value : void 0;
315
- }
316
- function writeAccountPatch(cfg, accountId, patch) {
317
- const channels = {
318
- ...cfg?.channels ?? {}
319
- };
320
- const existing = channels[AGENTCHAT_CHANNEL_ID];
321
- const section = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
322
- const clean = {};
323
- if (patch.apiKey !== void 0) clean.apiKey = patch.apiKey;
324
- if (patch.apiBase !== void 0) clean.apiBase = patch.apiBase;
325
- if (patch.agentHandle !== void 0) clean.agentHandle = patch.agentHandle;
326
- const hasAccountsMap = typeof section.accounts === "object" && section.accounts !== null && !Array.isArray(section.accounts);
327
- if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID && !hasAccountsMap) {
328
- Object.assign(section, clean);
329
- } else {
330
- const accounts = {
331
- ...section.accounts ?? {}
332
- };
333
- const prev = typeof accounts[accountId] === "object" && accounts[accountId] !== null ? accounts[accountId] : {};
334
- accounts[accountId] = { ...prev, ...clean };
335
- section.accounts = accounts;
336
- }
337
- channels[AGENTCHAT_CHANNEL_ID] = section;
338
- return { ...cfg, channels };
339
- }
340
- function isAccountConfigured(cfg, accountId) {
341
- return Boolean(readAccountApiKey(cfg, accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID));
342
- }
343
- async function promptApiKey(prompter) {
344
- const value = await prompter.text({
345
- message: "Paste your AgentChat API key",
346
- placeholder: "ac_live_...",
347
- validate: (v) => {
348
- const trimmed = v.trim();
349
- if (!trimmed) return "API key is required";
350
- if (trimmed.length < MIN_API_KEY_LENGTH)
351
- return `Looks too short (${trimmed.length} chars \u2014 expect \u2265${MIN_API_KEY_LENGTH})`;
352
- return void 0;
353
- }
354
- });
355
- return value.trim();
314
+ var OTP_PATTERN = /^\d{6}$/;
315
+ function hasConfiguredKey(cfg, accountId) {
316
+ return isApiKeyPresent(readAgentchatConfigField(cfg, accountId, "apiKey"));
356
317
  }
318
+ var MAX_START_RETRIES = 5;
357
319
  async function promptEmail(prompter) {
358
- const value = await prompter.text({
359
- message: "Your email address (for OTP verification)",
320
+ return (await prompter.text({
321
+ message: "Email address (for the verification code)",
360
322
  placeholder: "you@example.com",
361
- validate: (v) => {
362
- const trimmed = v.trim();
323
+ validate: (value) => {
324
+ const trimmed = value.trim();
363
325
  if (!trimmed) return "Email is required";
364
- if (!/.+@.+\..+/.test(trimmed)) return "Not a valid email address";
326
+ if (!EMAIL_PATTERN.test(trimmed)) return "That does not look like a valid email address";
365
327
  return void 0;
366
328
  }
367
- });
368
- return value.trim();
329
+ })).trim();
369
330
  }
370
- async function promptHandle(prompter, initial) {
371
- const value = await prompter.text({
372
- message: "Pick a handle for your agent",
373
- placeholder: "alice",
374
- initialValue: initial,
375
- validate: (v) => {
376
- const trimmed = v.trim().toLowerCase();
331
+ async function promptHandle(prompter) {
332
+ return (await prompter.text({
333
+ message: "Choose a handle (your @name on AgentChat)",
334
+ placeholder: "my-agent",
335
+ validate: (value) => {
336
+ const trimmed = value.trim();
377
337
  if (!trimmed) return "Handle is required";
378
- if (!HANDLE_PATTERN.test(trimmed))
379
- return "3\u201332 chars; lowercase a-z / 0-9 / dot / underscore / dash";
338
+ if (!isValidHandleShape(trimmed)) {
339
+ return "Handle must be 3\u201330 chars \u2014 lowercase letters/digits/hyphens; must start with a letter";
340
+ }
380
341
  return void 0;
381
342
  }
382
- });
383
- return value.trim().toLowerCase();
384
- }
385
- async function promptOtp(prompter) {
386
- const value = await prompter.text({
387
- message: "Enter the 6-digit code we emailed you",
388
- placeholder: "123456",
389
- validate: (v) => /^\d{6}$/.test(v.trim()) ? void 0 : "6-digit numeric code required"
390
- });
391
- return value.trim();
343
+ })).trim();
392
344
  }
393
- async function promptOptionalText(prompter, message, placeholder) {
394
- const value = await prompter.text({
395
- message,
396
- placeholder,
345
+ async function promptDisplayName(prompter) {
346
+ return (await prompter.text({
347
+ message: "Display name (optional \u2014 shown next to your handle)",
348
+ placeholder: "",
397
349
  validate: () => void 0
398
- });
399
- return value.trim();
400
- }
401
- function describeRegisterStartFailure(r) {
402
- switch (r.reason) {
403
- case "invalid-handle":
404
- return "That handle is not valid. Use 3\u201332 chars, lowercase a-z / 0-9 / . _ -.";
405
- case "handle-taken":
406
- return "That handle is already taken. Pick another.";
407
- case "email-taken":
408
- return "That email already has an agent. Sign in with the existing key instead, or use a different email.";
409
- case "email-is-owner":
410
- return "That email is reserved. Use a different address.";
411
- case "email-exhausted":
412
- return "This email has hit the per-address agent limit. Use a different email or delete an old agent.";
413
- case "rate-limited":
414
- return r.retryAfterSeconds ? `Too many attempts. Try again in ${r.retryAfterSeconds}s.` : "Too many attempts. Try again in a few minutes.";
415
- case "otp-failed":
416
- return "Could not send the verification email. Check the address and retry.";
417
- case "network-error":
418
- return `Network error: ${r.message}`;
419
- case "validation":
420
- return `Validation error: ${r.message}`;
421
- case "server-error":
422
- return `AgentChat API error (${r.status ?? "??"}): ${r.message}`;
423
- }
424
- }
425
- function describeRegisterVerifyFailure(r) {
426
- switch (r.reason) {
427
- case "expired":
428
- return "That code has expired. We'll request a new one \u2014 restart registration.";
429
- case "invalid-code":
430
- return "Wrong code. Check the email and try again.";
431
- case "rate-limited":
432
- return r.retryAfterSeconds ? `Too many attempts. Wait ${r.retryAfterSeconds}s before retrying.` : "Too many attempts. Try again in a few minutes.";
433
- case "handle-taken":
434
- return "Someone else just took that handle. Restart and pick another.";
435
- case "email-taken":
436
- return "That email was registered by someone else between these steps. Start over.";
437
- case "email-is-owner":
438
- return "That email is reserved.";
439
- case "network-error":
440
- return `Network error: ${r.message}`;
441
- case "unexpected-shape":
442
- return `Server returned an unexpected response (${r.status ?? "??"}). Try again shortly.`;
443
- case "validation":
444
- return `Validation error: ${r.message}`;
445
- case "server-error":
446
- return `AgentChat API error (${r.status ?? "??"}): ${r.message}`;
447
- }
350
+ })).trim();
448
351
  }
449
- async function runHaveKeyFlow(cfg, accountId, prompter, apiBase) {
450
- const apiKey = await promptApiKey(prompter);
451
- const progress = prompter.progress("Validating key against AgentChat\u2026");
452
- const result = await validateApiKey(apiKey, { apiBase });
453
- if (!result.ok) {
454
- progress.stop("Key rejected.");
352
+ async function runChangeApiBaseFlow(params) {
353
+ const { cfg, accountId, prompter } = params;
354
+ const current = readAgentchatConfigField(cfg, accountId, "apiBase");
355
+ const input = (await prompter.text({
356
+ message: "New API base URL (blank to reset to default)",
357
+ placeholder: current ?? "https://api.agentchat.me",
358
+ validate: (value) => {
359
+ const trimmed = value.trim();
360
+ if (!trimmed) return void 0;
361
+ try {
362
+ const url = new URL(trimmed);
363
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
364
+ return "API base must use http:// or https://";
365
+ }
366
+ return void 0;
367
+ } catch {
368
+ return "Not a valid URL";
369
+ }
370
+ }
371
+ })).trim();
372
+ if (!input) {
373
+ const patched2 = applyAgentchatAccountPatch(cfg, accountId, {
374
+ apiBase: void 0
375
+ });
455
376
  await prompter.note(
456
- [
457
- `AgentChat rejected the key (${result.reason}):`,
458
- ` ${result.message}`,
459
- "",
460
- 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."
461
- ].join("\n"),
462
- "Validation failed"
377
+ "API base reset to default (https://api.agentchat.me).",
378
+ "Updated"
463
379
  );
464
- return runNewAccountFlow(cfg, accountId, prompter, apiBase);
380
+ return { cfg: patched2 };
465
381
  }
466
- progress.stop(`Authenticated as @${result.agent.handle}.`);
467
- const next = writeAccountPatch(cfg, accountId, {
468
- apiKey,
469
- agentHandle: result.agent.handle,
470
- ...apiBase ? { apiBase } : {}
382
+ const patched = applyAgentchatAccountPatch(cfg, accountId, {
383
+ apiBase: input
471
384
  });
472
- await prompter.note(
473
- [
474
- `Connected to AgentChat as @${result.agent.handle}.`,
475
- `Email: ${result.agent.email}`,
476
- result.agent.displayName ? `Display name: ${result.agent.displayName}` : void 0
477
- ].filter((v) => Boolean(v)).join("\n"),
478
- "AgentChat configured"
479
- );
480
- return { cfg: next };
385
+ await prompter.note(`API base set to ${input}`, "Updated");
386
+ return { cfg: patched };
481
387
  }
482
- async function runRegisterFlow(cfg, accountId, prompter, apiBase) {
388
+ async function runRegisterFlow(params) {
389
+ const { cfg, accountId, prompter, apiBase } = params;
483
390
  await prompter.note(
484
391
  [
485
- "We will email a 6-digit code to verify ownership of this address.",
486
- "The email is hashed server-side and used only for account recovery."
392
+ "Registration mints a new AgentChat agent identity tied to your email.",
393
+ "You will receive a 6-digit code to verify \u2014 check your inbox (and spam)."
487
394
  ].join("\n"),
488
- "New-agent registration"
489
- );
490
- const email = await promptEmail(prompter);
491
- const handle = await promptHandle(prompter);
492
- const displayName = await promptOptionalText(
493
- prompter,
494
- "Display name (optional, press Enter to skip)",
495
- handle
496
- );
497
- const description = await promptOptionalText(
498
- prompter,
499
- "Short description of your agent (optional)",
500
- "Research assistant that summarises papers"
501
- );
502
- const startProgress = prompter.progress("Requesting OTP\u2026");
503
- const start = await registerAgentStart(
504
- {
505
- email,
506
- handle,
507
- displayName: displayName || void 0,
508
- description: description || void 0
509
- },
510
- { apiBase }
395
+ "AgentChat: register a new agent"
511
396
  );
512
- if (!start.ok) {
513
- startProgress.stop("Registration could not start.");
514
- await prompter.note(describeRegisterStartFailure(start), "Registration failed");
515
- const retry = await prompter.confirm({
516
- message: start.reason === "handle-taken" || start.reason === "invalid-handle" ? "Try a different handle?" : "Try again?",
517
- initialValue: true
518
- });
519
- if (!retry) return { cfg };
520
- return runRegisterFlow(cfg, accountId, prompter, apiBase);
521
- }
522
- startProgress.stop(`OTP sent to ${email}.`);
523
- const pendingId = start.pendingId;
524
- for (let attempt = 1; attempt <= OTP_ATTEMPTS; attempt += 1) {
525
- const code = await promptOtp(prompter);
526
- const verifyProgress = prompter.progress("Verifying code\u2026");
527
- const verify = await registerAgentVerify({ pendingId, code }, { apiBase });
528
- if (verify.ok) {
529
- verifyProgress.stop(`Verified. Agent @${verify.agent.handle} created.`);
530
- const next = writeAccountPatch(cfg, accountId, {
531
- apiKey: verify.apiKey,
532
- agentHandle: verify.agent.handle,
533
- ...apiBase ? { apiBase } : {}
534
- });
397
+ let email = await promptEmail(prompter);
398
+ let handle = await promptHandle(prompter);
399
+ const displayName = await promptDisplayName(prompter);
400
+ let startResult;
401
+ let startedOk = false;
402
+ for (let attempt = 1; attempt <= MAX_START_RETRIES; attempt += 1) {
403
+ const startSpinner = prompter.progress(
404
+ attempt === 1 ? "Sending verification code\u2026" : "Retrying\u2026"
405
+ );
406
+ try {
407
+ startResult = await registerAgentStart(
408
+ {
409
+ email,
410
+ handle,
411
+ ...displayName ? { displayName } : {}
412
+ },
413
+ { apiBase }
414
+ );
415
+ } catch (err) {
416
+ startSpinner.stop("Could not reach AgentChat");
535
417
  await prompter.note(
536
- [
537
- "Your AgentChat API key has been written to config.",
538
- "",
539
- ` handle: @${verify.agent.handle}`,
540
- ` email: ${verify.agent.email}`,
541
- "",
542
- "Keep the key safe \u2014 it authenticates sends as this agent.",
543
- "You can rotate it any time from agentchat.me/dashboard."
544
- ].join("\n"),
545
- "Registration complete"
418
+ `${err instanceof Error ? err.message : String(err)}. Try again when the network is available, or paste an existing key instead.`,
419
+ "Registration failed"
546
420
  );
547
- return { cfg: next };
421
+ return "abort";
422
+ }
423
+ if (startResult.ok) {
424
+ startSpinner.stop(`Verification code sent to ${email}`);
425
+ startedOk = true;
426
+ break;
427
+ }
428
+ startSpinner.stop("Registration rejected");
429
+ switch (startResult.reason) {
430
+ case "invalid-handle":
431
+ case "handle-taken": {
432
+ const detail = startResult.reason === "handle-taken" ? `Handle @${handle} is already taken.` : "That handle is not acceptable (3\u201330 chars \u2014 lowercase letters/digits/hyphens; must start with a letter).";
433
+ await prompter.note(`${detail} Pick a different handle and we'll try again.`, "Pick a different handle");
434
+ handle = await promptHandle(prompter);
435
+ continue;
436
+ }
437
+ case "email-taken": {
438
+ const choice = await prompter.select({
439
+ message: `${email} is already registered as an AgentChat agent. What would you like to do?`,
440
+ options: [
441
+ {
442
+ value: "paste",
443
+ label: "Paste the existing API key for this agent",
444
+ hint: "recommended if you own the account"
445
+ },
446
+ { value: "retry", label: "Use a different email address" },
447
+ { value: "cancel", label: "Cancel registration" }
448
+ ],
449
+ initialValue: "paste"
450
+ });
451
+ if (choice === "paste") return "user-chose-paste";
452
+ if (choice === "cancel") return "abort";
453
+ email = await promptEmail(prompter);
454
+ continue;
455
+ }
456
+ case "email-exhausted": {
457
+ const choice = await prompter.select({
458
+ message: `${email} has reached the per-email agent quota. What next?`,
459
+ options: [
460
+ { value: "retry", label: "Use a different email address" },
461
+ { value: "paste", label: "Paste a key from an existing agent" },
462
+ { value: "cancel", label: "Cancel registration" }
463
+ ],
464
+ initialValue: "retry"
465
+ });
466
+ if (choice === "paste") return "user-chose-paste";
467
+ if (choice === "cancel") return "abort";
468
+ email = await promptEmail(prompter);
469
+ continue;
470
+ }
471
+ case "rate-limited": {
472
+ const wait = startResult.retryAfterSeconds ? ` Try again in ${startResult.retryAfterSeconds}s.` : "";
473
+ await prompter.note(`Too many registration attempts.${wait}`, "Rate limited");
474
+ return "abort";
475
+ }
476
+ case "otp-failed": {
477
+ await prompter.note(
478
+ "The verification-code email could not be sent. Try again in a minute, or paste an existing key instead.",
479
+ "OTP delivery failed"
480
+ );
481
+ return "abort";
482
+ }
483
+ case "network-error":
484
+ case "server-error":
485
+ case "validation":
486
+ default: {
487
+ await prompter.note(describeRegisterStartError(startResult), "Could not start registration");
488
+ return "abort";
489
+ }
548
490
  }
549
- verifyProgress.stop(`Attempt ${attempt}/${OTP_ATTEMPTS} failed (${verify.reason}).`);
550
- await prompter.note(describeRegisterVerifyFailure(verify), "Code rejected");
551
- if (verify.reason === "expired") {
552
- return runRegisterFlow(cfg, accountId, prompter, apiBase);
491
+ }
492
+ if (!startedOk || !startResult || !startResult.ok) {
493
+ await prompter.note(
494
+ "Too many attempts. Restart the wizard to try again, or paste an existing key at the next prompt.",
495
+ "Registration failed"
496
+ );
497
+ return "abort";
498
+ }
499
+ const maxCodeAttempts = 3;
500
+ let verifyResult = null;
501
+ for (let attempt = 1; attempt <= maxCodeAttempts; attempt += 1) {
502
+ const code = (await prompter.text({
503
+ message: attempt === 1 ? "Enter the 6-digit verification code from your email" : `Verification code (attempt ${attempt}/${maxCodeAttempts})`,
504
+ placeholder: "123456",
505
+ validate: (value) => {
506
+ const trimmed = value.trim();
507
+ if (!trimmed) return "Code is required";
508
+ if (!OTP_PATTERN.test(trimmed)) return "Code is 6 digits";
509
+ return void 0;
510
+ }
511
+ })).trim();
512
+ const verifySpinner = prompter.progress("Verifying code\u2026");
513
+ try {
514
+ verifyResult = await registerAgentVerify({ pendingId: startResult.pendingId, code }, { apiBase });
515
+ } catch (err) {
516
+ verifySpinner.stop("Could not reach AgentChat");
517
+ await prompter.note(
518
+ `${err instanceof Error ? err.message : String(err)}. Try again, or paste an existing key instead.`,
519
+ "Verification failed"
520
+ );
521
+ return "abort";
553
522
  }
554
- if (verify.reason !== "invalid-code") {
555
- const retry = await prompter.confirm({
556
- message: "Start over with different details?",
557
- initialValue: true
558
- });
559
- if (!retry) return { cfg };
560
- return runRegisterFlow(cfg, accountId, prompter, apiBase);
523
+ if (verifyResult.ok) {
524
+ verifySpinner.stop(`Registered as @${verifyResult.agent.handle}`);
525
+ break;
561
526
  }
527
+ verifySpinner.stop("Verification failed");
528
+ if (verifyResult.reason === "invalid-code" && attempt < maxCodeAttempts) {
529
+ await prompter.note(
530
+ "That code did not match. Check your email and try again.",
531
+ "Invalid verification code"
532
+ );
533
+ continue;
534
+ }
535
+ await prompter.note(describeRegisterVerifyError(verifyResult), "Registration failed");
536
+ return "abort";
537
+ }
538
+ if (!verifyResult || !verifyResult.ok) {
539
+ await prompter.note(
540
+ "Too many incorrect codes. Restart the wizard to receive a new code.",
541
+ "Registration failed"
542
+ );
543
+ return "abort";
544
+ }
545
+ const patch = { apiKey: verifyResult.apiKey };
546
+ if (isValidHandleShape(verifyResult.agent.handle)) {
547
+ patch.agentHandle = verifyResult.agent.handle;
562
548
  }
549
+ const nextCfg = applyAgentchatAccountPatch(cfg, accountId, patch);
563
550
  await prompter.note(
564
- "Too many invalid codes. Restart setup to request a new one.",
565
- "Registration aborted"
551
+ [
552
+ `Handle: @${verifyResult.agent.handle}`,
553
+ `Email: ${verifyResult.agent.email}`,
554
+ `API key: ${redactKey(verifyResult.apiKey)} (saved to your OpenClaw config)`
555
+ ].join("\n"),
556
+ "AgentChat account created"
566
557
  );
567
- return { cfg };
558
+ return {
559
+ cfg: nextCfg,
560
+ credentialValues: {
561
+ token: verifyResult.apiKey,
562
+ [JUST_REGISTERED_SENTINEL]: "1"
563
+ }
564
+ };
568
565
  }
569
- async function runEditFlow(cfg, accountId, prompter) {
570
- const currentKey = readAccountApiKey(cfg, accountId);
571
- const currentBase = readAccountApiBase(cfg, accountId);
572
- const choice = await prompter.select({
573
- message: "AgentChat is already configured. What would you like to do?",
574
- options: [
575
- {
576
- value: "validate",
577
- label: "Re-validate the current key",
578
- hint: "Hit /agents/me to confirm it still authenticates"
579
- },
580
- {
581
- value: "rotate",
582
- label: "Rotate to a new API key",
583
- hint: "Paste a freshly generated key or register a new agent"
584
- },
585
- {
586
- value: "change-base",
587
- label: "Change API base URL",
588
- hint: "Only for self-hosted AgentChat deployments"
589
- },
590
- { value: "skip", label: "Leave as is", hint: "No changes" }
591
- ],
592
- initialValue: "validate"
593
- });
594
- if (choice === "skip") return { cfg };
595
- if (choice === "validate") {
596
- if (!currentKey) {
597
- await prompter.note("No API key is currently set.", "Nothing to validate");
598
- return runNewAccountFlow(cfg, accountId, prompter, currentBase);
599
- }
600
- const progress = prompter.progress("Probing /agents/me\u2026");
601
- const result = await validateApiKey(currentKey, { apiBase: currentBase });
602
- if (result.ok) {
603
- progress.stop(`Still authenticated as @${result.agent.handle}.`);
604
- return { cfg };
605
- }
606
- progress.stop("Key no longer works.");
607
- await prompter.note(`${result.message} [reason=${result.reason}]`, "Re-validation failed");
608
- const doRotate = await prompter.confirm({
609
- message: "Rotate to a new key now?",
610
- initialValue: true
611
- });
612
- if (doRotate) return runNewAccountFlow(cfg, accountId, prompter, currentBase);
613
- return { cfg };
614
- }
615
- if (choice === "rotate") {
616
- return runNewAccountFlow(cfg, accountId, prompter, currentBase);
566
+ function describeRegisterStartError(result) {
567
+ switch (result.reason) {
568
+ case "invalid-handle":
569
+ return "That handle is not acceptable. Try a different one (3\u201330 chars \u2014 lowercase letters/digits/hyphens; must start with a letter).";
570
+ case "handle-taken":
571
+ return "That handle is already taken. Try a different one.";
572
+ case "email-taken":
573
+ return "That email is already registered as an agent. Paste the existing key instead, or use a different email.";
574
+ case "email-exhausted":
575
+ return "This email has reached the agent quota. Use a different email, or paste a key from an existing agent.";
576
+ case "rate-limited": {
577
+ const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
578
+ return `Rate limited.${wait}`;
579
+ }
580
+ case "otp-failed":
581
+ return "The verification-code email could not be sent. Try again in a minute.";
582
+ case "network-error":
583
+ case "server-error":
584
+ case "validation":
585
+ default:
586
+ return result.message;
617
587
  }
618
- const nextBase = await promptOptionalText(
619
- prompter,
620
- "New API base URL (blank to reset to default)",
621
- currentBase ?? "https://api.agentchat.me"
622
- );
623
- if (nextBase) {
624
- try {
625
- const url = new URL(nextBase);
626
- if (url.protocol !== "https:" && url.protocol !== "http:") {
627
- await prompter.note("API base must use http:// or https://. Not updated.", "Ignored");
628
- return { cfg };
629
- }
630
- } catch {
631
- await prompter.note("Not a valid URL. API base not updated.", "Ignored");
632
- return { cfg };
588
+ }
589
+ function describeRegisterVerifyError(result) {
590
+ switch (result.reason) {
591
+ case "expired":
592
+ return "This code expired. Restart the wizard to receive a new one.";
593
+ case "invalid-code":
594
+ return "Too many incorrect codes. Restart the wizard to receive a new one.";
595
+ case "handle-taken":
596
+ return "Your chosen handle was claimed by another registration in the meantime. Restart with a different handle.";
597
+ case "email-taken":
598
+ return "This email is already registered. Paste the existing key instead.";
599
+ case "rate-limited": {
600
+ const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
601
+ return `Rate limited.${wait}`;
633
602
  }
603
+ case "network-error":
604
+ case "server-error":
605
+ case "unexpected-shape":
606
+ case "validation":
607
+ default:
608
+ return result.message;
634
609
  }
635
- const next = writeAccountPatch(cfg, accountId, { apiBase: nextBase || void 0 });
636
- await prompter.note(
637
- nextBase ? `API base set to ${nextBase}` : "API base reset to default",
638
- "Updated"
639
- );
640
- return { cfg: next };
641
610
  }
642
- async function runNewAccountFlow(cfg, accountId, prompter, apiBase) {
643
- const choice = await prompter.select({
644
- message: "How do you want to connect this agent?",
645
- options: [
646
- {
647
- value: "register",
648
- label: "Register a new agent (email + OTP)",
649
- hint: "Mint a fresh API key \u2014 ~60 seconds"
650
- },
651
- {
652
- value: "have-key",
653
- label: "I already have an API key",
654
- hint: "Paste ac_live_..."
655
- }
656
- ],
657
- initialValue: "register"
658
- });
659
- if (choice === "have-key") return runHaveKeyFlow(cfg, accountId, prompter, apiBase);
660
- return runRegisterFlow(cfg, accountId, prompter, apiBase);
611
+ function redactKey(apiKey) {
612
+ if (apiKey.length < 12) return "\u2022\u2022\u2022\u2022";
613
+ return `${apiKey.slice(0, 8)}\u2026${apiKey.slice(-4)}`;
661
614
  }
662
615
  var agentchatSetupWizard = {
663
616
  channel: AGENTCHAT_CHANNEL_ID,
664
- resolveShouldPromptAccountIds: () => false,
665
617
  status: {
666
- configuredLabel: "connected",
667
- unconfiguredLabel: "needs API key",
668
- configuredHint: "configured",
669
- unconfiguredHint: "needs agent credentials",
670
- configuredScore: 2,
671
- unconfiguredScore: 0,
672
- resolveConfigured: ({ cfg, accountId }) => isAccountConfigured(cfg, accountId),
673
- resolveStatusLines: async ({ cfg, accountId, configured }) => {
674
- if (!configured) return ["AgentChat: needs API key \u2014 run setup to register or paste one"];
675
- const id = accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID;
676
- const key = readAccountApiKey(cfg, id);
677
- const apiBase = readAccountApiBase(cfg, id);
678
- if (!key) return ["AgentChat: configured (no key visible)"];
679
- try {
680
- const result = await validateApiKey(key, { apiBase, timeoutMs: 3e3 });
681
- if (result.ok) return [`AgentChat: connected as @${result.agent.handle}`];
682
- return [`AgentChat: configured (live probe failed: ${result.reason})`];
683
- } catch {
684
- return ["AgentChat: configured (live probe unreachable)"];
618
+ configuredLabel: "configured",
619
+ unconfiguredLabel: "not configured",
620
+ configuredHint: "AgentChat agent is ready to receive messages",
621
+ unconfiguredHint: "connect your agent to the AgentChat messaging platform",
622
+ configuredScore: 90,
623
+ unconfiguredScore: 30,
624
+ resolveConfigured: ({ cfg, accountId }) => {
625
+ return hasConfiguredKey(cfg, accountId ?? "default");
626
+ },
627
+ resolveStatusLines: ({ cfg, accountId, configured }) => {
628
+ const id = accountId ?? "default";
629
+ if (!configured) {
630
+ return ["AgentChat: not configured \u2014 the wizard will register you or accept an existing key."];
685
631
  }
632
+ const handle = readAgentchatConfigField(cfg, id, "agentHandle");
633
+ return [`AgentChat: configured${handle ? ` (@${handle})` : ""}`];
686
634
  }
687
635
  },
688
636
  introNote: {
689
637
  title: "AgentChat",
690
638
  lines: [
691
- "A messaging platform for AI agents \u2014 handle-based routing, realtime WebSocket,",
692
- "typed error taxonomy, idempotent sends.",
639
+ "AgentChat is a messaging platform for AI agents \u2014 direct messages,",
640
+ "groups, presence, attachments. Registration is free.",
693
641
  "",
694
- "You can paste an existing API key, or register a new agent inline (email + OTP)."
642
+ "This wizard will either mint a new account via email OTP, or accept",
643
+ "an existing API key \u2014 your choice in the next prompt."
695
644
  ]
696
645
  },
697
- prepare: async ({ cfg, accountId, credentialValues }) => {
698
- const flow = isAccountConfigured(cfg, accountId) ? "edit" : "new";
699
- return { credentialValues: { ...credentialValues, _flow: flow } };
646
+ prepare: async ({ cfg, accountId, credentialValues, prompter }) => {
647
+ if (hasConfiguredKey(cfg, accountId) && typeof credentialValues.token === "string") {
648
+ const editChoice = await prompter.select({
649
+ message: "AgentChat is already configured. What would you like to do?",
650
+ options: [
651
+ {
652
+ value: "keep",
653
+ label: "Keep current config",
654
+ hint: "the credential step will still re-validate on the next run"
655
+ },
656
+ {
657
+ value: "change-base",
658
+ label: "Change API base URL",
659
+ hint: "only for self-hosted AgentChat deployments"
660
+ },
661
+ {
662
+ value: "replace-key",
663
+ label: "Replace the API key",
664
+ hint: "paste a new key, or register a new agent"
665
+ }
666
+ ],
667
+ initialValue: "keep"
668
+ });
669
+ if (editChoice === "keep") return;
670
+ if (editChoice === "change-base") {
671
+ return await runChangeApiBaseFlow({ cfg, accountId, prompter });
672
+ }
673
+ }
674
+ const choice = await prompter.select({
675
+ message: "How would you like to configure AgentChat?",
676
+ options: [
677
+ {
678
+ value: "register",
679
+ label: "Register a new agent (email OTP)",
680
+ hint: "no account yet \u2014 the wizard creates one"
681
+ },
682
+ {
683
+ value: "paste",
684
+ label: "I already have an API key",
685
+ hint: "paste ac_live_\u2026 on the next prompt"
686
+ }
687
+ ],
688
+ initialValue: "register"
689
+ });
690
+ if (choice === "paste") {
691
+ return;
692
+ }
693
+ const apiBase = readAgentchatConfigField(cfg, accountId, "apiBase");
694
+ try {
695
+ const result = await runRegisterFlow({ cfg, accountId, prompter, apiBase });
696
+ if (result === "abort") {
697
+ await prompter.note(
698
+ "Registration was not completed. You can still paste an existing API key at the next prompt, or cancel the wizard.",
699
+ "Falling back to credential entry"
700
+ );
701
+ return;
702
+ }
703
+ if (result === "user-chose-paste") {
704
+ return;
705
+ }
706
+ return result;
707
+ } catch (err) {
708
+ if (err instanceof setup.WizardCancelledError) throw err;
709
+ await prompter.note(
710
+ `${err instanceof Error ? err.message : String(err)}`,
711
+ "Registration flow failed"
712
+ );
713
+ return;
714
+ }
700
715
  },
701
- credentials: [],
702
- finalize: async ({ cfg, accountId, prompter, credentialValues }) => {
703
- const rawFlow = credentialValues._flow;
704
- const flow = rawFlow === "edit" ? "edit" : "new";
705
- const apiBase = readAccountApiBase(cfg, accountId);
706
- return flow === "edit" ? await runEditFlow(cfg, accountId, prompter) : await runNewAccountFlow(cfg, accountId, prompter, apiBase);
716
+ credentials: [
717
+ {
718
+ inputKey: "token",
719
+ providerHint: "agentchat",
720
+ credentialLabel: "API key",
721
+ preferredEnvVar: "AGENTCHAT_API_KEY",
722
+ envPrompt: "AGENTCHAT_API_KEY detected in env. Use it?",
723
+ keepPrompt: "AgentChat API key already configured. Keep it?",
724
+ inputPrompt: "Paste your AgentChat API key (ac_live_\u2026)",
725
+ helpTitle: "AgentChat API key",
726
+ helpLines: [
727
+ "Format: ac_live_<base64>, \u226520 chars. Validated against",
728
+ "GET /v1/agents/me during this wizard \u2014 bad keys fail fast instead",
729
+ "of flapping reconnects at runtime."
730
+ ],
731
+ allowEnv: () => true,
732
+ shouldPrompt: ({ credentialValues, currentValue }) => {
733
+ if (credentialValues[JUST_REGISTERED_SENTINEL] === "1") return false;
734
+ if (!currentValue) return true;
735
+ return true;
736
+ },
737
+ inspect: ({ cfg, accountId }) => {
738
+ const apiKey = readAgentchatConfigField(cfg, accountId, "apiKey");
739
+ const configured = isApiKeyPresent(apiKey);
740
+ const envValue = process.env.AGENTCHAT_API_KEY?.trim();
741
+ return {
742
+ accountConfigured: configured,
743
+ hasConfiguredValue: configured,
744
+ resolvedValue: configured ? apiKey : void 0,
745
+ envValue: envValue && envValue.length >= MIN_API_KEY_LENGTH ? envValue : void 0
746
+ };
747
+ }
748
+ }
749
+ ],
750
+ finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
751
+ const apiKey = typeof credentialValues.token === "string" && credentialValues.token.length >= MIN_API_KEY_LENGTH ? credentialValues.token : readAgentchatConfigField(cfg, accountId, "apiKey");
752
+ if (!apiKey || !isApiKeyPresent(apiKey)) {
753
+ return;
754
+ }
755
+ const apiBase = readAgentchatConfigField(cfg, accountId, "apiBase");
756
+ const spinner = prompter.progress("Validating API key against AgentChat\u2026");
757
+ try {
758
+ const result = await validateApiKey(apiKey, { apiBase });
759
+ if (result.ok) {
760
+ spinner.stop(`Authenticated as @${result.agent.handle}`);
761
+ const existingHandle = readAgentchatConfigField(cfg, accountId, "agentHandle");
762
+ if (!existingHandle && isValidHandleShape(result.agent.handle)) {
763
+ return {
764
+ cfg: applyAgentchatAccountPatch(cfg, accountId, {
765
+ agentHandle: result.agent.handle
766
+ })
767
+ };
768
+ }
769
+ return;
770
+ }
771
+ spinner.stop(`API key did not pass the live check (${result.reason})`);
772
+ await prompter.note(
773
+ [
774
+ result.message,
775
+ "",
776
+ "The config was saved \u2014 you can re-run `openclaw channels add agentchat`",
777
+ "to replace the key, or edit ~/.openclaw/config.yaml directly."
778
+ ].join("\n"),
779
+ "AgentChat validation warning"
780
+ );
781
+ } catch (err) {
782
+ spinner.stop("Could not reach AgentChat for validation");
783
+ await prompter.note(
784
+ [
785
+ err instanceof Error ? err.message : String(err),
786
+ "",
787
+ "The config was saved \u2014 the runtime will retry on startup."
788
+ ].join("\n"),
789
+ "AgentChat API unreachable"
790
+ );
791
+ }
792
+ return;
707
793
  },
708
794
  completionNote: {
709
795
  title: "AgentChat is ready",
710
796
  lines: [
711
- "The channel runtime will auto-connect on the next `openclaw` boot.",
712
- "Messages addressed to your handle arrive over WebSocket; sends go out",
713
- "over HTTPS with at-least-once + idempotent semantics."
797
+ "Next steps:",
798
+ " \u2022 Start OpenClaw \u2014 the AgentChat channel auto-connects via WebSocket.",
799
+ " \u2022 DM another agent: @<handle> <message>",
800
+ " \u2022 Docs: https://agentchat.me/docs"
714
801
  ]
715
802
  },
716
- disable: (cfg) => {
717
- const channels = {
718
- ...cfg?.channels ?? {}
719
- };
720
- const existing = channels[AGENTCHAT_CHANNEL_ID];
721
- const section = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
722
- section.enabled = false;
723
- channels[AGENTCHAT_CHANNEL_ID] = section;
724
- return { ...cfg, channels };
725
- }
803
+ disable: (cfg) => setup.setSetupChannelEnabled(cfg, AGENTCHAT_CHANNEL_ID, false)
726
804
  };
805
+ var reconnectConfigSchema = zod.z.object({
806
+ initialBackoffMs: zod.z.number().int().min(100).max(1e4).default(1e3),
807
+ maxBackoffMs: zod.z.number().int().min(1e3).max(3e5).default(3e4),
808
+ jitterRatio: zod.z.number().min(0).max(1).default(0.2)
809
+ }).strict();
810
+ var pingConfigSchema = zod.z.object({
811
+ intervalMs: zod.z.number().int().min(5e3).max(12e4).default(3e4),
812
+ timeoutMs: zod.z.number().int().min(1e3).max(3e4).default(1e4)
813
+ }).strict();
814
+ var outboundConfigSchema = zod.z.object({
815
+ maxInFlight: zod.z.number().int().min(1).max(1e4).default(256),
816
+ sendTimeoutMs: zod.z.number().int().min(1e3).max(6e4).default(15e3)
817
+ }).strict();
818
+ var observabilityConfigSchema = zod.z.object({
819
+ logLevel: zod.z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
820
+ redactKeys: zod.z.array(zod.z.string()).default(["apiKey", "authorization", "cookie", "set-cookie"])
821
+ }).strict();
822
+ var agentHandleSchema = zod.z.string().regex(
823
+ /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,
824
+ "handle must be lowercase letters/digits/hyphens; start with a letter; no trailing or doubled hyphens"
825
+ ).min(3, "handle must be at least 3 characters").max(30, "handle must be at most 30 characters");
826
+ var agentchatChannelConfigSchema = zod.z.object({
827
+ apiBase: zod.z.string().url().default("https://api.agentchat.me"),
828
+ apiKey: zod.z.string().min(20, "apiKey looks too short for an AgentChat API key"),
829
+ agentHandle: agentHandleSchema.optional(),
830
+ // `.prefault({})` — Zod 4 idiom: when the key is missing from input, parse
831
+ // `{}` through the inner schema so its own per-field defaults kick in.
832
+ // Using `.default({})` here fails in Zod 4 because the output type's fields
833
+ // are non-optional once the inner schema has defaults.
834
+ reconnect: reconnectConfigSchema.prefault({}),
835
+ ping: pingConfigSchema.prefault({}),
836
+ outbound: outboundConfigSchema.prefault({}),
837
+ observability: observabilityConfigSchema.prefault({})
838
+ }).strict();
839
+ function parseChannelConfig(input) {
840
+ return agentchatChannelConfigSchema.parse(input);
841
+ }
727
842
 
728
843
  // src/channel.ts
729
- var AGENTCHAT_CHANNEL_ID = "agentchat";
730
- var AGENTCHAT_DEFAULT_ACCOUNT_ID = "default";
731
- var MIN_API_KEY_LENGTH2 = 20;
732
- function readChannelSection(cfg) {
733
- const channels = cfg?.channels;
734
- const section = channels?.[AGENTCHAT_CHANNEL_ID];
735
- return section && typeof section === "object" && !Array.isArray(section) ? section : void 0;
736
- }
737
- function readAccountRaw2(section, accountId) {
738
- if (!section) return void 0;
739
- const { accounts } = section;
740
- if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
741
- const entry = accounts[accountId];
742
- if (entry && typeof entry === "object") return entry;
743
- }
744
- if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID) {
745
- const { accounts: _accounts, ...rest } = section;
746
- return rest;
747
- }
748
- return void 0;
749
- }
750
- function splitEnabledFromRaw(raw) {
751
- if (!raw) return { enabled: true, forParse: void 0 };
752
- const { enabled, ...rest } = raw;
753
- return { enabled: enabled !== false, forParse: rest };
754
- }
755
- function isApiKeyPresent(value) {
756
- return typeof value === "string" && value.length >= MIN_API_KEY_LENGTH2;
757
- }
758
844
  var uiHints = {
759
845
  apiKey: {
760
846
  label: "AgentChat API key",
761
847
  placeholder: "ac_live_...",
762
848
  sensitive: true,
763
- help: "Obtain from AgentChat dashboard \u2192 Settings \u2192 API keys."
849
+ help: "The setup wizard registers you via email OTP and mints a key \u2014 or paste an existing ac_live_\u2026 key."
764
850
  },
765
851
  apiBase: {
766
852
  label: "API base URL",
@@ -771,7 +857,7 @@ var uiHints = {
771
857
  agentHandle: {
772
858
  label: "Agent handle",
773
859
  placeholder: "my-agent",
774
- help: "3\u201332 chars, lowercase alphanumeric plus . _ -"
860
+ help: "3\u201330 chars, lowercase letters/digits/hyphens; must start with a letter."
775
861
  },
776
862
  reconnect: { label: "Reconnect backoff", advanced: true },
777
863
  ping: { label: "WebSocket ping cadence", advanced: true },
@@ -822,7 +908,7 @@ var agentchatPlugin = {
822
908
  resolveAccount(cfg, accountId) {
823
909
  const id = accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID;
824
910
  const section = readChannelSection(cfg);
825
- const raw = readAccountRaw2(section, id);
911
+ const raw = readAccountRaw(section, id);
826
912
  const { enabled, forParse } = splitEnabledFromRaw(raw);
827
913
  let config = null;
828
914
  let parseError = null;
@@ -887,8 +973,8 @@ var agentchatPlugin = {
887
973
  if (typeof input.token !== "string" || input.token.trim().length === 0) {
888
974
  return "apiKey is required \u2014 pass via --token or run the register flow";
889
975
  }
890
- if (input.token.length < MIN_API_KEY_LENGTH2) {
891
- return `apiKey looks too short (got ${input.token.length} chars, expect \u2265${MIN_API_KEY_LENGTH2})`;
976
+ if (input.token.length < MIN_API_KEY_LENGTH) {
977
+ return `apiKey looks too short (got ${input.token.length} chars, expect \u2265${MIN_API_KEY_LENGTH})`;
892
978
  }
893
979
  if (typeof input.url === "string" && input.url.length > 0) {
894
980
  try {
@@ -903,26 +989,10 @@ var agentchatPlugin = {
903
989
  return null;
904
990
  },
905
991
  applyAccountConfig({ cfg, accountId, input }) {
906
- const channels = {
907
- ...cfg?.channels ?? {}
908
- };
909
- const currentSection = channels[AGENTCHAT_CHANNEL_ID];
910
- const section = currentSection && typeof currentSection === "object" && !Array.isArray(currentSection) ? { ...currentSection } : {};
911
992
  const patch = {};
912
993
  if (typeof input.token === "string" && input.token.length > 0) patch.apiKey = input.token;
913
994
  if (typeof input.url === "string" && input.url.length > 0) patch.apiBase = input.url;
914
- if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID && !section.accounts) {
915
- Object.assign(section, patch);
916
- } else {
917
- const accounts = {
918
- ...section.accounts ?? {}
919
- };
920
- const prevAccount = typeof accounts[accountId] === "object" && accounts[accountId] !== null ? accounts[accountId] : {};
921
- accounts[accountId] = { ...prevAccount, ...patch };
922
- section.accounts = accounts;
923
- }
924
- channels[AGENTCHAT_CHANNEL_ID] = section;
925
- return { ...cfg, channels };
995
+ return applyAgentchatAccountPatch(cfg, accountId, patch);
926
996
  },
927
997
  /**
928
998
  * Post-write probe: call `GET /v1/agents/me` with the key we just wrote
@@ -1936,7 +2006,7 @@ var CircuitBreaker = class {
1936
2006
  };
1937
2007
 
1938
2008
  // src/version.ts
1939
- var PACKAGE_VERSION = "0.2.0";
2009
+ var PACKAGE_VERSION = "0.3.0";
1940
2010
 
1941
2011
  // src/outbound.ts
1942
2012
  var DEFAULT_RETRY_POLICY = {
@@ -2400,7 +2470,6 @@ exports.agentchatChannelEntry = agentchatChannelEntry;
2400
2470
  exports.agentchatPlugin = agentchatPlugin;
2401
2471
  exports.agentchatSetupEntry = agentchatSetupEntry;
2402
2472
  exports.agentchatSetupPlugin = agentchatPlugin;
2403
- exports.agentchatSetupWizard = agentchatSetupWizard;
2404
2473
  exports.assertApiKeyValid = assertApiKeyValid;
2405
2474
  exports.default = agentchatChannelEntry;
2406
2475
  exports.hasAgentChatConfiguredState = hasAgentChatConfiguredState;