@clawling/clawchat-plugin-openclaw 2026.9.7-1 → 2026.9.8-2
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/README.md +1 -1
- package/dist/src/commands.js +21 -3
- package/dist/src/login.runtime.js +75 -19
- package/package.json +1 -1
- package/src/commands.ts +24 -3
- package/src/login.runtime.ts +88 -19
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Install and activate the ClawChat plugin from npm package @clawling/clawchat-plu
|
|
|
31
31
|
Pick one of these invite-code activation paths after the plugin is loaded
|
|
32
32
|
into OpenClaw:
|
|
33
33
|
|
|
34
|
-
- **Runtime slash command (recommended).** Send `/clawchat-activate
|
|
34
|
+
- **Runtime slash command (recommended).** Send `/clawchat-activate K7RM4TQP`
|
|
35
35
|
in the chat where OpenClaw is running. Not a shell command — running
|
|
36
36
|
`openclaw clawchat-activate` is expected to fail. This is the reliable
|
|
37
37
|
first-time activation path on OpenClaw 2026.5.5 and newer.
|
package/dist/src/commands.js
CHANGED
|
@@ -4,9 +4,27 @@ const ACTIVATION_FLAGS = ["--new-account", "--repair"];
|
|
|
4
4
|
function stripActivationFlags(raw) {
|
|
5
5
|
return ACTIVATION_FLAGS.reduce((acc, flag) => acc.split(flag).join(" "), raw);
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* A whole argument token that looks like a ClawChat connect code.
|
|
9
|
+
*
|
|
10
|
+
* member-backend mints **8** characters drawn from
|
|
11
|
+
* `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`, with 0/1/I/O excluded so humans can
|
|
12
|
+
* transcribe them. This deliberately accepts a band around that length rather
|
|
13
|
+
* than exactly 8: the length is the server's to choose, and pinning it here is
|
|
14
|
+
* what previously broke activation outright. A too-long or too-short token is left for the server to reject —
|
|
15
|
+
* the code is checked there anyway, so a permissive local filter costs nothing
|
|
16
|
+
* while a strict one silently swallows valid codes.
|
|
17
|
+
*
|
|
18
|
+
* Matching whole tokens (not a substring scan) is what keeps the command word
|
|
19
|
+
* itself out of the result when `ctx.args` is absent and we fall back to
|
|
20
|
+
* `ctx.commandBody`, which still carries `/clawchat-activate`.
|
|
21
|
+
*/
|
|
22
|
+
const INVITE_CODE_PATTERN = /^[A-Z0-9]{6,12}$/u;
|
|
7
23
|
function extractInviteCode(value) {
|
|
8
24
|
const raw = typeof value === "string" ? value.trim() : "";
|
|
9
|
-
return stripActivationFlags(raw)
|
|
25
|
+
return (stripActivationFlags(raw)
|
|
26
|
+
.split(/\s+/u)
|
|
27
|
+
.find((token) => INVITE_CODE_PATTERN.test(token)) ?? "");
|
|
10
28
|
}
|
|
11
29
|
/**
|
|
12
30
|
* Read the activation intent from the command args.
|
|
@@ -102,14 +120,14 @@ function formatOutputVisibilityResult(outputVisibility) {
|
|
|
102
120
|
export function registerOpenclawClawlingCommands(api) {
|
|
103
121
|
api.registerCommand({
|
|
104
122
|
name: "clawchat-activate",
|
|
105
|
-
description: "Activate ClawChat with an invite code, e.g. /clawchat-activate
|
|
123
|
+
description: "Activate ClawChat with an invite code, e.g. /clawchat-activate K7RM4TQP.",
|
|
106
124
|
acceptsArgs: true,
|
|
107
125
|
requireAuth: true,
|
|
108
126
|
async handler(ctx) {
|
|
109
127
|
const rawArgs = ctx.args ?? ctx.commandBody;
|
|
110
128
|
const code = extractInviteCode(rawArgs);
|
|
111
129
|
if (!code) {
|
|
112
|
-
return { text: "ClawChat invite code is required. Usage: /clawchat-activate
|
|
130
|
+
return { text: "ClawChat invite code is required. Usage: /clawchat-activate K7RM4TQP" };
|
|
113
131
|
}
|
|
114
132
|
const intent = extractActivationIntent(rawArgs);
|
|
115
133
|
try {
|
|
@@ -37,17 +37,17 @@ export class ExistingActivationError extends Error {
|
|
|
37
37
|
agentId;
|
|
38
38
|
constructor(userId, agentId = "") {
|
|
39
39
|
const who = agentId ? `agent ${agentId} (shadow user ${userId})` : `agent ${userId}`;
|
|
40
|
-
super(`this OpenClaw instance is already paired to ClawChat ${who}
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
40
|
+
super(`this OpenClaw instance is already paired to ClawChat ${who}, and there is ` +
|
|
41
|
+
"nobody here to ask which outcome you want. The invite code was NOT " +
|
|
42
|
+
"spent. Re-run stating the intent:\n" +
|
|
43
|
+
" - Pair as a BRAND-NEW agent (this instance stops using the identity " +
|
|
44
|
+
"above): /clawchat-activate <CODE> --new-account\n" +
|
|
45
|
+
" - RESTORE the identity above (re-pairs that same agent; if it was " +
|
|
46
|
+
"deleted this brings it back with its history): " +
|
|
47
|
+
"/clawchat-activate <CODE> --repair\n" +
|
|
48
|
+
" - To run a SECOND agent alongside this one instead, give it its own " +
|
|
49
|
+
"OpenClaw home:\n" +
|
|
50
|
+
" OPENCLAW_HOME=<path> openclaw channels login --channel clawchat-plugin-openclaw");
|
|
51
51
|
this.name = "ExistingActivationError";
|
|
52
52
|
this.userId = userId;
|
|
53
53
|
this.agentId = agentId;
|
|
@@ -76,6 +76,43 @@ async function promptInviteCodeFromStdin(runtime) {
|
|
|
76
76
|
rl?.close();
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Ask the operator which outcome they want, on the same stdin channel the
|
|
81
|
+
* invite-code prompt already uses.
|
|
82
|
+
*
|
|
83
|
+
* Returns `null` when nobody can answer. Both outcomes are irreversible in
|
|
84
|
+
* opposite directions — "new" strands the agent this instance currently holds,
|
|
85
|
+
* "restore" can revive an agent the owner just deleted — so an unattended run
|
|
86
|
+
* must not pick one. `null` refuses instead, leaving the code redeemable.
|
|
87
|
+
*
|
|
88
|
+
* A non-TTY stdin is the unattended case: a piped/closed stdin makes
|
|
89
|
+
* `rl.question` resolve instantly with an empty line, which would otherwise
|
|
90
|
+
* read as a silent answer. An unrecognized reply is also `null`: this is the
|
|
91
|
+
* one prompt where guessing at the operator's meaning is worse than stopping.
|
|
92
|
+
*/
|
|
93
|
+
async function promptActivationIntentFromStdin(runtime, who) {
|
|
94
|
+
if (!process.stdin.isTTY)
|
|
95
|
+
return null;
|
|
96
|
+
runtime.log(`This OpenClaw instance already holds ClawChat ${who}.\n` +
|
|
97
|
+
" [1] Pair as a BRAND-NEW agent — this instance stops using that identity.\n" +
|
|
98
|
+
" [2] RESTORE that identity — re-pairs the same agent; if it was deleted, " +
|
|
99
|
+
"this brings it back with its history.\n" +
|
|
100
|
+
"Enter 1 or 2 (press Enter to submit):");
|
|
101
|
+
let rl;
|
|
102
|
+
try {
|
|
103
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
104
|
+
const answer = (await rl.question("> ")).trim();
|
|
105
|
+
if (answer === "1")
|
|
106
|
+
return "new-account";
|
|
107
|
+
if (answer === "2")
|
|
108
|
+
return "repair";
|
|
109
|
+
runtime.log(`Unrecognized choice ${JSON.stringify(answer)} — not activating.`);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
rl?.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
79
116
|
function buildLoginConfig(cfg, result) {
|
|
80
117
|
const channels = (cfg.channels ?? {});
|
|
81
118
|
const existing = (channels[CHANNEL_ID] ?? {});
|
|
@@ -165,13 +202,32 @@ export async function runOpenclawClawlingLogin(params) {
|
|
|
165
202
|
// `DEFAULT_BASE_URL` / `DEFAULT_WEBSOCKET_URL` when the operator has not
|
|
166
203
|
// overridden them, so login works without a prior `openclaw channels setup --channel clawchat-plugin-openclaw`.
|
|
167
204
|
const account = resolveOpenclawClawlingAccount(cfg);
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
205
|
+
// Fork before the invite code is even read, so neither branch spends the code
|
|
206
|
+
// before the outcome is settled. "Live" is decided by whether a usable token
|
|
207
|
+
// remains: auto-logout (§C) blanks the tokens but preserves the identity, so a
|
|
208
|
+
// logged-out instance still re-pairs with no flag at all, which is the flow
|
|
209
|
+
// the replay exists for.
|
|
210
|
+
//
|
|
211
|
+
// Holding an identity is not itself an error — it just means the run is
|
|
212
|
+
// ambiguous, because redeeming a code here can either mint a new agent or
|
|
213
|
+
// re-pair the incumbent one. Ask which, and only refuse when nobody answers.
|
|
214
|
+
// An explicit `newAccount` / `repair` from the caller has already settled it,
|
|
215
|
+
// so no prompt.
|
|
216
|
+
let newAccount = params.newAccount;
|
|
217
|
+
if (account.userId.trim() && account.token.trim() && !newAccount && !params.repair) {
|
|
218
|
+
const identity = account.agentId.trim()
|
|
219
|
+
? `agent ${account.agentId.trim()} (shadow user ${account.userId.trim()})`
|
|
220
|
+
: `agent ${account.userId.trim()}`;
|
|
221
|
+
const intent = await (params.readActivationIntent ??
|
|
222
|
+
(() => promptActivationIntentFromStdin(runtime, identity)))();
|
|
223
|
+
// Only "new-account" changes what gets sent. Restoring needs no flag:
|
|
224
|
+
// replaying the stored `user_id` IS the re-pair, and that is already the
|
|
225
|
+
// default below — so choosing it simply means "don't refuse".
|
|
226
|
+
if (intent === "new-account")
|
|
227
|
+
newAccount = true;
|
|
228
|
+
else if (intent === null) {
|
|
229
|
+
throw new ExistingActivationError(account.userId.trim(), account.agentId.trim());
|
|
230
|
+
}
|
|
175
231
|
}
|
|
176
232
|
const inviteCode = (await (params.readInviteCode ?? (() => promptInviteCodeFromStdin(runtime)))()).trim();
|
|
177
233
|
if (!inviteCode) {
|
|
@@ -189,7 +245,7 @@ export async function runOpenclawClawlingLogin(params) {
|
|
|
189
245
|
let result;
|
|
190
246
|
// A brand-new identity is precisely "do not replay" — the replay is the only
|
|
191
247
|
// thing that would bind this code to the incumbent agent.
|
|
192
|
-
const existingUserId =
|
|
248
|
+
const existingUserId = newAccount ? "" : account.userId.trim();
|
|
193
249
|
try {
|
|
194
250
|
result = await apiClient.agentsConnect({
|
|
195
251
|
code: inviteCode,
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -10,9 +10,30 @@ function stripActivationFlags(raw: string): string {
|
|
|
10
10
|
return ACTIVATION_FLAGS.reduce((acc, flag) => acc.split(flag).join(" "), raw);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A whole argument token that looks like a ClawChat connect code.
|
|
15
|
+
*
|
|
16
|
+
* member-backend mints **8** characters drawn from
|
|
17
|
+
* `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`, with 0/1/I/O excluded so humans can
|
|
18
|
+
* transcribe them. This deliberately accepts a band around that length rather
|
|
19
|
+
* than exactly 8: the length is the server's to choose, and pinning it here is
|
|
20
|
+
* what previously broke activation outright. A too-long or too-short token is left for the server to reject —
|
|
21
|
+
* the code is checked there anyway, so a permissive local filter costs nothing
|
|
22
|
+
* while a strict one silently swallows valid codes.
|
|
23
|
+
*
|
|
24
|
+
* Matching whole tokens (not a substring scan) is what keeps the command word
|
|
25
|
+
* itself out of the result when `ctx.args` is absent and we fall back to
|
|
26
|
+
* `ctx.commandBody`, which still carries `/clawchat-activate`.
|
|
27
|
+
*/
|
|
28
|
+
const INVITE_CODE_PATTERN = /^[A-Z0-9]{6,12}$/u;
|
|
29
|
+
|
|
13
30
|
function extractInviteCode(value: unknown): string {
|
|
14
31
|
const raw = typeof value === "string" ? value.trim() : "";
|
|
15
|
-
return
|
|
32
|
+
return (
|
|
33
|
+
stripActivationFlags(raw)
|
|
34
|
+
.split(/\s+/u)
|
|
35
|
+
.find((token) => INVITE_CODE_PATTERN.test(token)) ?? ""
|
|
36
|
+
);
|
|
16
37
|
}
|
|
17
38
|
|
|
18
39
|
/**
|
|
@@ -118,14 +139,14 @@ function formatOutputVisibilityResult(outputVisibility: OutputVisibility): strin
|
|
|
118
139
|
export function registerOpenclawClawlingCommands(api: Pick<OpenClawPluginApi, "registerCommand" | "logger" | "runtime">): void {
|
|
119
140
|
api.registerCommand({
|
|
120
141
|
name: "clawchat-activate",
|
|
121
|
-
description: "Activate ClawChat with an invite code, e.g. /clawchat-activate
|
|
142
|
+
description: "Activate ClawChat with an invite code, e.g. /clawchat-activate K7RM4TQP.",
|
|
122
143
|
acceptsArgs: true,
|
|
123
144
|
requireAuth: true,
|
|
124
145
|
async handler(ctx) {
|
|
125
146
|
const rawArgs = ctx.args ?? ctx.commandBody;
|
|
126
147
|
const code = extractInviteCode(rawArgs);
|
|
127
148
|
if (!code) {
|
|
128
|
-
return { text: "ClawChat invite code is required. Usage: /clawchat-activate
|
|
149
|
+
return { text: "ClawChat invite code is required. Usage: /clawchat-activate K7RM4TQP" };
|
|
129
150
|
}
|
|
130
151
|
const intent = extractActivationIntent(rawArgs);
|
|
131
152
|
try {
|
package/src/login.runtime.ts
CHANGED
|
@@ -63,8 +63,20 @@ export interface LoginParams {
|
|
|
63
63
|
newAccount?: boolean;
|
|
64
64
|
/** Re-pair the agent this instance already holds (keeps its identity). */
|
|
65
65
|
repair?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Ask the operator which outcome they want when this instance already holds
|
|
68
|
+
* an identity and the caller stated no intent.
|
|
69
|
+
*
|
|
70
|
+
* Resolving `null` means "nobody could be asked" and activation is refused
|
|
71
|
+
* with `ExistingActivationError`. Defaults to
|
|
72
|
+
* `promptActivationIntentFromStdin`; overridden by tests.
|
|
73
|
+
*/
|
|
74
|
+
readActivationIntent?: () => Promise<ActivationIntent | null>;
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
/** The two outcomes available once an identity is already stored. */
|
|
78
|
+
export type ActivationIntent = "new-account" | "repair";
|
|
79
|
+
|
|
68
80
|
/**
|
|
69
81
|
* Thrown when redeeming an invite code would silently replace a live activation.
|
|
70
82
|
*
|
|
@@ -83,17 +95,17 @@ export class ExistingActivationError extends Error {
|
|
|
83
95
|
constructor(userId: string, agentId = "") {
|
|
84
96
|
const who = agentId ? `agent ${agentId} (shadow user ${userId})` : `agent ${userId}`;
|
|
85
97
|
super(
|
|
86
|
-
`this OpenClaw instance is already paired to ClawChat ${who}
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
98
|
+
`this OpenClaw instance is already paired to ClawChat ${who}, and there is ` +
|
|
99
|
+
"nobody here to ask which outcome you want. The invite code was NOT " +
|
|
100
|
+
"spent. Re-run stating the intent:\n" +
|
|
101
|
+
" - Pair as a BRAND-NEW agent (this instance stops using the identity " +
|
|
102
|
+
"above): /clawchat-activate <CODE> --new-account\n" +
|
|
103
|
+
" - RESTORE the identity above (re-pairs that same agent; if it was " +
|
|
104
|
+
"deleted this brings it back with its history): " +
|
|
105
|
+
"/clawchat-activate <CODE> --repair\n" +
|
|
106
|
+
" - To run a SECOND agent alongside this one instead, give it its own " +
|
|
107
|
+
"OpenClaw home:\n" +
|
|
108
|
+
" OPENCLAW_HOME=<path> openclaw channels login --channel clawchat-plugin-openclaw",
|
|
97
109
|
);
|
|
98
110
|
this.name = "ExistingActivationError";
|
|
99
111
|
this.userId = userId;
|
|
@@ -126,6 +138,45 @@ async function promptInviteCodeFromStdin(runtime: {
|
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Ask the operator which outcome they want, on the same stdin channel the
|
|
143
|
+
* invite-code prompt already uses.
|
|
144
|
+
*
|
|
145
|
+
* Returns `null` when nobody can answer. Both outcomes are irreversible in
|
|
146
|
+
* opposite directions — "new" strands the agent this instance currently holds,
|
|
147
|
+
* "restore" can revive an agent the owner just deleted — so an unattended run
|
|
148
|
+
* must not pick one. `null` refuses instead, leaving the code redeemable.
|
|
149
|
+
*
|
|
150
|
+
* A non-TTY stdin is the unattended case: a piped/closed stdin makes
|
|
151
|
+
* `rl.question` resolve instantly with an empty line, which would otherwise
|
|
152
|
+
* read as a silent answer. An unrecognized reply is also `null`: this is the
|
|
153
|
+
* one prompt where guessing at the operator's meaning is worse than stopping.
|
|
154
|
+
*/
|
|
155
|
+
async function promptActivationIntentFromStdin(
|
|
156
|
+
runtime: { log: (message: string) => void },
|
|
157
|
+
who: string,
|
|
158
|
+
): Promise<ActivationIntent | null> {
|
|
159
|
+
if (!process.stdin.isTTY) return null;
|
|
160
|
+
runtime.log(
|
|
161
|
+
`This OpenClaw instance already holds ClawChat ${who}.\n` +
|
|
162
|
+
" [1] Pair as a BRAND-NEW agent — this instance stops using that identity.\n" +
|
|
163
|
+
" [2] RESTORE that identity — re-pairs the same agent; if it was deleted, " +
|
|
164
|
+
"this brings it back with its history.\n" +
|
|
165
|
+
"Enter 1 or 2 (press Enter to submit):",
|
|
166
|
+
);
|
|
167
|
+
let rl: ReadlineInterface | undefined;
|
|
168
|
+
try {
|
|
169
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
170
|
+
const answer = (await rl.question("> ")).trim();
|
|
171
|
+
if (answer === "1") return "new-account";
|
|
172
|
+
if (answer === "2") return "repair";
|
|
173
|
+
runtime.log(`Unrecognized choice ${JSON.stringify(answer)} — not activating.`);
|
|
174
|
+
return null;
|
|
175
|
+
} finally {
|
|
176
|
+
rl?.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
129
180
|
function buildLoginConfig(cfg: OpenClawConfig, result: AgentConnectResult): OpenClawConfig {
|
|
130
181
|
const channels = (cfg.channels ?? {}) as Record<string, unknown>;
|
|
131
182
|
const existing = (channels[CHANNEL_ID] ?? {}) as Record<string, unknown>;
|
|
@@ -235,13 +286,31 @@ export async function runOpenclawClawlingLogin(params: LoginParams): Promise<voi
|
|
|
235
286
|
// overridden them, so login works without a prior `openclaw channels setup --channel clawchat-plugin-openclaw`.
|
|
236
287
|
const account = resolveOpenclawClawlingAccount(cfg);
|
|
237
288
|
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
289
|
+
// Fork before the invite code is even read, so neither branch spends the code
|
|
290
|
+
// before the outcome is settled. "Live" is decided by whether a usable token
|
|
291
|
+
// remains: auto-logout (§C) blanks the tokens but preserves the identity, so a
|
|
292
|
+
// logged-out instance still re-pairs with no flag at all, which is the flow
|
|
293
|
+
// the replay exists for.
|
|
294
|
+
//
|
|
295
|
+
// Holding an identity is not itself an error — it just means the run is
|
|
296
|
+
// ambiguous, because redeeming a code here can either mint a new agent or
|
|
297
|
+
// re-pair the incumbent one. Ask which, and only refuse when nobody answers.
|
|
298
|
+
// An explicit `newAccount` / `repair` from the caller has already settled it,
|
|
299
|
+
// so no prompt.
|
|
300
|
+
let newAccount = params.newAccount;
|
|
301
|
+
if (account.userId.trim() && account.token.trim() && !newAccount && !params.repair) {
|
|
302
|
+
const identity = account.agentId.trim()
|
|
303
|
+
? `agent ${account.agentId.trim()} (shadow user ${account.userId.trim()})`
|
|
304
|
+
: `agent ${account.userId.trim()}`;
|
|
305
|
+
const intent = await (params.readActivationIntent ??
|
|
306
|
+
(() => promptActivationIntentFromStdin(runtime, identity)))();
|
|
307
|
+
// Only "new-account" changes what gets sent. Restoring needs no flag:
|
|
308
|
+
// replaying the stored `user_id` IS the re-pair, and that is already the
|
|
309
|
+
// default below — so choosing it simply means "don't refuse".
|
|
310
|
+
if (intent === "new-account") newAccount = true;
|
|
311
|
+
else if (intent === null) {
|
|
312
|
+
throw new ExistingActivationError(account.userId.trim(), account.agentId.trim());
|
|
313
|
+
}
|
|
245
314
|
}
|
|
246
315
|
|
|
247
316
|
const inviteCode = (
|
|
@@ -264,7 +333,7 @@ export async function runOpenclawClawlingLogin(params: LoginParams): Promise<voi
|
|
|
264
333
|
let result;
|
|
265
334
|
// A brand-new identity is precisely "do not replay" — the replay is the only
|
|
266
335
|
// thing that would bind this code to the incumbent agent.
|
|
267
|
-
const existingUserId =
|
|
336
|
+
const existingUserId = newAccount ? "" : account.userId.trim();
|
|
268
337
|
try {
|
|
269
338
|
result = await apiClient.agentsConnect({
|
|
270
339
|
code: inviteCode,
|