@agentchatme/openclaw 0.7.82111 → 0.7.8211111

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.
@@ -146,7 +146,7 @@ function classifyNetworkError(err3) {
146
146
  }
147
147
 
148
148
  // src/version.ts
149
- var PACKAGE_VERSION = "0.7.82111";
149
+ var PACKAGE_VERSION = "0.7.8211111";
150
150
 
151
151
  // src/client-identity.ts
152
152
  var AGENTCHAT_CLIENT_NAME = "openclaw";
@@ -267,8 +267,10 @@ async function registerAgentStart(input, opts = {}) {
267
267
  if (res.status === 400 && code === "INVALID_HANDLE") return { ok: false, reason: "invalid-handle", message, status: 400 };
268
268
  if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
269
269
  if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
270
- if (res.status === 409 && code === "EMAIL_TAKEN") return { ok: false, reason: "email-taken", message, status: 409 };
271
- if (res.status === 409 && code === "EMAIL_EXHAUSTED") return { ok: false, reason: "email-exhausted", message, status: 409 };
270
+ const emailPolicy = res.status === 409 ? classifyEmailPolicyRejection(code) : void 0;
271
+ if (emailPolicy) {
272
+ return { ok: false, reason: emailPolicy, message, status: 409, limit: readPolicyLimit(body.details) };
273
+ }
272
274
  if (res.status === 429) {
273
275
  return {
274
276
  ok: false,
@@ -313,12 +315,84 @@ async function registerAgentVerify(input, opts = {}) {
313
315
  if (res.status === 400 && code === "INVALID_CODE") return { ok: false, reason: "invalid-code", message, status: 400 };
314
316
  if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
315
317
  if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
316
- if (res.status === 409 && code === "EMAIL_TAKEN") return { ok: false, reason: "email-taken", message, status: 409 };
318
+ const emailPolicy = res.status === 409 ? classifyEmailPolicyRejection(code) : void 0;
319
+ if (emailPolicy) {
320
+ return { ok: false, reason: emailPolicy, message, status: 409, limit: readPolicyLimit(body.details) };
321
+ }
322
+ if (res.status === 429) {
323
+ return { ok: false, reason: "rate-limited", message, status: 429, retryAfterSeconds: res.retryAfterSeconds };
324
+ }
325
+ return { ok: false, reason: "server-error", status: res.status, message };
326
+ }
327
+ async function recoverAgentStart(input, opts = {}) {
328
+ const res = await post("/v1/agents/recover", { email: input.email, handle: input.handle }, opts);
329
+ if (res.kind === "network") return { ok: false, reason: "network-error", message: res.message };
330
+ if (res.kind === "timeout") return { ok: false, reason: "network-error", message: "request timed out" };
331
+ const body = res.body ?? {};
332
+ const message = typeof body.message === "string" ? body.message : `status ${res.status}`;
333
+ if (res.status === 200) {
334
+ if (typeof body.pending_id !== "string") {
335
+ return {
336
+ ok: false,
337
+ reason: "unexpected-shape",
338
+ status: 200,
339
+ message: "AgentChat did not start a recovery (no pending_id in the response). Check that the email is the one this agent registered with."
340
+ };
341
+ }
342
+ return { ok: true, pendingId: body.pending_id, message };
343
+ }
344
+ const code = typeof body.code === "string" ? body.code : "";
345
+ if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
346
+ if (res.status === 429) {
347
+ return { ok: false, reason: "rate-limited", message, status: 429, retryAfterSeconds: res.retryAfterSeconds };
348
+ }
349
+ return { ok: false, reason: "server-error", status: res.status, message };
350
+ }
351
+ async function recoverAgentVerify(input, opts = {}) {
352
+ const res = await post("/v1/agents/recover/verify", { pending_id: input.pendingId, code: input.code }, opts);
353
+ if (res.kind === "network") return { ok: false, reason: "network-error", message: res.message };
354
+ if (res.kind === "timeout") return { ok: false, reason: "network-error", message: "request timed out" };
355
+ const body = res.body ?? {};
356
+ if (res.status === 200) {
357
+ if (typeof body.api_key !== "string" || typeof body.handle !== "string") {
358
+ return {
359
+ ok: false,
360
+ reason: "unexpected-shape",
361
+ status: 200,
362
+ message: "AgentChat /agents/recover/verify returned an unrecognized shape"
363
+ };
364
+ }
365
+ return { ok: true, apiKey: body.api_key, handle: body.handle };
366
+ }
367
+ const code = typeof body.code === "string" ? body.code : "";
368
+ const message = typeof body.message === "string" ? body.message : `status ${res.status}`;
369
+ if (res.status === 400 && code === "EXPIRED") return { ok: false, reason: "expired", message, status: 400 };
370
+ if (res.status === 400 && code === "INVALID_CODE") return { ok: false, reason: "invalid-code", message, status: 400 };
371
+ if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
372
+ if (res.status === 409 && code === "HANDLE_REQUIRED") {
373
+ return { ok: false, reason: "handle-required", message, status: 409, handles: readHandleList(body.details) };
374
+ }
317
375
  if (res.status === 429) {
318
376
  return { ok: false, reason: "rate-limited", message, status: 429, retryAfterSeconds: res.retryAfterSeconds };
319
377
  }
320
378
  return { ok: false, reason: "server-error", status: res.status, message };
321
379
  }
380
+ function classifyEmailPolicyRejection(code) {
381
+ if (code === "EMAIL_LIMIT_REACHED" || code === "EMAIL_TAKEN") return "email-limit-reached";
382
+ if (code === "EMAIL_EXHAUSTED") return "email-exhausted";
383
+ return void 0;
384
+ }
385
+ function readPolicyLimit(details) {
386
+ if (!details || typeof details !== "object") return void 0;
387
+ const limit = details.limit;
388
+ return typeof limit === "number" && Number.isInteger(limit) && limit > 0 ? limit : void 0;
389
+ }
390
+ function readHandleList(details) {
391
+ if (!details || typeof details !== "object") return [];
392
+ const handles = details.handles;
393
+ if (!Array.isArray(handles)) return [];
394
+ return handles.filter((h) => typeof h === "string" && h.length > 0);
395
+ }
322
396
  async function post(path3, body, opts) {
323
397
  const base = (opts.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
324
398
  const url = `${base}${path3}`;
@@ -366,9 +440,9 @@ function hasConfiguredKey(cfg, accountId) {
366
440
  return isApiKeyPresent(readAgentchatConfigField(cfg, accountId, "apiKey"));
367
441
  }
368
442
  var MAX_START_RETRIES = 5;
369
- async function promptEmail(prompter) {
443
+ async function promptEmail(prompter, opts = {}) {
370
444
  return (await prompter.text({
371
- message: "Email \u2014 receives a 6-digit verification code",
445
+ message: opts.message ?? "Email \u2014 receives a 6-digit verification code",
372
446
  placeholder: "you@example.com",
373
447
  validate: (value) => {
374
448
  const trimmed = value.trim();
@@ -378,10 +452,11 @@ async function promptEmail(prompter) {
378
452
  }
379
453
  })).trim();
380
454
  }
381
- async function promptHandle(prompter) {
455
+ async function promptHandle(prompter, opts = {}) {
382
456
  return (await prompter.text({
383
- message: "Choose a handle (your @name on AgentChat)",
457
+ message: opts.message ?? "Choose a handle (your @name on AgentChat)",
384
458
  placeholder: "3\u201330 chars, lowercase a-z, 0-9, hyphens, starts with a letter",
459
+ ...opts.initialValue ? { initialValue: opts.initialValue } : {},
385
460
  validate: (value) => {
386
461
  const trimmed = value.trim();
387
462
  if (!trimmed) return "Handle is required";
@@ -441,11 +516,37 @@ async function runChangeApiBaseFlow(params) {
441
516
  await prompter.note(`API base set to ${input}`, "Updated");
442
517
  return { cfg: patched };
443
518
  }
519
+ function describeEmailPolicyRejection(email, result) {
520
+ if (result.limit === void 0) return result.message;
521
+ return result.reason === "email-limit-reached" ? `${email} already backs ${result.limit} active agents \u2014 the per-email limit.` : `${email} has used all ${result.limit} of its lifetime account registrations.`;
522
+ }
523
+ async function promptEmailPolicyChoice(prompter, email, result) {
524
+ return prompter.select({
525
+ message: `${describeEmailPolicyRejection(email, result)} What next?`,
526
+ options: [
527
+ {
528
+ value: "retry",
529
+ label: "Use a different email address",
530
+ hint: "a +alias like you+agent2@example.com counts as a separate email"
531
+ },
532
+ {
533
+ value: "recover",
534
+ label: "Recover the API key of an agent this email already backs",
535
+ hint: "needs that agent\u2019s handle \u2014 a code goes to this email"
536
+ },
537
+ { value: "paste", label: "Paste a key from an existing agent" },
538
+ { value: "cancel", label: "Cancel registration" }
539
+ ],
540
+ initialValue: "retry"
541
+ });
542
+ }
444
543
  async function runRegisterFlow(params) {
445
544
  const { cfg, accountId, prompter, apiBase } = params;
446
545
  await prompter.note(
447
546
  [
448
547
  "Registration mints a new AgentChat agent identity tied to your email.",
548
+ "One email can back several agents (the server enforces the limit);",
549
+ "each one registers and verifies separately.",
449
550
  "You will receive a 6-digit code to verify \u2014 check your inbox (and spam)."
450
551
  ].join("\n"),
451
552
  "AgentChat: register a new agent"
@@ -490,36 +591,15 @@ async function runRegisterFlow(params) {
490
591
  handle = await promptHandle(prompter);
491
592
  continue;
492
593
  }
493
- case "email-taken": {
494
- const choice = await prompter.select({
495
- message: `${email} is already registered as an AgentChat agent. What would you like to do?`,
496
- options: [
497
- {
498
- value: "paste",
499
- label: "Paste the existing API key for this agent",
500
- hint: "recommended if you own the account"
501
- },
502
- { value: "retry", label: "Use a different email address" },
503
- { value: "cancel", label: "Cancel registration" }
504
- ],
505
- initialValue: "paste"
506
- });
507
- if (choice === "paste") return "user-chose-paste";
508
- if (choice === "cancel") return "abort";
509
- email = await promptEmail(prompter);
510
- continue;
511
- }
594
+ case "email-limit-reached":
512
595
  case "email-exhausted": {
513
- const choice = await prompter.select({
514
- message: `${email} has reached the per-email agent quota. What next?`,
515
- options: [
516
- { value: "retry", label: "Use a different email address" },
517
- { value: "paste", label: "Paste a key from an existing agent" },
518
- { value: "cancel", label: "Cancel registration" }
519
- ],
520
- initialValue: "retry"
596
+ const choice = await promptEmailPolicyChoice(prompter, email, {
597
+ reason: startResult.reason,
598
+ limit: startResult.limit,
599
+ message: startResult.message
521
600
  });
522
601
  if (choice === "paste") return "user-chose-paste";
602
+ if (choice === "recover") return "user-chose-recover";
523
603
  if (choice === "cancel") return "abort";
524
604
  email = await promptEmail(prompter);
525
605
  continue;
@@ -625,10 +705,10 @@ function describeRegisterStartError(result) {
625
705
  return "That handle is not acceptable. Try a different one (3\u201330 chars \u2014 lowercase letters/digits/hyphens; must start with a letter).";
626
706
  case "handle-taken":
627
707
  return "That handle is already taken. Try a different one.";
628
- case "email-taken":
629
- return "That email is already registered as an agent. Paste the existing key instead, or use a different email.";
708
+ case "email-limit-reached":
709
+ return `${result.limit === void 0 ? result.message : `This email already backs ${result.limit} active agents \u2014 the per-email limit.`} Use a different email (a +alias works), recover a key for one of its agents, or paste an existing key.`;
630
710
  case "email-exhausted":
631
- return "This email has reached the agent quota. Use a different email, or paste a key from an existing agent.";
711
+ return `${result.limit === void 0 ? result.message : `This email has used all ${result.limit} of its lifetime account registrations.`} Use a different email (a +alias works), or paste a key from an existing agent.`;
632
712
  case "rate-limited": {
633
713
  const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
634
714
  return `Rate limited.${wait}`;
@@ -650,8 +730,158 @@ function describeRegisterVerifyError(result) {
650
730
  return "Too many incorrect codes. Restart the wizard to receive a new one.";
651
731
  case "handle-taken":
652
732
  return "Your chosen handle was claimed by another registration in the meantime. Restart with a different handle.";
653
- case "email-taken":
654
- return "This email is already registered. Paste the existing key instead.";
733
+ case "email-limit-reached":
734
+ return `${result.limit === void 0 ? result.message : `This email reached its limit of ${result.limit} active agents while you were verifying.`} Restart with a different email (a +alias works), or paste an existing key.`;
735
+ case "email-exhausted":
736
+ return `${result.limit === void 0 ? result.message : `This email used all ${result.limit} of its lifetime account registrations while you were verifying.`} Restart with a different email (a +alias works), or paste an existing key.`;
737
+ case "rate-limited": {
738
+ const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
739
+ return `Rate limited.${wait}`;
740
+ }
741
+ case "network-error":
742
+ case "server-error":
743
+ case "unexpected-shape":
744
+ case "validation":
745
+ default:
746
+ return result.message;
747
+ }
748
+ }
749
+ async function runRecoverFlow(params) {
750
+ const { cfg, accountId, prompter, apiBase } = params;
751
+ const storedHandle = readAgentchatConfigField(cfg, accountId, "agentHandle");
752
+ const defaultHandle = storedHandle && isValidHandleShape(storedHandle) ? storedHandle : void 0;
753
+ await prompter.note(
754
+ [
755
+ "Recovery re-issues the API key for ONE agent \u2014 the handle you name below.",
756
+ "You need that handle and the email it registered with; a 6-digit code",
757
+ "goes to that email. The old key stops working the moment the new one",
758
+ "is minted.",
759
+ ...defaultHandle ? ["", `@${defaultHandle} is configured here \u2014 press Enter at the handle prompt to recover it.`] : []
760
+ ].join("\n"),
761
+ "AgentChat: recover a lost API key"
762
+ );
763
+ const handle = await promptHandle(prompter, {
764
+ message: "Handle of the agent to recover (its @name on AgentChat)",
765
+ ...defaultHandle ? { initialValue: defaultHandle } : {}
766
+ });
767
+ const email = await promptEmail(prompter, {
768
+ message: `Email @${handle} registered with \u2014 receives a 6-digit recovery code`
769
+ });
770
+ const startSpinner = prompter.progress("Requesting recovery code\u2026");
771
+ let startResult;
772
+ try {
773
+ startResult = await recoverAgentStart({ email, handle }, { apiBase });
774
+ } catch (err3) {
775
+ startSpinner.stop("Could not reach AgentChat");
776
+ await prompter.note(
777
+ `${err3 instanceof Error ? err3.message : String(err3)}. Try again when the network is available, or paste an existing key instead.`,
778
+ "Recovery failed"
779
+ );
780
+ return "abort";
781
+ }
782
+ if (!startResult.ok) {
783
+ startSpinner.stop("Recovery rejected");
784
+ await prompter.note(describeRecoverStartError(startResult), "Could not start recovery");
785
+ return "abort";
786
+ }
787
+ startSpinner.stop(startResult.message);
788
+ const maxCodeAttempts = 3;
789
+ let verifyResult = null;
790
+ for (let attempt = 1; attempt <= maxCodeAttempts; attempt += 1) {
791
+ const code = (await prompter.text({
792
+ message: attempt === 1 ? `Enter the 6-digit recovery code (check ${email}, including spam)` : `Recovery code (attempt ${attempt}/${maxCodeAttempts})`,
793
+ placeholder: "123456",
794
+ validate: (value) => {
795
+ const trimmed = value.trim();
796
+ if (!trimmed) return "Code is required";
797
+ if (!OTP_PATTERN.test(trimmed)) return "Code is 6 digits";
798
+ return void 0;
799
+ }
800
+ })).trim();
801
+ const verifySpinner = prompter.progress("Verifying code\u2026");
802
+ try {
803
+ verifyResult = await recoverAgentVerify({ pendingId: startResult.pendingId, code }, { apiBase });
804
+ } catch (err3) {
805
+ verifySpinner.stop("Could not reach AgentChat");
806
+ await prompter.note(
807
+ `${err3 instanceof Error ? err3.message : String(err3)}. Try again, or paste an existing key instead.`,
808
+ "Recovery failed"
809
+ );
810
+ return "abort";
811
+ }
812
+ if (verifyResult.ok) {
813
+ verifySpinner.stop(`Recovered @${verifyResult.handle}`);
814
+ break;
815
+ }
816
+ verifySpinner.stop("Verification failed");
817
+ if (verifyResult.reason === "invalid-code" && attempt < maxCodeAttempts) {
818
+ await prompter.note(
819
+ "That code did not match. Check your email and try again \u2014 if no code arrived, the handle and email may not belong to the same agent.",
820
+ "Invalid recovery code"
821
+ );
822
+ continue;
823
+ }
824
+ await prompter.note(describeRecoverVerifyError(verifyResult), "Recovery failed");
825
+ return "abort";
826
+ }
827
+ if (!verifyResult || !verifyResult.ok) {
828
+ await prompter.note(
829
+ "Too many incorrect codes. Restart the wizard to request a new one \u2014 and double-check the handle and email belong to the same agent.",
830
+ "Recovery failed"
831
+ );
832
+ return "abort";
833
+ }
834
+ const patch = { apiKey: verifyResult.apiKey };
835
+ if (isValidHandleShape(verifyResult.handle)) {
836
+ patch.agentHandle = verifyResult.handle;
837
+ }
838
+ const nextCfg = applyAgentchatAccountPatch(cfg, accountId, patch);
839
+ await prompter.note(
840
+ [
841
+ `Handle: @${verifyResult.handle}`,
842
+ `API key: ${redactKey(verifyResult.apiKey)} (saved to your OpenClaw config)`,
843
+ "",
844
+ "The previous key for this agent has been revoked."
845
+ ].join("\n"),
846
+ "AgentChat API key recovered"
847
+ );
848
+ return {
849
+ cfg: nextCfg,
850
+ credentialValues: {
851
+ token: verifyResult.apiKey,
852
+ [JUST_REGISTERED_SENTINEL]: "1"
853
+ }
854
+ };
855
+ }
856
+ function describeRecoverStartError(result) {
857
+ switch (result.reason) {
858
+ case "rate-limited": {
859
+ const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
860
+ return `Too many recovery attempts from this network.${wait}`;
861
+ }
862
+ case "validation":
863
+ return `AgentChat rejected the request: ${result.message}`;
864
+ case "network-error":
865
+ case "server-error":
866
+ case "unexpected-shape":
867
+ default:
868
+ return result.message;
869
+ }
870
+ }
871
+ function describeRecoverVerifyError(result) {
872
+ switch (result.reason) {
873
+ case "expired":
874
+ return "This recovery code expired. Restart the wizard to request a new one.";
875
+ case "invalid-code":
876
+ return "Too many incorrect codes. Restart the wizard to request a new one.";
877
+ case "handle-required": {
878
+ const handles = result.handles ?? [];
879
+ const list = handles.length > 0 ? `
880
+
881
+ Agents on this email:
882
+ ${handles.map((h) => ` @${h}`).join("\n")}` : "";
883
+ return `This email backs more than one agent. Run recovery again and enter the handle you want to recover.${list}`;
884
+ }
655
885
  case "rate-limited": {
656
886
  const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
657
887
  return `Rate limited.${wait}`;
@@ -699,7 +929,7 @@ var agentchatSetupWizard = {
699
929
  resolveStatusLines: ({ cfg, accountId, configured }) => {
700
930
  const id = accountId ?? "default";
701
931
  if (!configured) {
702
- return ["AgentChat: not configured \u2014 the wizard will register you or accept an existing key."];
932
+ return ["AgentChat: not configured \u2014 the wizard will register you, accept an existing key, or recover a lost one."];
703
933
  }
704
934
  const handle = readAgentchatConfigField(cfg, id, "agentHandle");
705
935
  return [`AgentChat: configured${handle ? ` (@${handle})` : ""}`];
@@ -711,8 +941,9 @@ var agentchatSetupWizard = {
711
941
  "AgentChat is a messaging platform for AI agents \u2014 direct messages,",
712
942
  "groups, presence, attachments. Registration is free.",
713
943
  "",
714
- "This wizard will either mint a new account via email OTP, or accept",
715
- "an existing API key \u2014 your choice in the next prompt."
944
+ "This wizard will mint a new account via email OTP, accept an existing",
945
+ "API key, or recover a lost key (handle + email OTP) \u2014 your choice in",
946
+ "the next prompt."
716
947
  ]
717
948
  },
718
949
  prepare: async ({ cfg, accountId, credentialValues, prompter }) => {
@@ -733,7 +964,7 @@ var agentchatSetupWizard = {
733
964
  {
734
965
  value: "replace-key",
735
966
  label: "Replace the API key",
736
- hint: "paste a new key, or register a new agent"
967
+ hint: "paste a new key, register a new agent, or recover a lost key"
737
968
  }
738
969
  ],
739
970
  initialValue: "keep"
@@ -755,6 +986,11 @@ var agentchatSetupWizard = {
755
986
  value: "paste",
756
987
  label: "I already have an API key",
757
988
  hint: "paste ac_live_\u2026 on the next prompt"
989
+ },
990
+ {
991
+ value: "recover",
992
+ label: "Recover a lost API key (handle + email OTP)",
993
+ hint: "an agent exists but its key is gone \u2014 re-issue it"
758
994
  }
759
995
  ],
760
996
  initialValue: "register"
@@ -763,19 +999,32 @@ var agentchatSetupWizard = {
763
999
  return;
764
1000
  }
765
1001
  const apiBase = readAgentchatConfigField(cfg, accountId, "apiBase");
766
- try {
767
- const result = await runRegisterFlow({ cfg, accountId, prompter, apiBase });
768
- if (result === "abort") {
1002
+ const recover = async () => {
1003
+ try {
1004
+ const result = await runRecoverFlow({ cfg, accountId, prompter, apiBase });
1005
+ if (result === "abort") {
1006
+ await prompter.note(
1007
+ "Recovery was not completed. You can still paste an existing API key at the next prompt, or cancel the wizard.",
1008
+ "Falling back to credential entry"
1009
+ );
1010
+ return;
1011
+ }
1012
+ return result;
1013
+ } catch (err3) {
1014
+ if (err3 instanceof setup.WizardCancelledError) throw err3;
769
1015
  await prompter.note(
770
- "Registration was not completed. You can still paste an existing API key at the next prompt, or cancel the wizard.",
771
- "Falling back to credential entry"
1016
+ `${err3 instanceof Error ? err3.message : String(err3)}`,
1017
+ "Recovery flow failed"
772
1018
  );
773
1019
  return;
774
1020
  }
775
- if (result === "user-chose-paste") {
776
- return;
777
- }
778
- return result;
1021
+ };
1022
+ if (choice === "recover") {
1023
+ return await recover();
1024
+ }
1025
+ let registerOutcome;
1026
+ try {
1027
+ registerOutcome = await runRegisterFlow({ cfg, accountId, prompter, apiBase });
779
1028
  } catch (err3) {
780
1029
  if (err3 instanceof setup.WizardCancelledError) throw err3;
781
1030
  await prompter.note(
@@ -784,6 +1033,20 @@ var agentchatSetupWizard = {
784
1033
  );
785
1034
  return;
786
1035
  }
1036
+ if (registerOutcome === "abort") {
1037
+ await prompter.note(
1038
+ "Registration was not completed. You can still paste an existing API key at the next prompt, or cancel the wizard.",
1039
+ "Falling back to credential entry"
1040
+ );
1041
+ return;
1042
+ }
1043
+ if (registerOutcome === "user-chose-paste") {
1044
+ return;
1045
+ }
1046
+ if (registerOutcome === "user-chose-recover") {
1047
+ return await recover();
1048
+ }
1049
+ return registerOutcome;
787
1050
  },
788
1051
  credentials: [
789
1052
  {
@@ -5121,7 +5384,7 @@ var uiHints = {
5121
5384
  label: "AgentChat API key",
5122
5385
  placeholder: "ac_live_...",
5123
5386
  sensitive: true,
5124
- help: "The setup wizard registers you via email OTP and mints a key \u2014 or paste an existing ac_live_\u2026 key."
5387
+ help: "The setup wizard registers you via email OTP and mints a key, recovers a lost key (handle + email OTP), or accepts an existing ac_live_\u2026 key."
5125
5388
  },
5126
5389
  apiBase: {
5127
5390
  label: "API base URL",
@@ -5232,7 +5495,7 @@ var agentchatPlugin = {
5232
5495
  */
5233
5496
  validateInput({ input }) {
5234
5497
  if (typeof input.token !== "string" || input.token.trim().length === 0) {
5235
- return "apiKey is required \u2014 pass via --token or run the register flow";
5498
+ return "apiKey is required \u2014 pass via --token, or run `openclaw channels add agentchat` to register a new agent or recover a lost key (handle + email OTP)";
5236
5499
  }
5237
5500
  if (input.token.length < MIN_API_KEY_LENGTH) {
5238
5501
  return `apiKey looks too short (got ${input.token.length} chars, expect \u2265${MIN_API_KEY_LENGTH})`;