@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/CHANGELOG.md +67 -0
- package/dist/configured-state.cjs.map +1 -1
- package/dist/configured-state.js.map +1 -1
- package/dist/index.cjs +544 -475
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -117
- package/dist/index.d.ts +120 -117
- package/dist/index.js +545 -475
- package/dist/index.js.map +1 -1
- package/dist/{setup-entry-CU0vHfyd.d.cts → setup-entry-1vjPD1gu.d.cts} +5 -9
- package/dist/{setup-entry-CU0vHfyd.d.ts → setup-entry-1vjPD1gu.d.ts} +5 -9
- package/dist/setup-entry.cjs +541 -471
- package/dist/setup-entry.cjs.map +1 -1
- package/dist/setup-entry.d.cts +1 -1
- package/dist/setup-entry.d.ts +1 -1
- package/dist/setup-entry.js +541 -471
- package/dist/setup-entry.js.map +1 -1
- package/openclaw.plugin.json +5 -3
- package/package.json +128 -129
- package/skills/agentchat/SKILL.md +128 -100
package/dist/setup-entry.cjs
CHANGED
|
@@ -3,42 +3,66 @@
|
|
|
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
|
|
|
8
9
|
// src/channel.setup.ts
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
function
|
|
41
|
-
|
|
10
|
+
|
|
11
|
+
// src/channel-account.ts
|
|
12
|
+
var AGENTCHAT_CHANNEL_ID = "agentchat";
|
|
13
|
+
var AGENTCHAT_DEFAULT_ACCOUNT_ID = "default";
|
|
14
|
+
var MIN_API_KEY_LENGTH = 20;
|
|
15
|
+
function readChannelSection(cfg) {
|
|
16
|
+
const channels = cfg?.channels;
|
|
17
|
+
const section = channels?.[AGENTCHAT_CHANNEL_ID];
|
|
18
|
+
return section && typeof section === "object" && !Array.isArray(section) ? section : void 0;
|
|
19
|
+
}
|
|
20
|
+
function readAccountRaw(section, accountId) {
|
|
21
|
+
if (!section) return void 0;
|
|
22
|
+
const { accounts } = section;
|
|
23
|
+
if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
|
|
24
|
+
const entry = accounts[accountId];
|
|
25
|
+
if (entry && typeof entry === "object") return entry;
|
|
26
|
+
}
|
|
27
|
+
if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID) {
|
|
28
|
+
const { accounts: _accounts, ...rest } = section;
|
|
29
|
+
return rest;
|
|
30
|
+
}
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
function splitEnabledFromRaw(raw) {
|
|
34
|
+
if (!raw) return { enabled: true, forParse: void 0 };
|
|
35
|
+
const { enabled, ...rest } = raw;
|
|
36
|
+
return { enabled: enabled !== false, forParse: rest };
|
|
37
|
+
}
|
|
38
|
+
function isApiKeyPresent(value) {
|
|
39
|
+
return typeof value === "string" && value.length >= MIN_API_KEY_LENGTH;
|
|
40
|
+
}
|
|
41
|
+
function readAgentchatConfigField(cfg, accountId, field) {
|
|
42
|
+
const section = readChannelSection(cfg);
|
|
43
|
+
const raw = readAccountRaw(section, accountId);
|
|
44
|
+
if (!raw) return void 0;
|
|
45
|
+
const value = raw[field];
|
|
46
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
47
|
+
}
|
|
48
|
+
function applyAgentchatAccountPatch(cfg, accountId, patch) {
|
|
49
|
+
const channels = {
|
|
50
|
+
...cfg?.channels ?? {}
|
|
51
|
+
};
|
|
52
|
+
const currentSection = channels[AGENTCHAT_CHANNEL_ID];
|
|
53
|
+
const section = currentSection && typeof currentSection === "object" && !Array.isArray(currentSection) ? { ...currentSection } : {};
|
|
54
|
+
if (accountId === AGENTCHAT_DEFAULT_ACCOUNT_ID && !section.accounts) {
|
|
55
|
+
Object.assign(section, patch);
|
|
56
|
+
} else {
|
|
57
|
+
const accounts = {
|
|
58
|
+
...section.accounts ?? {}
|
|
59
|
+
};
|
|
60
|
+
const prevAccount = typeof accounts[accountId] === "object" && accounts[accountId] !== null ? accounts[accountId] : {};
|
|
61
|
+
accounts[accountId] = { ...prevAccount, ...patch };
|
|
62
|
+
section.accounts = accounts;
|
|
63
|
+
}
|
|
64
|
+
channels[AGENTCHAT_CHANNEL_ID] = section;
|
|
65
|
+
return { ...cfg ?? {}, channels };
|
|
42
66
|
}
|
|
43
67
|
|
|
44
68
|
// src/setup-client.ts
|
|
@@ -141,7 +165,6 @@ async function registerAgentStart(input, opts = {}) {
|
|
|
141
165
|
if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
|
|
142
166
|
if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
|
|
143
167
|
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
168
|
if (res.status === 409 && code === "EMAIL_EXHAUSTED") return { ok: false, reason: "email-exhausted", message, status: 409 };
|
|
146
169
|
if (res.status === 429) {
|
|
147
170
|
return {
|
|
@@ -188,7 +211,6 @@ async function registerAgentVerify(input, opts = {}) {
|
|
|
188
211
|
if (res.status === 400 && code === "VALIDATION_ERROR") return { ok: false, reason: "validation", message, status: 400 };
|
|
189
212
|
if (res.status === 409 && code === "HANDLE_TAKEN") return { ok: false, reason: "handle-taken", message, status: 409 };
|
|
190
213
|
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
214
|
if (res.status === 429) {
|
|
193
215
|
return { ok: false, reason: "rate-limited", message, status: 429, retryAfterSeconds: res.retryAfterSeconds };
|
|
194
216
|
}
|
|
@@ -225,487 +247,551 @@ async function post(path, body, opts) {
|
|
|
225
247
|
}
|
|
226
248
|
}
|
|
227
249
|
|
|
228
|
-
// src/
|
|
229
|
-
var
|
|
230
|
-
var
|
|
231
|
-
var
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return
|
|
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 };
|
|
250
|
+
// src/channel.wizard.ts
|
|
251
|
+
var JUST_REGISTERED_SENTINEL = "_agentchatJustRegistered";
|
|
252
|
+
var HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
253
|
+
var HANDLE_MIN_LENGTH = 3;
|
|
254
|
+
var HANDLE_MAX_LENGTH = 30;
|
|
255
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
256
|
+
function isValidHandleShape(value) {
|
|
257
|
+
return value.length >= HANDLE_MIN_LENGTH && value.length <= HANDLE_MAX_LENGTH && HANDLE_PATTERN.test(value);
|
|
284
258
|
}
|
|
285
|
-
|
|
286
|
-
|
|
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();
|
|
259
|
+
var OTP_PATTERN = /^\d{6}$/;
|
|
260
|
+
function hasConfiguredKey(cfg, accountId) {
|
|
261
|
+
return isApiKeyPresent(readAgentchatConfigField(cfg, accountId, "apiKey"));
|
|
301
262
|
}
|
|
263
|
+
var MAX_START_RETRIES = 5;
|
|
302
264
|
async function promptEmail(prompter) {
|
|
303
|
-
|
|
304
|
-
message: "
|
|
265
|
+
return (await prompter.text({
|
|
266
|
+
message: "Email address (for the verification code)",
|
|
305
267
|
placeholder: "you@example.com",
|
|
306
|
-
validate: (
|
|
307
|
-
const trimmed =
|
|
268
|
+
validate: (value) => {
|
|
269
|
+
const trimmed = value.trim();
|
|
308
270
|
if (!trimmed) return "Email is required";
|
|
309
|
-
if (
|
|
271
|
+
if (!EMAIL_PATTERN.test(trimmed)) return "That does not look like a valid email address";
|
|
310
272
|
return void 0;
|
|
311
273
|
}
|
|
312
|
-
});
|
|
313
|
-
return value.trim();
|
|
274
|
+
})).trim();
|
|
314
275
|
}
|
|
315
|
-
async function promptHandle(prompter
|
|
316
|
-
|
|
317
|
-
message: "
|
|
318
|
-
placeholder: "
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const trimmed = v.trim().toLowerCase();
|
|
276
|
+
async function promptHandle(prompter) {
|
|
277
|
+
return (await prompter.text({
|
|
278
|
+
message: "Choose a handle (your @name on AgentChat)",
|
|
279
|
+
placeholder: "my-agent",
|
|
280
|
+
validate: (value) => {
|
|
281
|
+
const trimmed = value.trim();
|
|
322
282
|
if (!trimmed) return "Handle is required";
|
|
323
|
-
if (!
|
|
324
|
-
return "3\
|
|
283
|
+
if (!isValidHandleShape(trimmed)) {
|
|
284
|
+
return "Handle must be 3\u201330 chars \u2014 lowercase letters/digits/hyphens; must start with a letter";
|
|
285
|
+
}
|
|
325
286
|
return void 0;
|
|
326
287
|
}
|
|
327
|
-
});
|
|
328
|
-
return value.trim().toLowerCase();
|
|
288
|
+
})).trim();
|
|
329
289
|
}
|
|
330
|
-
async function
|
|
331
|
-
|
|
332
|
-
message: "
|
|
333
|
-
placeholder: "
|
|
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,
|
|
290
|
+
async function promptDisplayName(prompter) {
|
|
291
|
+
return (await prompter.text({
|
|
292
|
+
message: "Display name (optional \u2014 shown next to your handle)",
|
|
293
|
+
placeholder: "",
|
|
342
294
|
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
|
-
}
|
|
295
|
+
})).trim();
|
|
369
296
|
}
|
|
370
|
-
function
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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.");
|
|
297
|
+
async function runChangeApiBaseFlow(params) {
|
|
298
|
+
const { cfg, accountId, prompter } = params;
|
|
299
|
+
const current = readAgentchatConfigField(cfg, accountId, "apiBase");
|
|
300
|
+
const input = (await prompter.text({
|
|
301
|
+
message: "New API base URL (blank to reset to default)",
|
|
302
|
+
placeholder: current ?? "https://api.agentchat.me",
|
|
303
|
+
validate: (value) => {
|
|
304
|
+
const trimmed = value.trim();
|
|
305
|
+
if (!trimmed) return void 0;
|
|
306
|
+
try {
|
|
307
|
+
const url = new URL(trimmed);
|
|
308
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
309
|
+
return "API base must use http:// or https://";
|
|
310
|
+
}
|
|
311
|
+
return void 0;
|
|
312
|
+
} catch {
|
|
313
|
+
return "Not a valid URL";
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
})).trim();
|
|
317
|
+
if (!input) {
|
|
318
|
+
const patched2 = applyAgentchatAccountPatch(cfg, accountId, {
|
|
319
|
+
apiBase: void 0
|
|
320
|
+
});
|
|
400
321
|
await prompter.note(
|
|
401
|
-
|
|
402
|
-
|
|
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"
|
|
322
|
+
"API base reset to default (https://api.agentchat.me).",
|
|
323
|
+
"Updated"
|
|
408
324
|
);
|
|
409
|
-
return
|
|
325
|
+
return { cfg: patched2 };
|
|
410
326
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
apiKey,
|
|
414
|
-
agentHandle: result.agent.handle,
|
|
415
|
-
...apiBase ? { apiBase } : {}
|
|
327
|
+
const patched = applyAgentchatAccountPatch(cfg, accountId, {
|
|
328
|
+
apiBase: input
|
|
416
329
|
});
|
|
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 };
|
|
330
|
+
await prompter.note(`API base set to ${input}`, "Updated");
|
|
331
|
+
return { cfg: patched };
|
|
426
332
|
}
|
|
427
|
-
async function runRegisterFlow(
|
|
333
|
+
async function runRegisterFlow(params) {
|
|
334
|
+
const { cfg, accountId, prompter, apiBase } = params;
|
|
428
335
|
await prompter.note(
|
|
429
336
|
[
|
|
430
|
-
"
|
|
431
|
-
"
|
|
337
|
+
"Registration mints a new AgentChat agent identity tied to your email.",
|
|
338
|
+
"You will receive a 6-digit code to verify \u2014 check your inbox (and spam)."
|
|
432
339
|
].join("\n"),
|
|
433
|
-
"
|
|
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
|
|
340
|
+
"AgentChat: register a new agent"
|
|
441
341
|
);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
342
|
+
let email = await promptEmail(prompter);
|
|
343
|
+
let handle = await promptHandle(prompter);
|
|
344
|
+
const displayName = await promptDisplayName(prompter);
|
|
345
|
+
let startResult;
|
|
346
|
+
let startedOk = false;
|
|
347
|
+
for (let attempt = 1; attempt <= MAX_START_RETRIES; attempt += 1) {
|
|
348
|
+
const startSpinner = prompter.progress(
|
|
349
|
+
attempt === 1 ? "Sending verification code\u2026" : "Retrying\u2026"
|
|
350
|
+
);
|
|
351
|
+
try {
|
|
352
|
+
startResult = await registerAgentStart(
|
|
353
|
+
{
|
|
354
|
+
email,
|
|
355
|
+
handle,
|
|
356
|
+
...displayName ? { displayName } : {}
|
|
357
|
+
},
|
|
358
|
+
{ apiBase }
|
|
359
|
+
);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
startSpinner.stop("Could not reach AgentChat");
|
|
362
|
+
await prompter.note(
|
|
363
|
+
`${err instanceof Error ? err.message : String(err)}. Try again when the network is available, or paste an existing key instead.`,
|
|
364
|
+
"Registration failed"
|
|
365
|
+
);
|
|
366
|
+
return "abort";
|
|
367
|
+
}
|
|
368
|
+
if (startResult.ok) {
|
|
369
|
+
startSpinner.stop(`Verification code sent to ${email}`);
|
|
370
|
+
startedOk = true;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
startSpinner.stop("Registration rejected");
|
|
374
|
+
switch (startResult.reason) {
|
|
375
|
+
case "invalid-handle":
|
|
376
|
+
case "handle-taken": {
|
|
377
|
+
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).";
|
|
378
|
+
await prompter.note(`${detail} Pick a different handle and we'll try again.`, "Pick a different handle");
|
|
379
|
+
handle = await promptHandle(prompter);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
case "email-taken": {
|
|
383
|
+
const choice = await prompter.select({
|
|
384
|
+
message: `${email} is already registered as an AgentChat agent. What would you like to do?`,
|
|
385
|
+
options: [
|
|
386
|
+
{
|
|
387
|
+
value: "paste",
|
|
388
|
+
label: "Paste the existing API key for this agent",
|
|
389
|
+
hint: "recommended if you own the account"
|
|
390
|
+
},
|
|
391
|
+
{ value: "retry", label: "Use a different email address" },
|
|
392
|
+
{ value: "cancel", label: "Cancel registration" }
|
|
393
|
+
],
|
|
394
|
+
initialValue: "paste"
|
|
395
|
+
});
|
|
396
|
+
if (choice === "paste") return "user-chose-paste";
|
|
397
|
+
if (choice === "cancel") return "abort";
|
|
398
|
+
email = await promptEmail(prompter);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
case "email-exhausted": {
|
|
402
|
+
const choice = await prompter.select({
|
|
403
|
+
message: `${email} has reached the per-email agent quota. What next?`,
|
|
404
|
+
options: [
|
|
405
|
+
{ value: "retry", label: "Use a different email address" },
|
|
406
|
+
{ value: "paste", label: "Paste a key from an existing agent" },
|
|
407
|
+
{ value: "cancel", label: "Cancel registration" }
|
|
408
|
+
],
|
|
409
|
+
initialValue: "retry"
|
|
410
|
+
});
|
|
411
|
+
if (choice === "paste") return "user-chose-paste";
|
|
412
|
+
if (choice === "cancel") return "abort";
|
|
413
|
+
email = await promptEmail(prompter);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
case "rate-limited": {
|
|
417
|
+
const wait = startResult.retryAfterSeconds ? ` Try again in ${startResult.retryAfterSeconds}s.` : "";
|
|
418
|
+
await prompter.note(`Too many registration attempts.${wait}`, "Rate limited");
|
|
419
|
+
return "abort";
|
|
420
|
+
}
|
|
421
|
+
case "otp-failed": {
|
|
422
|
+
await prompter.note(
|
|
423
|
+
"The verification-code email could not be sent. Try again in a minute, or paste an existing key instead.",
|
|
424
|
+
"OTP delivery failed"
|
|
425
|
+
);
|
|
426
|
+
return "abort";
|
|
427
|
+
}
|
|
428
|
+
case "network-error":
|
|
429
|
+
case "server-error":
|
|
430
|
+
case "validation":
|
|
431
|
+
default: {
|
|
432
|
+
await prompter.note(describeRegisterStartError(startResult), "Could not start registration");
|
|
433
|
+
return "abort";
|
|
434
|
+
}
|
|
435
|
+
}
|
|
466
436
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
437
|
+
if (!startedOk || !startResult || !startResult.ok) {
|
|
438
|
+
await prompter.note(
|
|
439
|
+
"Too many attempts. Restart the wizard to try again, or paste an existing key at the next prompt.",
|
|
440
|
+
"Registration failed"
|
|
441
|
+
);
|
|
442
|
+
return "abort";
|
|
443
|
+
}
|
|
444
|
+
const maxCodeAttempts = 3;
|
|
445
|
+
let verifyResult = null;
|
|
446
|
+
for (let attempt = 1; attempt <= maxCodeAttempts; attempt += 1) {
|
|
447
|
+
const code = (await prompter.text({
|
|
448
|
+
message: attempt === 1 ? "Enter the 6-digit verification code from your email" : `Verification code (attempt ${attempt}/${maxCodeAttempts})`,
|
|
449
|
+
placeholder: "123456",
|
|
450
|
+
validate: (value) => {
|
|
451
|
+
const trimmed = value.trim();
|
|
452
|
+
if (!trimmed) return "Code is required";
|
|
453
|
+
if (!OTP_PATTERN.test(trimmed)) return "Code is 6 digits";
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
})).trim();
|
|
457
|
+
const verifySpinner = prompter.progress("Verifying code\u2026");
|
|
458
|
+
try {
|
|
459
|
+
verifyResult = await registerAgentVerify({ pendingId: startResult.pendingId, code }, { apiBase });
|
|
460
|
+
} catch (err) {
|
|
461
|
+
verifySpinner.stop("Could not reach AgentChat");
|
|
480
462
|
await prompter.note(
|
|
481
|
-
|
|
482
|
-
|
|
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"
|
|
463
|
+
`${err instanceof Error ? err.message : String(err)}. Try again, or paste an existing key instead.`,
|
|
464
|
+
"Verification failed"
|
|
491
465
|
);
|
|
492
|
-
return
|
|
466
|
+
return "abort";
|
|
493
467
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return runRegisterFlow(cfg, accountId, prompter, apiBase);
|
|
468
|
+
if (verifyResult.ok) {
|
|
469
|
+
verifySpinner.stop(`Registered as @${verifyResult.agent.handle}`);
|
|
470
|
+
break;
|
|
498
471
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
472
|
+
verifySpinner.stop("Verification failed");
|
|
473
|
+
if (verifyResult.reason === "invalid-code" && attempt < maxCodeAttempts) {
|
|
474
|
+
await prompter.note(
|
|
475
|
+
"That code did not match. Check your email and try again.",
|
|
476
|
+
"Invalid verification code"
|
|
477
|
+
);
|
|
478
|
+
continue;
|
|
506
479
|
}
|
|
480
|
+
await prompter.note(describeRegisterVerifyError(verifyResult), "Registration failed");
|
|
481
|
+
return "abort";
|
|
507
482
|
}
|
|
483
|
+
if (!verifyResult || !verifyResult.ok) {
|
|
484
|
+
await prompter.note(
|
|
485
|
+
"Too many incorrect codes. Restart the wizard to receive a new code.",
|
|
486
|
+
"Registration failed"
|
|
487
|
+
);
|
|
488
|
+
return "abort";
|
|
489
|
+
}
|
|
490
|
+
const patch = { apiKey: verifyResult.apiKey };
|
|
491
|
+
if (isValidHandleShape(verifyResult.agent.handle)) {
|
|
492
|
+
patch.agentHandle = verifyResult.agent.handle;
|
|
493
|
+
}
|
|
494
|
+
const nextCfg = applyAgentchatAccountPatch(cfg, accountId, patch);
|
|
508
495
|
await prompter.note(
|
|
509
|
-
|
|
510
|
-
|
|
496
|
+
[
|
|
497
|
+
`Handle: @${verifyResult.agent.handle}`,
|
|
498
|
+
`Email: ${verifyResult.agent.email}`,
|
|
499
|
+
`API key: ${redactKey(verifyResult.apiKey)} (saved to your OpenClaw config)`
|
|
500
|
+
].join("\n"),
|
|
501
|
+
"AgentChat account created"
|
|
511
502
|
);
|
|
512
|
-
return {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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);
|
|
503
|
+
return {
|
|
504
|
+
cfg: nextCfg,
|
|
505
|
+
credentialValues: {
|
|
506
|
+
token: verifyResult.apiKey,
|
|
507
|
+
[JUST_REGISTERED_SENTINEL]: "1"
|
|
544
508
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function describeRegisterStartError(result) {
|
|
512
|
+
switch (result.reason) {
|
|
513
|
+
case "invalid-handle":
|
|
514
|
+
return "That handle is not acceptable. Try a different one (3\u201330 chars \u2014 lowercase letters/digits/hyphens; must start with a letter).";
|
|
515
|
+
case "handle-taken":
|
|
516
|
+
return "That handle is already taken. Try a different one.";
|
|
517
|
+
case "email-taken":
|
|
518
|
+
return "That email is already registered as an agent. Paste the existing key instead, or use a different email.";
|
|
519
|
+
case "email-exhausted":
|
|
520
|
+
return "This email has reached the agent quota. Use a different email, or paste a key from an existing agent.";
|
|
521
|
+
case "rate-limited": {
|
|
522
|
+
const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
|
|
523
|
+
return `Rate limited.${wait}`;
|
|
550
524
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return { cfg };
|
|
559
|
-
}
|
|
560
|
-
if (choice === "rotate") {
|
|
561
|
-
return runNewAccountFlow(cfg, accountId, prompter, currentBase);
|
|
525
|
+
case "otp-failed":
|
|
526
|
+
return "The verification-code email could not be sent. Try again in a minute.";
|
|
527
|
+
case "network-error":
|
|
528
|
+
case "server-error":
|
|
529
|
+
case "validation":
|
|
530
|
+
default:
|
|
531
|
+
return result.message;
|
|
562
532
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
return { cfg };
|
|
533
|
+
}
|
|
534
|
+
function describeRegisterVerifyError(result) {
|
|
535
|
+
switch (result.reason) {
|
|
536
|
+
case "expired":
|
|
537
|
+
return "This code expired. Restart the wizard to receive a new one.";
|
|
538
|
+
case "invalid-code":
|
|
539
|
+
return "Too many incorrect codes. Restart the wizard to receive a new one.";
|
|
540
|
+
case "handle-taken":
|
|
541
|
+
return "Your chosen handle was claimed by another registration in the meantime. Restart with a different handle.";
|
|
542
|
+
case "email-taken":
|
|
543
|
+
return "This email is already registered. Paste the existing key instead.";
|
|
544
|
+
case "rate-limited": {
|
|
545
|
+
const wait = result.retryAfterSeconds ? ` Try again in ${result.retryAfterSeconds}s.` : "";
|
|
546
|
+
return `Rate limited.${wait}`;
|
|
578
547
|
}
|
|
548
|
+
case "network-error":
|
|
549
|
+
case "server-error":
|
|
550
|
+
case "unexpected-shape":
|
|
551
|
+
case "validation":
|
|
552
|
+
default:
|
|
553
|
+
return result.message;
|
|
579
554
|
}
|
|
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
555
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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);
|
|
556
|
+
function redactKey(apiKey) {
|
|
557
|
+
if (apiKey.length < 12) return "\u2022\u2022\u2022\u2022";
|
|
558
|
+
return `${apiKey.slice(0, 8)}\u2026${apiKey.slice(-4)}`;
|
|
606
559
|
}
|
|
607
560
|
var agentchatSetupWizard = {
|
|
608
561
|
channel: AGENTCHAT_CHANNEL_ID,
|
|
609
|
-
resolveShouldPromptAccountIds: () => false,
|
|
610
562
|
status: {
|
|
611
|
-
configuredLabel: "
|
|
612
|
-
unconfiguredLabel: "
|
|
613
|
-
configuredHint: "
|
|
614
|
-
unconfiguredHint: "
|
|
615
|
-
configuredScore:
|
|
616
|
-
unconfiguredScore:
|
|
617
|
-
resolveConfigured: ({ cfg, accountId }) =>
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
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)"];
|
|
563
|
+
configuredLabel: "configured",
|
|
564
|
+
unconfiguredLabel: "not configured",
|
|
565
|
+
configuredHint: "AgentChat agent is ready to receive messages",
|
|
566
|
+
unconfiguredHint: "connect your agent to the AgentChat messaging platform",
|
|
567
|
+
configuredScore: 90,
|
|
568
|
+
unconfiguredScore: 30,
|
|
569
|
+
resolveConfigured: ({ cfg, accountId }) => {
|
|
570
|
+
return hasConfiguredKey(cfg, accountId ?? "default");
|
|
571
|
+
},
|
|
572
|
+
resolveStatusLines: ({ cfg, accountId, configured }) => {
|
|
573
|
+
const id = accountId ?? "default";
|
|
574
|
+
if (!configured) {
|
|
575
|
+
return ["AgentChat: not configured \u2014 the wizard will register you or accept an existing key."];
|
|
630
576
|
}
|
|
577
|
+
const handle = readAgentchatConfigField(cfg, id, "agentHandle");
|
|
578
|
+
return [`AgentChat: configured${handle ? ` (@${handle})` : ""}`];
|
|
631
579
|
}
|
|
632
580
|
},
|
|
633
581
|
introNote: {
|
|
634
582
|
title: "AgentChat",
|
|
635
583
|
lines: [
|
|
636
|
-
"
|
|
637
|
-
"
|
|
584
|
+
"AgentChat is a messaging platform for AI agents \u2014 direct messages,",
|
|
585
|
+
"groups, presence, attachments. Registration is free.",
|
|
638
586
|
"",
|
|
639
|
-
"
|
|
587
|
+
"This wizard will either mint a new account via email OTP, or accept",
|
|
588
|
+
"an existing API key \u2014 your choice in the next prompt."
|
|
640
589
|
]
|
|
641
590
|
},
|
|
642
|
-
prepare: async ({ cfg, accountId, credentialValues }) => {
|
|
643
|
-
|
|
644
|
-
|
|
591
|
+
prepare: async ({ cfg, accountId, credentialValues, prompter }) => {
|
|
592
|
+
if (hasConfiguredKey(cfg, accountId) && typeof credentialValues.token === "string") {
|
|
593
|
+
const editChoice = await prompter.select({
|
|
594
|
+
message: "AgentChat is already configured. What would you like to do?",
|
|
595
|
+
options: [
|
|
596
|
+
{
|
|
597
|
+
value: "keep",
|
|
598
|
+
label: "Keep current config",
|
|
599
|
+
hint: "the credential step will still re-validate on the next run"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
value: "change-base",
|
|
603
|
+
label: "Change API base URL",
|
|
604
|
+
hint: "only for self-hosted AgentChat deployments"
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
value: "replace-key",
|
|
608
|
+
label: "Replace the API key",
|
|
609
|
+
hint: "paste a new key, or register a new agent"
|
|
610
|
+
}
|
|
611
|
+
],
|
|
612
|
+
initialValue: "keep"
|
|
613
|
+
});
|
|
614
|
+
if (editChoice === "keep") return;
|
|
615
|
+
if (editChoice === "change-base") {
|
|
616
|
+
return await runChangeApiBaseFlow({ cfg, accountId, prompter });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const choice = await prompter.select({
|
|
620
|
+
message: "How would you like to configure AgentChat?",
|
|
621
|
+
options: [
|
|
622
|
+
{
|
|
623
|
+
value: "register",
|
|
624
|
+
label: "Register a new agent (email OTP)",
|
|
625
|
+
hint: "no account yet \u2014 the wizard creates one"
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
value: "paste",
|
|
629
|
+
label: "I already have an API key",
|
|
630
|
+
hint: "paste ac_live_\u2026 on the next prompt"
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
initialValue: "register"
|
|
634
|
+
});
|
|
635
|
+
if (choice === "paste") {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const apiBase = readAgentchatConfigField(cfg, accountId, "apiBase");
|
|
639
|
+
try {
|
|
640
|
+
const result = await runRegisterFlow({ cfg, accountId, prompter, apiBase });
|
|
641
|
+
if (result === "abort") {
|
|
642
|
+
await prompter.note(
|
|
643
|
+
"Registration was not completed. You can still paste an existing API key at the next prompt, or cancel the wizard.",
|
|
644
|
+
"Falling back to credential entry"
|
|
645
|
+
);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (result === "user-chose-paste") {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
} catch (err) {
|
|
653
|
+
if (err instanceof setup.WizardCancelledError) throw err;
|
|
654
|
+
await prompter.note(
|
|
655
|
+
`${err instanceof Error ? err.message : String(err)}`,
|
|
656
|
+
"Registration flow failed"
|
|
657
|
+
);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
645
660
|
},
|
|
646
|
-
credentials: [
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
661
|
+
credentials: [
|
|
662
|
+
{
|
|
663
|
+
inputKey: "token",
|
|
664
|
+
providerHint: "agentchat",
|
|
665
|
+
credentialLabel: "API key",
|
|
666
|
+
preferredEnvVar: "AGENTCHAT_API_KEY",
|
|
667
|
+
envPrompt: "AGENTCHAT_API_KEY detected in env. Use it?",
|
|
668
|
+
keepPrompt: "AgentChat API key already configured. Keep it?",
|
|
669
|
+
inputPrompt: "Paste your AgentChat API key (ac_live_\u2026)",
|
|
670
|
+
helpTitle: "AgentChat API key",
|
|
671
|
+
helpLines: [
|
|
672
|
+
"Format: ac_live_<base64>, \u226520 chars. Validated against",
|
|
673
|
+
"GET /v1/agents/me during this wizard \u2014 bad keys fail fast instead",
|
|
674
|
+
"of flapping reconnects at runtime."
|
|
675
|
+
],
|
|
676
|
+
allowEnv: () => true,
|
|
677
|
+
shouldPrompt: ({ credentialValues, currentValue }) => {
|
|
678
|
+
if (credentialValues[JUST_REGISTERED_SENTINEL] === "1") return false;
|
|
679
|
+
if (!currentValue) return true;
|
|
680
|
+
return true;
|
|
681
|
+
},
|
|
682
|
+
inspect: ({ cfg, accountId }) => {
|
|
683
|
+
const apiKey = readAgentchatConfigField(cfg, accountId, "apiKey");
|
|
684
|
+
const configured = isApiKeyPresent(apiKey);
|
|
685
|
+
const envValue = process.env.AGENTCHAT_API_KEY?.trim();
|
|
686
|
+
return {
|
|
687
|
+
accountConfigured: configured,
|
|
688
|
+
hasConfiguredValue: configured,
|
|
689
|
+
resolvedValue: configured ? apiKey : void 0,
|
|
690
|
+
envValue: envValue && envValue.length >= MIN_API_KEY_LENGTH ? envValue : void 0
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
],
|
|
695
|
+
finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
|
|
696
|
+
const apiKey = typeof credentialValues.token === "string" && credentialValues.token.length >= MIN_API_KEY_LENGTH ? credentialValues.token : readAgentchatConfigField(cfg, accountId, "apiKey");
|
|
697
|
+
if (!apiKey || !isApiKeyPresent(apiKey)) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const apiBase = readAgentchatConfigField(cfg, accountId, "apiBase");
|
|
701
|
+
const spinner = prompter.progress("Validating API key against AgentChat\u2026");
|
|
702
|
+
try {
|
|
703
|
+
const result = await validateApiKey(apiKey, { apiBase });
|
|
704
|
+
if (result.ok) {
|
|
705
|
+
spinner.stop(`Authenticated as @${result.agent.handle}`);
|
|
706
|
+
const existingHandle = readAgentchatConfigField(cfg, accountId, "agentHandle");
|
|
707
|
+
if (!existingHandle && isValidHandleShape(result.agent.handle)) {
|
|
708
|
+
return {
|
|
709
|
+
cfg: applyAgentchatAccountPatch(cfg, accountId, {
|
|
710
|
+
agentHandle: result.agent.handle
|
|
711
|
+
})
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
spinner.stop(`API key did not pass the live check (${result.reason})`);
|
|
717
|
+
await prompter.note(
|
|
718
|
+
[
|
|
719
|
+
result.message,
|
|
720
|
+
"",
|
|
721
|
+
"The config was saved \u2014 you can re-run `openclaw channels add agentchat`",
|
|
722
|
+
"to replace the key, or edit ~/.openclaw/config.yaml directly."
|
|
723
|
+
].join("\n"),
|
|
724
|
+
"AgentChat validation warning"
|
|
725
|
+
);
|
|
726
|
+
} catch (err) {
|
|
727
|
+
spinner.stop("Could not reach AgentChat for validation");
|
|
728
|
+
await prompter.note(
|
|
729
|
+
[
|
|
730
|
+
err instanceof Error ? err.message : String(err),
|
|
731
|
+
"",
|
|
732
|
+
"The config was saved \u2014 the runtime will retry on startup."
|
|
733
|
+
].join("\n"),
|
|
734
|
+
"AgentChat API unreachable"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
return;
|
|
652
738
|
},
|
|
653
739
|
completionNote: {
|
|
654
740
|
title: "AgentChat is ready",
|
|
655
741
|
lines: [
|
|
656
|
-
"
|
|
657
|
-
"
|
|
658
|
-
"
|
|
742
|
+
"Next steps:",
|
|
743
|
+
" \u2022 Start OpenClaw \u2014 the AgentChat channel auto-connects via WebSocket.",
|
|
744
|
+
" \u2022 DM another agent: @<handle> <message>",
|
|
745
|
+
" \u2022 Docs: https://agentchat.me/docs"
|
|
659
746
|
]
|
|
660
747
|
},
|
|
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
|
-
}
|
|
748
|
+
disable: (cfg) => setup.setSetupChannelEnabled(cfg, AGENTCHAT_CHANNEL_ID, false)
|
|
671
749
|
};
|
|
750
|
+
var reconnectConfigSchema = zod.z.object({
|
|
751
|
+
initialBackoffMs: zod.z.number().int().min(100).max(1e4).default(1e3),
|
|
752
|
+
maxBackoffMs: zod.z.number().int().min(1e3).max(3e5).default(3e4),
|
|
753
|
+
jitterRatio: zod.z.number().min(0).max(1).default(0.2)
|
|
754
|
+
}).strict();
|
|
755
|
+
var pingConfigSchema = zod.z.object({
|
|
756
|
+
intervalMs: zod.z.number().int().min(5e3).max(12e4).default(3e4),
|
|
757
|
+
timeoutMs: zod.z.number().int().min(1e3).max(3e4).default(1e4)
|
|
758
|
+
}).strict();
|
|
759
|
+
var outboundConfigSchema = zod.z.object({
|
|
760
|
+
maxInFlight: zod.z.number().int().min(1).max(1e4).default(256),
|
|
761
|
+
sendTimeoutMs: zod.z.number().int().min(1e3).max(6e4).default(15e3)
|
|
762
|
+
}).strict();
|
|
763
|
+
var observabilityConfigSchema = zod.z.object({
|
|
764
|
+
logLevel: zod.z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
|
|
765
|
+
redactKeys: zod.z.array(zod.z.string()).default(["apiKey", "authorization", "cookie", "set-cookie"])
|
|
766
|
+
}).strict();
|
|
767
|
+
var agentHandleSchema = zod.z.string().regex(
|
|
768
|
+
/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,
|
|
769
|
+
"handle must be lowercase letters/digits/hyphens; start with a letter; no trailing or doubled hyphens"
|
|
770
|
+
).min(3, "handle must be at least 3 characters").max(30, "handle must be at most 30 characters");
|
|
771
|
+
var agentchatChannelConfigSchema = zod.z.object({
|
|
772
|
+
apiBase: zod.z.string().url().default("https://api.agentchat.me"),
|
|
773
|
+
apiKey: zod.z.string().min(20, "apiKey looks too short for an AgentChat API key"),
|
|
774
|
+
agentHandle: agentHandleSchema.optional(),
|
|
775
|
+
// `.prefault({})` — Zod 4 idiom: when the key is missing from input, parse
|
|
776
|
+
// `{}` through the inner schema so its own per-field defaults kick in.
|
|
777
|
+
// Using `.default({})` here fails in Zod 4 because the output type's fields
|
|
778
|
+
// are non-optional once the inner schema has defaults.
|
|
779
|
+
reconnect: reconnectConfigSchema.prefault({}),
|
|
780
|
+
ping: pingConfigSchema.prefault({}),
|
|
781
|
+
outbound: outboundConfigSchema.prefault({}),
|
|
782
|
+
observability: observabilityConfigSchema.prefault({})
|
|
783
|
+
}).strict();
|
|
784
|
+
function parseChannelConfig(input) {
|
|
785
|
+
return agentchatChannelConfigSchema.parse(input);
|
|
786
|
+
}
|
|
672
787
|
|
|
673
788
|
// 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
789
|
var uiHints = {
|
|
704
790
|
apiKey: {
|
|
705
791
|
label: "AgentChat API key",
|
|
706
792
|
placeholder: "ac_live_...",
|
|
707
793
|
sensitive: true,
|
|
708
|
-
help: "
|
|
794
|
+
help: "The setup wizard registers you via email OTP and mints a key \u2014 or paste an existing ac_live_\u2026 key."
|
|
709
795
|
},
|
|
710
796
|
apiBase: {
|
|
711
797
|
label: "API base URL",
|
|
@@ -716,7 +802,7 @@ var uiHints = {
|
|
|
716
802
|
agentHandle: {
|
|
717
803
|
label: "Agent handle",
|
|
718
804
|
placeholder: "my-agent",
|
|
719
|
-
help: "3\
|
|
805
|
+
help: "3\u201330 chars, lowercase letters/digits/hyphens; must start with a letter."
|
|
720
806
|
},
|
|
721
807
|
reconnect: { label: "Reconnect backoff", advanced: true },
|
|
722
808
|
ping: { label: "WebSocket ping cadence", advanced: true },
|
|
@@ -767,7 +853,7 @@ var agentchatPlugin = {
|
|
|
767
853
|
resolveAccount(cfg, accountId) {
|
|
768
854
|
const id = accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID;
|
|
769
855
|
const section = readChannelSection(cfg);
|
|
770
|
-
const raw =
|
|
856
|
+
const raw = readAccountRaw(section, id);
|
|
771
857
|
const { enabled, forParse } = splitEnabledFromRaw(raw);
|
|
772
858
|
let config = null;
|
|
773
859
|
let parseError = null;
|
|
@@ -832,8 +918,8 @@ var agentchatPlugin = {
|
|
|
832
918
|
if (typeof input.token !== "string" || input.token.trim().length === 0) {
|
|
833
919
|
return "apiKey is required \u2014 pass via --token or run the register flow";
|
|
834
920
|
}
|
|
835
|
-
if (input.token.length <
|
|
836
|
-
return `apiKey looks too short (got ${input.token.length} chars, expect \u2265${
|
|
921
|
+
if (input.token.length < MIN_API_KEY_LENGTH) {
|
|
922
|
+
return `apiKey looks too short (got ${input.token.length} chars, expect \u2265${MIN_API_KEY_LENGTH})`;
|
|
837
923
|
}
|
|
838
924
|
if (typeof input.url === "string" && input.url.length > 0) {
|
|
839
925
|
try {
|
|
@@ -848,26 +934,10 @@ var agentchatPlugin = {
|
|
|
848
934
|
return null;
|
|
849
935
|
},
|
|
850
936
|
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
937
|
const patch = {};
|
|
857
938
|
if (typeof input.token === "string" && input.token.length > 0) patch.apiKey = input.token;
|
|
858
939
|
if (typeof input.url === "string" && input.url.length > 0) patch.apiBase = input.url;
|
|
859
|
-
|
|
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 };
|
|
940
|
+
return applyAgentchatAccountPatch(cfg, accountId, patch);
|
|
871
941
|
},
|
|
872
942
|
/**
|
|
873
943
|
* Post-write probe: call `GET /v1/agents/me` with the key we just wrote
|