@newbase-clawchat/openclaw-clawchat 2026.4.29 → 2026.4.30
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 +33 -10
- package/dist/index.js +27 -0
- package/dist/src/api-client.js +156 -0
- package/dist/src/api-types.js +17 -0
- package/dist/src/buffered-stream.js +177 -0
- package/dist/src/channel.js +191 -0
- package/dist/src/client.js +176 -0
- package/dist/src/commands.js +35 -0
- package/dist/src/config.js +214 -0
- package/dist/src/inbound.js +133 -0
- package/dist/src/login.runtime.js +130 -0
- package/dist/src/media-runtime.js +85 -0
- package/dist/src/message-mapper.js +82 -0
- package/dist/src/outbound.js +181 -0
- package/dist/src/protocol.js +38 -0
- package/dist/src/reply-dispatcher.js +440 -0
- package/dist/src/runtime.js +288 -0
- package/dist/src/streaming.js +65 -0
- package/dist/src/tools-schema.js +38 -0
- package/dist/src/tools.js +287 -0
- package/openclaw.plugin.json +12 -0
- package/package.json +25 -5
- package/skills/clawchat-activate/SKILL.md +17 -8
- package/src/buffered-stream.test.ts +10 -0
- package/src/buffered-stream.ts +6 -6
- package/src/channel.outbound.test.ts +3 -3
- package/src/channel.ts +11 -3
- package/src/client.test.ts +8 -1
- package/src/client.ts +11 -10
- package/src/commands.test.ts +6 -0
- package/src/commands.ts +5 -1
- package/src/config.test.ts +3 -0
- package/src/config.ts +7 -0
- package/src/inbound.test.ts +4 -1
- package/src/inbound.ts +11 -10
- package/src/login.runtime.test.ts +36 -0
- package/src/login.runtime.ts +54 -26
- package/src/manifest.test.ts +98 -22
- package/src/outbound.test.ts +6 -5
- package/src/outbound.ts +8 -7
- package/src/reply-dispatcher.test.ts +418 -3
- package/src/reply-dispatcher.ts +137 -12
- package/src/runtime.ts +1 -0
- package/src/streaming.test.ts +12 -9
- package/src/streaming.ts +6 -6
- package/src/tools.test.ts +81 -18
- package/src/tools.ts +63 -72
package/src/runtime.ts
CHANGED
|
@@ -262,6 +262,7 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
262
262
|
...(replyCtx ? { replyCtx } : {}),
|
|
263
263
|
inboundMessageId: turn.messageId,
|
|
264
264
|
inboundForFinalReply: {
|
|
265
|
+
chatId: turn.peer.id,
|
|
265
266
|
senderId: turn.senderId,
|
|
266
267
|
senderNickName: turn.senderNickName || turn.senderId,
|
|
267
268
|
bodyText: turn.rawBody,
|
package/src/streaming.test.ts
CHANGED
|
@@ -37,12 +37,12 @@ describe("openclaw-clawchat streaming", () => {
|
|
|
37
37
|
"message.done",
|
|
38
38
|
]);
|
|
39
39
|
expect((client.typing as ReturnType<typeof vi.fn>).mock.calls).toEqual([
|
|
40
|
-
["u1", true
|
|
41
|
-
["u1", false
|
|
40
|
+
["u1", true],
|
|
41
|
+
["u1", false],
|
|
42
42
|
]);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
it("message.created payload is minimal (just message_id); adds carry monotonic sequences", async () => {
|
|
45
|
+
it("message.created payload is minimal (just message_id); adds carry zero-based monotonic sequences", async () => {
|
|
46
46
|
const { client, sent } = mockClient();
|
|
47
47
|
await sendStreamingText({
|
|
48
48
|
client,
|
|
@@ -54,10 +54,10 @@ describe("openclaw-clawchat streaming", () => {
|
|
|
54
54
|
|
|
55
55
|
// created payload is { message_id } only — no embedded message / streaming.
|
|
56
56
|
expect(sent[0]!.payload).toEqual({ message_id: "m1" });
|
|
57
|
-
expect(sent[1]!.payload.sequence).toBe(
|
|
58
|
-
expect(sent[2]!.payload.sequence).toBe(
|
|
59
|
-
expect(sent[3]!.payload.sequence).toBe(
|
|
60
|
-
expect((sent[4]!.payload.streaming as { sequence: number }).sequence).toBe(
|
|
57
|
+
expect(sent[1]!.payload.sequence).toBe(0);
|
|
58
|
+
expect(sent[2]!.payload.sequence).toBe(1);
|
|
59
|
+
expect(sent[3]!.payload.sequence).toBe(2);
|
|
60
|
+
expect((sent[4]!.payload.streaming as { sequence: number }).sequence).toBe(2);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it("each message.add carries fragments: [{ text: cumulative, delta: new }]", async () => {
|
|
@@ -107,10 +107,13 @@ describe("openclaw-clawchat streaming", () => {
|
|
|
107
107
|
reason: "boom",
|
|
108
108
|
});
|
|
109
109
|
expect(sent[0]!.event).toBe("message.failed");
|
|
110
|
-
expect(sent[0]!.payload.sequence).toBe(
|
|
110
|
+
expect(sent[0]!.payload.sequence).toBe(2);
|
|
111
111
|
expect(sent[0]!.payload.reason).toBe("boom");
|
|
112
|
+
expect(sent[0]!.payload).toHaveProperty("completed_at");
|
|
113
|
+
expect(sent[0]!.payload).not.toHaveProperty("failed_at");
|
|
114
|
+
expect(sent[0]!.payload.fragments).toEqual([{ kind: "text", text: "boom" }]);
|
|
112
115
|
expect((client.typing as ReturnType<typeof vi.fn>).mock.calls).toEqual([
|
|
113
|
-
["u1", false
|
|
116
|
+
["u1", false],
|
|
114
117
|
]);
|
|
115
118
|
});
|
|
116
119
|
});
|
package/src/streaming.ts
CHANGED
|
@@ -45,13 +45,13 @@ export async function sendStreamingText(params: StreamingSendParams): Promise<vo
|
|
|
45
45
|
const routing = resolveRouting(params);
|
|
46
46
|
const emitTyping = params.emitTyping !== false;
|
|
47
47
|
if (emitTyping) {
|
|
48
|
-
params.client.typing(routing.chatId, true
|
|
48
|
+
params.client.typing(routing.chatId, true);
|
|
49
49
|
}
|
|
50
50
|
emitStreamCreated(params.client, {
|
|
51
51
|
messageId: params.messageId,
|
|
52
52
|
routing,
|
|
53
53
|
});
|
|
54
|
-
let sequence =
|
|
54
|
+
let sequence = -1;
|
|
55
55
|
let fullText = "";
|
|
56
56
|
for (const chunk of params.chunks) {
|
|
57
57
|
sequence += 1;
|
|
@@ -67,11 +67,11 @@ export async function sendStreamingText(params: StreamingSendParams): Promise<vo
|
|
|
67
67
|
emitStreamDone(params.client, {
|
|
68
68
|
messageId: params.messageId,
|
|
69
69
|
routing,
|
|
70
|
-
finalSequence: sequence,
|
|
70
|
+
finalSequence: Math.max(sequence, 0),
|
|
71
71
|
finalText: fullText,
|
|
72
72
|
});
|
|
73
73
|
if (emitTyping) {
|
|
74
|
-
params.client.typing(routing.chatId, false
|
|
74
|
+
params.client.typing(routing.chatId, false);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -90,10 +90,10 @@ export async function sendStreamingFailure(params: StreamingFailureParams): Prom
|
|
|
90
90
|
emitStreamFailed(params.client, {
|
|
91
91
|
messageId: params.messageId,
|
|
92
92
|
routing,
|
|
93
|
-
sequence: params.currentSequence
|
|
93
|
+
sequence: params.currentSequence,
|
|
94
94
|
reason: params.reason,
|
|
95
95
|
});
|
|
96
96
|
if (params.emitTyping !== false) {
|
|
97
|
-
params.client.typing(routing.chatId, false
|
|
97
|
+
params.client.typing(routing.chatId, false);
|
|
98
98
|
}
|
|
99
99
|
}
|
package/src/tools.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
3
|
import { registerOpenclawClawlingTools } from "./tools.ts";
|
|
3
4
|
|
|
4
5
|
const loginRuntime = vi.hoisted(() => ({
|
|
@@ -12,6 +13,16 @@ interface RegisteredTool {
|
|
|
12
13
|
execute: (callId: string, params: unknown) => Promise<unknown>;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
const ALWAYS_VISIBLE_TOOL_NAMES = [
|
|
17
|
+
"clawchat_activate",
|
|
18
|
+
"clawchat_get_account_profile",
|
|
19
|
+
"clawchat_get_user_profile",
|
|
20
|
+
"clawchat_list_account_friends",
|
|
21
|
+
"clawchat_update_account_profile",
|
|
22
|
+
"clawchat_upload_avatar_image",
|
|
23
|
+
"clawchat_upload_media_file",
|
|
24
|
+
];
|
|
25
|
+
|
|
15
26
|
function buildApi(opts: {
|
|
16
27
|
configChannel?: Record<string, unknown> | null;
|
|
17
28
|
configTools?: Record<string, unknown>;
|
|
@@ -32,6 +43,11 @@ function buildApi(opts: {
|
|
|
32
43
|
warn: vi.fn(),
|
|
33
44
|
error: vi.fn(),
|
|
34
45
|
},
|
|
46
|
+
runtime: {
|
|
47
|
+
config: {
|
|
48
|
+
mutateConfigFile: vi.fn(),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
35
51
|
registerTool: (tool: RegisteredTool, _options?: { name: string }) => {
|
|
36
52
|
registered.push(tool);
|
|
37
53
|
},
|
|
@@ -49,12 +65,22 @@ function configuredChannel(extra: Record<string, unknown> = {}) {
|
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
describe("registerOpenclawClawlingTools", () => {
|
|
52
|
-
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("uses OpenClaw SDK tool result types instead of direct pi-agent-core imports", () => {
|
|
73
|
+
const source = fs.readFileSync(new URL("./tools.ts", import.meta.url), "utf8");
|
|
74
|
+
expect(source).not.toMatch(/@mariozechner\/pi-agent-core/);
|
|
75
|
+
expect(source).toMatch(/openclaw\/plugin-sdk\/agent-harness-runtime/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("registers all ClawChat tools even when account.configured is false", () => {
|
|
53
79
|
const { api, registered } = buildApi({
|
|
54
80
|
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
55
81
|
});
|
|
56
82
|
registerOpenclawClawlingTools(api);
|
|
57
|
-
expect(registered.map((t) => t.name)).toEqual(
|
|
83
|
+
expect(registered.map((t) => t.name).sort()).toEqual(ALWAYS_VISIBLE_TOOL_NAMES);
|
|
58
84
|
});
|
|
59
85
|
|
|
60
86
|
it("does not mutate tool policy during registration before account activation", () => {
|
|
@@ -65,22 +91,51 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
65
91
|
|
|
66
92
|
registerOpenclawClawlingTools(api);
|
|
67
93
|
|
|
68
|
-
expect(registered.map((t) => t.name)).toEqual(
|
|
94
|
+
expect(registered.map((t) => t.name).sort()).toEqual(ALWAYS_VISIBLE_TOOL_NAMES);
|
|
69
95
|
expect(api.config?.tools).toEqual({
|
|
70
96
|
profile: "coding",
|
|
71
97
|
allow: [],
|
|
72
98
|
});
|
|
73
99
|
});
|
|
74
100
|
|
|
75
|
-
it("
|
|
101
|
+
it("registers clawchat_activate for invite-code onboarding", async () => {
|
|
102
|
+
const { api, registered } = buildApi({
|
|
103
|
+
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
registerOpenclawClawlingTools(api);
|
|
107
|
+
|
|
108
|
+
const tool = registered.find((t) => t.name === "clawchat_activate");
|
|
109
|
+
expect(tool).toBeDefined();
|
|
110
|
+
loginRuntime.runOpenclawClawlingLogin.mockResolvedValueOnce(undefined);
|
|
111
|
+
|
|
112
|
+
const result = await tool!.execute("call-1", { code: "A1B2C3" });
|
|
113
|
+
|
|
114
|
+
expect(loginRuntime.runOpenclawClawlingLogin).toHaveBeenCalledTimes(1);
|
|
115
|
+
const params = loginRuntime.runOpenclawClawlingLogin.mock.calls[0]?.[0];
|
|
116
|
+
expect(params.cfg).toBe(api.config);
|
|
117
|
+
expect(params.mutateConfigFile).toBe(api.runtime.config.mutateConfigFile);
|
|
118
|
+
await expect(params.readInviteCode()).resolves.toBe("A1B2C3");
|
|
119
|
+
const text = (result as { content: { text: string }[] }).content[0]!.text;
|
|
120
|
+
const parsed = JSON.parse(text) as { ok?: boolean; message?: string };
|
|
121
|
+
expect(parsed.ok).toBe(true);
|
|
122
|
+
expect(parsed.message).toMatch(/activated successfully/i);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("clawchat_activate rejects empty invite codes", async () => {
|
|
76
126
|
const { api, registered } = buildApi({
|
|
77
127
|
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
78
128
|
});
|
|
79
129
|
|
|
80
130
|
registerOpenclawClawlingTools(api);
|
|
131
|
+
const tool = registered.find((t) => t.name === "clawchat_activate")!;
|
|
132
|
+
const result = await tool.execute("call-1", { code: " " });
|
|
81
133
|
|
|
82
|
-
expect(registered.some((t) => t.name === "clawchat_activate")).toBe(false);
|
|
83
134
|
expect(loginRuntime.runOpenclawClawlingLogin).not.toHaveBeenCalled();
|
|
135
|
+
const text = (result as { content: { text: string }[] }).content[0]!.text;
|
|
136
|
+
const parsed = JSON.parse(text) as { error?: string; message?: string };
|
|
137
|
+
expect(parsed.error).toBe("validation");
|
|
138
|
+
expect(parsed.message).toMatch(/code is required/i);
|
|
84
139
|
});
|
|
85
140
|
|
|
86
141
|
it("skips registration when api.config is undefined", () => {
|
|
@@ -89,20 +144,13 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
89
144
|
expect(registered).toHaveLength(0);
|
|
90
145
|
});
|
|
91
146
|
|
|
92
|
-
it("registers all
|
|
147
|
+
it("registers all seven ClawChat tools when configured (regardless of baseUrl)", () => {
|
|
93
148
|
const { api, registered } = buildApi({
|
|
94
149
|
configChannel: configuredChannel(/* no baseUrl */),
|
|
95
150
|
});
|
|
96
151
|
registerOpenclawClawlingTools(api);
|
|
97
152
|
const names = registered.map((t) => t.name).sort();
|
|
98
|
-
expect(names).toEqual(
|
|
99
|
-
"clawchat_get_account_profile",
|
|
100
|
-
"clawchat_get_user_profile",
|
|
101
|
-
"clawchat_list_account_friends",
|
|
102
|
-
"clawchat_update_account_profile",
|
|
103
|
-
"clawchat_upload_avatar_image",
|
|
104
|
-
"clawchat_upload_media_file",
|
|
105
|
-
]);
|
|
153
|
+
expect(names).toEqual(ALWAYS_VISIBLE_TOOL_NAMES);
|
|
106
154
|
});
|
|
107
155
|
|
|
108
156
|
it("logs configured tool registration at debug level only", () => {
|
|
@@ -118,16 +166,31 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
118
166
|
|
|
119
167
|
expect(logger.info).not.toHaveBeenCalled();
|
|
120
168
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
121
|
-
"openclaw-clawchat: registered
|
|
169
|
+
"openclaw-clawchat: registered 7 clawchat_* tools (activate, get_account_profile, get_user_profile, list_account_friends, update_account_profile, upload_avatar_image, upload_media_file)",
|
|
122
170
|
);
|
|
123
171
|
});
|
|
124
172
|
|
|
125
|
-
it("
|
|
173
|
+
it("registers clawchat_activate when configured", () => {
|
|
126
174
|
const { api, registered } = buildApi({
|
|
127
175
|
configChannel: configuredChannel(),
|
|
128
176
|
});
|
|
129
177
|
registerOpenclawClawlingTools(api);
|
|
130
|
-
expect(registered.some((t) => t.name === "clawchat_activate")).toBe(
|
|
178
|
+
expect(registered.some((t) => t.name === "clawchat_activate")).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("account tools return a config error before activation instead of disappearing", async () => {
|
|
182
|
+
const { api, registered } = buildApi({
|
|
183
|
+
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
registerOpenclawClawlingTools(api);
|
|
187
|
+
const tool = registered.find((t) => t.name === "clawchat_get_account_profile")!;
|
|
188
|
+
const result = await tool.execute("call-1", {});
|
|
189
|
+
|
|
190
|
+
const text = (result as { content: { text: string }[] }).content[0]!.text;
|
|
191
|
+
const parsed = JSON.parse(text) as { error?: string; message?: string };
|
|
192
|
+
expect(parsed.error).toBe("config");
|
|
193
|
+
expect(parsed.message).toMatch(/token is required/i);
|
|
131
194
|
});
|
|
132
195
|
|
|
133
196
|
it("clawchat_update_account_profile description names account profile triggers (EN + ZH)", () => {
|
package/src/tools.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type { OpenClawAgentToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
4
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/
|
|
4
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
5
|
+
import type { OpenclawClawchatMutateConfigFile } from "./login.runtime.ts";
|
|
5
6
|
import { createOpenclawClawlingApiClient } from "./api-client.ts";
|
|
6
7
|
import { ClawlingApiError, type Profile } from "./api-types.ts";
|
|
7
8
|
import { resolveOpenclawClawlingAccount } from "./config.ts";
|
|
8
9
|
import {
|
|
9
|
-
|
|
10
|
+
ClawchatActivateSchema,
|
|
10
11
|
ClawchatGetAccountProfileSchema,
|
|
11
12
|
ClawchatGetUserProfileSchema,
|
|
12
13
|
ClawchatListAccountFriendsSchema,
|
|
13
14
|
ClawchatUpdateAccountProfileSchema,
|
|
14
15
|
ClawchatUploadAvatarImageSchema,
|
|
15
16
|
ClawchatUploadMediaFileSchema,
|
|
16
|
-
|
|
17
|
+
type ClawchatActivateParams,
|
|
17
18
|
type ClawchatGetUserProfileParams,
|
|
18
19
|
type ClawchatListAccountFriendsParams,
|
|
19
20
|
type ClawchatUpdateAccountProfileParams,
|
|
@@ -46,12 +47,12 @@ function validationError(message: string) {
|
|
|
46
47
|
return jsonResponse({ error: "validation", message });
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
function resolveActivateCode(params: ClawchatActivateParams & { command?: unknown }): string {
|
|
51
|
+
const explicit = typeof params.code === "string" ? params.code.trim() : "";
|
|
52
|
+
if (explicit) return explicit;
|
|
53
|
+
const command = typeof params.command === "string" ? params.command.trim() : "";
|
|
54
|
+
return command.match(/\b[A-Z0-9]{6}\b/u)?.[0] ?? "";
|
|
55
|
+
}
|
|
55
56
|
|
|
56
57
|
function genericError(err: unknown) {
|
|
57
58
|
return jsonResponse({
|
|
@@ -94,64 +95,53 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// { name: "clawchat_activate" },
|
|
145
|
-
// );
|
|
146
|
-
|
|
147
|
-
const account = resolveOpenclawClawlingAccount(api.config);
|
|
148
|
-
if (!account.configured) {
|
|
149
|
-
api.logger.debug?.(
|
|
150
|
-
"openclaw-clawchat: account not yet configured; account tools will register " +
|
|
151
|
-
"on the next config reload after channel login.",
|
|
152
|
-
);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
98
|
+
api.registerTool(
|
|
99
|
+
{
|
|
100
|
+
name: "clawchat_activate",
|
|
101
|
+
label: "Clawling: Activate (Login with Invite Code)",
|
|
102
|
+
description:
|
|
103
|
+
"Activate this OpenClaw plugin on ClawChat by exchanging an invite code for a token. " +
|
|
104
|
+
"Invite codes use six uppercase letters/digits, e.g. A1B2C3. " +
|
|
105
|
+
"TRIGGER — invoke this tool whenever the user's message matches ANY of: " +
|
|
106
|
+
"(1) activation intent with an embedded invite code, such as 'activate ClawChat with invite code A1B2C3', " +
|
|
107
|
+
"'login to ClawChat with invite code A1B2C3', 'connect ClawChat using invite code A1B2C3', " +
|
|
108
|
+
"or '绑定 ClawChat,邀请码 A1B2C3' — call this tool with `code = \"A1B2C3\"`; " +
|
|
109
|
+
"(2) generic activation intent without an embedded code, such as 'activate ClawChat' or " +
|
|
110
|
+
"'login to ClawChat' — ask for invite code before calling the tool; " +
|
|
111
|
+
"(3) activation intent with an embedded code, such as 'use invite code A1B2C3', " +
|
|
112
|
+
"or the user pasting an invite code in the context of ClawChat activation. " +
|
|
113
|
+
"Extract the code verbatim — do NOT normalize / lowercase / add prefixes. " +
|
|
114
|
+
"On success the tool persists the resulting token + userId to the config, so " +
|
|
115
|
+
"subsequent `clawchat_*` calls work without another plugin registration pass.",
|
|
116
|
+
parameters: ClawchatActivateSchema,
|
|
117
|
+
async execute(_callId, params) {
|
|
118
|
+
const code = resolveActivateCode(params as ClawchatActivateParams & { command?: unknown });
|
|
119
|
+
if (!code) {
|
|
120
|
+
return validationError("openclaw-clawchat: code is required");
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const { runOpenclawClawlingLogin } = await import("./login.runtime.ts");
|
|
124
|
+
await runOpenclawClawlingLogin({
|
|
125
|
+
cfg: api.config!,
|
|
126
|
+
accountId: null,
|
|
127
|
+
runtime: { log: (message: string) => api.logger.info?.(message) },
|
|
128
|
+
readInviteCode: async () => code,
|
|
129
|
+
mutateConfigFile: (api.runtime.config as unknown as {
|
|
130
|
+
mutateConfigFile: OpenclawClawchatMutateConfigFile;
|
|
131
|
+
}).mutateConfigFile,
|
|
132
|
+
});
|
|
133
|
+
return jsonResponse({
|
|
134
|
+
ok: true,
|
|
135
|
+
message: "ClawChat activated successfully.",
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (err instanceof ClawlingApiError) return apiError(err);
|
|
139
|
+
return genericError(err);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{ name: "clawchat_activate" },
|
|
144
|
+
);
|
|
155
145
|
|
|
156
146
|
// Re-resolve at call time so config reloads pick up new tokens / baseUrl.
|
|
157
147
|
function resolveCurrent() {
|
|
@@ -159,8 +149,8 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
159
149
|
}
|
|
160
150
|
|
|
161
151
|
type ClientResult =
|
|
162
|
-
| { error: OpenClawAgentToolResult<unknown
|
|
163
|
-
| { client: ReturnType<typeof createOpenclawClawlingApiClient
|
|
152
|
+
| { ok: false; error: OpenClawAgentToolResult<unknown> }
|
|
153
|
+
| { ok: true; client: ReturnType<typeof createOpenclawClawlingApiClient> };
|
|
164
154
|
|
|
165
155
|
function buildClient(): ClientResult {
|
|
166
156
|
const acct = resolveCurrent();
|
|
@@ -168,9 +158,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
168
158
|
// only need to gate on `token` here (which is populated by `openclaw
|
|
169
159
|
// channel login`).
|
|
170
160
|
if (!acct.token) {
|
|
171
|
-
return { error: configError("openclaw-clawchat: token is required") };
|
|
161
|
+
return { ok: false, error: configError("openclaw-clawchat: token is required") };
|
|
172
162
|
}
|
|
173
163
|
return {
|
|
164
|
+
ok: true,
|
|
174
165
|
client: createOpenclawClawlingApiClient({
|
|
175
166
|
baseUrl: acct.baseUrl,
|
|
176
167
|
token: acct.token,
|
|
@@ -184,7 +175,7 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
184
175
|
): Promise<OpenClawAgentToolResult<unknown>> {
|
|
185
176
|
return (async (): Promise<OpenClawAgentToolResult<unknown>> => {
|
|
186
177
|
const built = buildClient();
|
|
187
|
-
if (built.
|
|
178
|
+
if (!built.ok) return built.error;
|
|
188
179
|
try {
|
|
189
180
|
const data = await fn(built.client);
|
|
190
181
|
return jsonResponse(data);
|
|
@@ -368,6 +359,6 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
368
359
|
);
|
|
369
360
|
|
|
370
361
|
api.logger.debug?.(
|
|
371
|
-
"openclaw-clawchat: registered
|
|
362
|
+
"openclaw-clawchat: registered 7 clawchat_* tools (activate, get_account_profile, get_user_profile, list_account_friends, update_account_profile, upload_avatar_image, upload_media_file)",
|
|
372
363
|
);
|
|
373
364
|
}
|