@newbase-clawchat/openclaw-clawchat 2026.4.30 → 2026.5.4-1
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 +30 -11
- package/dist/index.js +9 -18
- package/dist/setup-entry.js +3 -0
- package/dist/src/channel.js +19 -142
- package/dist/src/channel.setup.js +133 -0
- package/dist/src/config.js +17 -5
- package/dist/src/login.runtime.js +11 -2
- package/dist/src/runtime.js +6 -0
- package/dist/src/tools.js +2 -2
- package/index.ts +9 -21
- package/openclaw.plugin.json +10 -0
- package/package.json +20 -5
- package/setup-entry.ts +4 -0
- package/skills/clawchat-activate/SKILL.md +15 -10
- package/src/channel.setup.ts +156 -0
- package/src/channel.test.ts +7 -1
- package/src/channel.ts +23 -171
- package/src/config.test.ts +44 -0
- package/src/config.ts +21 -5
- package/src/login.runtime.test.ts +5 -2
- package/src/login.runtime.ts +13 -2
- package/src/manifest.test.ts +124 -26
- package/src/plugin-entry.test.ts +8 -1
- package/src/runtime.ts +8 -0
- package/src/tools.ts +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ OpenClaw channel plugin that connects an agent to ClawChat over the ClawChat Pro
|
|
|
10
10
|
- Outbound text replies in `static` or `stream` mode, with a consolidated final `message.reply`
|
|
11
11
|
- Typing indicators and filtered forwarding for thinking / tool-call content
|
|
12
12
|
- Media fragments (image / file / audio / video) in either direction
|
|
13
|
-
- Invite-code onboarding via `clawchat_activate` or
|
|
13
|
+
- Invite-code onboarding via `clawchat_activate` or `/clawchat-login`, plus always-registered `clawchat_*` account/media tools
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ OpenClaw channel plugin that connects an agent to ClawChat over the ClawChat Pro
|
|
|
19
19
|
npm i @newbase-clawchat/openclaw-clawchat
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Requires `openclaw >= 2026.4
|
|
22
|
+
Requires `openclaw >= 2026.5.4` as a peer host.
|
|
23
23
|
|
|
24
24
|
For the OpenClaw plugin install/update flow, see [`INSTALL.md`](./INSTALL.md).
|
|
25
25
|
|
|
@@ -38,15 +38,31 @@ activate ClawChat with invite code A1B2C3
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
If the plugin is already loaded by the Gateway, the activation skill calls the
|
|
41
|
-
`clawchat_activate` tool and
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
`clawchat_activate` tool and persists credentials. If you are activating from
|
|
42
|
+
Telegram or another chat platform and the agent does not call the tool, send the
|
|
43
|
+
runtime slash command in that chat:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
/clawchat-login A1B2C3
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The slash command is provided by the loaded plugin. It is not a shell command,
|
|
50
|
+
so `openclaw clawchat-login` is expected to fail.
|
|
51
|
+
|
|
52
|
+
On OpenClaw hosts where the CLI channel catalog includes `openclaw-clawchat`,
|
|
53
|
+
terminal activation can also use:
|
|
44
54
|
|
|
45
55
|
```bash
|
|
46
|
-
|
|
56
|
+
CLAWCHAT_INVITE_CODE="A1B2C3"
|
|
57
|
+
openclaw channels add --channel openclaw-clawchat --token "$CLAWCHAT_INVITE_CODE"
|
|
47
58
|
openclaw channels status --probe
|
|
48
59
|
```
|
|
49
60
|
|
|
61
|
+
OpenClaw 2026.5.5 can load an npm-installed third-party channel while still
|
|
62
|
+
omitting it from the `channels add` CLI catalog. If `channels add` fails with
|
|
63
|
+
`Unknown channel: openclaw-clawchat`, use `clawchat_activate` or
|
|
64
|
+
`/clawchat-login A1B2C3` after a real Gateway restart.
|
|
65
|
+
|
|
50
66
|
Restart the Gateway after installing/updating the plugin, when config reload is
|
|
51
67
|
disabled, or when the channel probe does not become healthy:
|
|
52
68
|
|
|
@@ -55,17 +71,20 @@ openclaw gateway restart
|
|
|
55
71
|
```
|
|
56
72
|
|
|
57
73
|
If you run the gateway manually instead of as a service and it is not already
|
|
58
|
-
running, start it after
|
|
74
|
+
running, start it after activation:
|
|
59
75
|
|
|
60
76
|
```bash
|
|
61
77
|
openclaw gateway run
|
|
62
78
|
```
|
|
63
79
|
|
|
64
|
-
The
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
The `--token` value above is the ClawChat invite code for OpenClaw's generic
|
|
81
|
+
`channels add` CLI surface on hosts that expose this plugin in the channel
|
|
82
|
+
catalog; persisted token fields are written only after the invite code exchange
|
|
83
|
+
succeeds. The plugin registers `clawchat_activate` and the six account/media tools at plugin
|
|
84
|
+
load time so they stay visible before activation. Before activation, account/media
|
|
67
85
|
tools return a config error instead of disappearing; after activation/login, the
|
|
68
|
-
channel is enabled and the same tools read the
|
|
86
|
+
channel is enabled and the same tools read the persisted token/userId after the
|
|
87
|
+
Gateway restarts.
|
|
69
88
|
|
|
70
89
|
After activation/login, the channel section is enabled and has credentials:
|
|
71
90
|
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
1
2
|
import { openclawClawlingPlugin } from "./src/channel.js";
|
|
2
3
|
import { registerOpenclawClawlingCommands } from "./src/commands.js";
|
|
4
|
+
import { openclawClawlingConfigSchema } from "./src/config.js";
|
|
3
5
|
import { setOpenclawClawlingRuntime } from "./src/runtime.js";
|
|
4
6
|
import { registerOpenclawClawlingTools } from "./src/tools.js";
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
+
export default defineChannelPluginEntry({
|
|
7
8
|
id: "openclaw-clawchat",
|
|
8
9
|
name: "Clawling Chat",
|
|
9
10
|
description: "Clawling Chat Protocol v2 channel plugin (chat-sdk)",
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
plugin: openclawClawlingPlugin,
|
|
12
|
+
configSchema: { schema: openclawClawlingConfigSchema },
|
|
13
|
+
setRuntime: setOpenclawClawlingRuntime,
|
|
14
|
+
registerFull(api) {
|
|
14
15
|
registerOpenclawClawlingCommands(api);
|
|
15
16
|
registerOpenclawClawlingTools(api);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
// export default defineChannelPluginEntry({
|
|
19
|
-
// id: "openclaw-clawchat",
|
|
20
|
-
// name: "Clawling Chat",
|
|
21
|
-
// description: "Clawling Chat Protocol v2 channel plugin (chat-sdk)",
|
|
22
|
-
// plugin: openclawClawlingPlugin,
|
|
23
|
-
// setRuntime: setOpenclawClawlingRuntime,
|
|
24
|
-
// registerFull(api) {
|
|
25
|
-
// registerOpenclawClawlingTools(api);
|
|
26
|
-
// },
|
|
27
|
-
// });
|
|
17
|
+
},
|
|
18
|
+
});
|
package/dist/src/channel.js
CHANGED
|
@@ -1,145 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createChatChannelPlugin, } from "openclaw/plugin-sdk/core";
|
|
1
|
+
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
3
2
|
import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
|
|
4
|
-
import {
|
|
5
|
-
import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, } from "openclaw/plugin-sdk/status-helpers";
|
|
6
|
-
import { CHANNEL_ID, listOpenclawClawlingAccountIds, mergeOpenclawClawchatToolAllow, openclawClawlingConfigSchema, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
3
|
+
import { resolveOpenclawClawlingAccount, } from "./config.js";
|
|
7
4
|
import { openclawClawlingOutbound } from "./outbound.js";
|
|
8
5
|
import { getOpenclawClawlingRuntime, startOpenclawClawlingGateway } from "./runtime.js";
|
|
9
|
-
|
|
10
|
-
sectionKey: CHANNEL_ID,
|
|
11
|
-
resolveAccount: (cfg) => resolveOpenclawClawlingAccount(cfg),
|
|
12
|
-
listAccountIds: () => listOpenclawClawlingAccountIds(),
|
|
13
|
-
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
14
|
-
deleteMode: "clear-fields",
|
|
15
|
-
clearBaseFields: [
|
|
16
|
-
"websocketUrl",
|
|
17
|
-
"baseUrl",
|
|
18
|
-
"token",
|
|
19
|
-
"userId",
|
|
20
|
-
"replyMode",
|
|
21
|
-
"forwardThinking",
|
|
22
|
-
"forwardToolCalls",
|
|
23
|
-
"richInteractions",
|
|
24
|
-
"enabled",
|
|
25
|
-
],
|
|
26
|
-
resolveAllowFrom: (account) => account.allowFrom,
|
|
27
|
-
formatAllowFrom: () => [],
|
|
28
|
-
});
|
|
29
|
-
/**
|
|
30
|
-
* Invite-code setup adapter used by OpenClaw setup surfaces that already have
|
|
31
|
-
* a concrete plugin instance. This plugin does not advertise catalog-driven
|
|
32
|
-
* one-shot setup metadata because current hosts do not discover channels from
|
|
33
|
-
* `plugins.load.paths`.
|
|
34
|
-
*
|
|
35
|
-
* Setup takes exactly ONE input: `code` (an invite code). URL + token +
|
|
36
|
-
* userId come from the login flow which is triggered automatically in
|
|
37
|
-
* `afterAccountConfigWritten`.
|
|
38
|
-
*
|
|
39
|
-
* `applyAccountConfig` itself only marks the section `enabled: true`;
|
|
40
|
-
* credentials are written by `runOpenclawClawlingLogin` via the runtime config
|
|
41
|
-
* mutator after the `/v1/agents/connect` response lands.
|
|
42
|
-
*/
|
|
43
|
-
const setupAdapter = {
|
|
44
|
-
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
45
|
-
validateInput: ({ input }) => {
|
|
46
|
-
if (!input.code?.trim()) {
|
|
47
|
-
return "ClawChat invite code is required.";
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
},
|
|
51
|
-
applyAccountConfig: ({ cfg, }) => {
|
|
52
|
-
// Base config: just enable the channel. Credentials arrive via
|
|
53
|
-
// `afterAccountConfigWritten` → `runOpenclawClawlingLogin`.
|
|
54
|
-
const channels = (cfg.channels ?? {});
|
|
55
|
-
const current = (channels[CHANNEL_ID] ?? {});
|
|
56
|
-
return mergeOpenclawClawchatToolAllow({
|
|
57
|
-
...cfg,
|
|
58
|
-
channels: {
|
|
59
|
-
...channels,
|
|
60
|
-
[CHANNEL_ID]: { ...current, enabled: true },
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
},
|
|
64
|
-
afterAccountConfigWritten: async ({ cfg, input, runtime, }) => {
|
|
65
|
-
const code = input.code?.trim();
|
|
66
|
-
if (!code)
|
|
67
|
-
return;
|
|
68
|
-
// Lazy-import the login runtime to keep @clack/prompts / readline /
|
|
69
|
-
// config-runtime off the plugin's cold-start path. `readInviteCode`
|
|
70
|
-
// feeds the fixed code so the stdin prompt is skipped entirely.
|
|
71
|
-
const { runOpenclawClawlingLogin } = await import("./login.runtime.js");
|
|
72
|
-
await runOpenclawClawlingLogin({
|
|
73
|
-
cfg,
|
|
74
|
-
accountId: null,
|
|
75
|
-
runtime: { log: (message) => runtime.log(message) },
|
|
76
|
-
readInviteCode: async () => code,
|
|
77
|
-
mutateConfigFile: getOpenclawClawlingRuntime().config.mutateConfigFile,
|
|
78
|
-
});
|
|
79
|
-
},
|
|
80
|
-
};
|
|
6
|
+
import { openclawClawlingSetupPlugin } from "./channel.setup.js";
|
|
81
7
|
export const openclawClawlingPlugin = createChatChannelPlugin({
|
|
82
8
|
base: {
|
|
83
|
-
|
|
84
|
-
meta: {
|
|
85
|
-
id: CHANNEL_ID,
|
|
86
|
-
label: "Clawling Chat",
|
|
87
|
-
selectionLabel: "Clawling Chat",
|
|
88
|
-
docsPath: "/channels/openclaw-clawchat",
|
|
89
|
-
docsLabel: "openclaw-clawchat",
|
|
90
|
-
blurb: "Clawling Protocol v2 over WebSocket (chat-sdk).",
|
|
91
|
-
order: 110,
|
|
92
|
-
},
|
|
93
|
-
capabilities: {
|
|
94
|
-
chatTypes: ["direct", "group"],
|
|
95
|
-
media: true,
|
|
96
|
-
reactions: false,
|
|
97
|
-
threads: false,
|
|
98
|
-
polls: false,
|
|
99
|
-
blockStreaming: true,
|
|
100
|
-
},
|
|
101
|
-
reload: {
|
|
102
|
-
configPrefixes: [`channels.${CHANNEL_ID}`],
|
|
103
|
-
},
|
|
104
|
-
configSchema: {
|
|
105
|
-
schema: openclawClawlingConfigSchema,
|
|
106
|
-
},
|
|
107
|
-
config: {
|
|
108
|
-
...configAdapter,
|
|
109
|
-
isConfigured: (account) => account.configured,
|
|
110
|
-
describeAccount: (account) => ({
|
|
111
|
-
accountId: account.accountId,
|
|
112
|
-
name: account.name,
|
|
113
|
-
enabled: account.enabled,
|
|
114
|
-
configured: account.configured,
|
|
115
|
-
}),
|
|
116
|
-
},
|
|
9
|
+
...openclawClawlingSetupPlugin,
|
|
117
10
|
directory: createEmptyChannelDirectoryAdapter(),
|
|
118
|
-
setup: setupAdapter,
|
|
119
|
-
status: createComputedAccountStatusAdapter({
|
|
120
|
-
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, {
|
|
121
|
-
connected: false,
|
|
122
|
-
lastInboundAt: null,
|
|
123
|
-
lastOutboundAt: null,
|
|
124
|
-
}),
|
|
125
|
-
resolveAccountSnapshot: ({ account }) => ({
|
|
126
|
-
accountId: account.accountId,
|
|
127
|
-
name: account.name,
|
|
128
|
-
enabled: account.enabled,
|
|
129
|
-
configured: account.configured,
|
|
130
|
-
extra: {
|
|
131
|
-
websocketUrl: account.websocketUrl || null,
|
|
132
|
-
baseUrl: account.baseUrl || null,
|
|
133
|
-
userId: account.userId || null,
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
}),
|
|
137
11
|
auth: {
|
|
138
12
|
login: async ({ cfg, accountId, runtime }) => {
|
|
139
|
-
// Lazy-load login.runtime: it pulls in @clack/prompts and other
|
|
140
|
-
// heavy modules that have no business loading on every plugin
|
|
141
|
-
// boot. Only the rare `openclaw channels login --channel
|
|
142
|
-
// openclaw-clawchat` invocation pays the import cost.
|
|
143
13
|
const { runOpenclawClawlingLogin } = await import("./login.runtime.js");
|
|
144
14
|
await runOpenclawClawlingLogin({
|
|
145
15
|
cfg,
|
|
@@ -152,17 +22,24 @@ export const openclawClawlingPlugin = createChatChannelPlugin({
|
|
|
152
22
|
gateway: {
|
|
153
23
|
startAccount: async (ctx) => {
|
|
154
24
|
const account = ctx.account ?? resolveOpenclawClawlingAccount(ctx.cfg);
|
|
25
|
+
ctx.log?.info?.(`[${account.accountId}] openclaw-clawchat lifecycle startAccount invoked configured=${account.configured} enabled=${account.enabled} hasToken=${Boolean(account.token)} hasUserId=${Boolean(account.userId)} websocketUrl=${account.websocketUrl || "(empty)"}`);
|
|
155
26
|
if (!account.configured) {
|
|
27
|
+
ctx.log?.error?.(`[${account.accountId}] openclaw-clawchat lifecycle startAccount refused: websocketUrl/token/userId are required`);
|
|
156
28
|
throw new Error("Clawling Chat websocketUrl/token/userId are required");
|
|
157
29
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
30
|
+
try {
|
|
31
|
+
await startOpenclawClawlingGateway({
|
|
32
|
+
cfg: ctx.cfg,
|
|
33
|
+
account,
|
|
34
|
+
abortSignal: ctx.abortSignal,
|
|
35
|
+
setStatus: ctx.setStatus,
|
|
36
|
+
getStatus: ctx.getStatus,
|
|
37
|
+
log: ctx.log,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
ctx.log?.info?.(`[${account.accountId}] openclaw-clawchat lifecycle startAccount completed/stopped`);
|
|
42
|
+
}
|
|
166
43
|
},
|
|
167
44
|
},
|
|
168
45
|
agentPrompt: {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
2
|
+
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
|
3
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
|
|
4
|
+
import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, } from "openclaw/plugin-sdk/status-helpers";
|
|
5
|
+
import { CHANNEL_ID, listOpenclawClawlingAccountIds, mergeOpenclawClawchatToolAllow, openclawClawlingConfigSchema, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
6
|
+
const configAdapter = createTopLevelChannelConfigAdapter({
|
|
7
|
+
sectionKey: CHANNEL_ID,
|
|
8
|
+
resolveAccount: (cfg) => resolveOpenclawClawlingAccount(cfg),
|
|
9
|
+
listAccountIds: () => listOpenclawClawlingAccountIds(),
|
|
10
|
+
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
11
|
+
deleteMode: "clear-fields",
|
|
12
|
+
clearBaseFields: [
|
|
13
|
+
"websocketUrl",
|
|
14
|
+
"baseUrl",
|
|
15
|
+
"token",
|
|
16
|
+
"userId",
|
|
17
|
+
"replyMode",
|
|
18
|
+
"forwardThinking",
|
|
19
|
+
"forwardToolCalls",
|
|
20
|
+
"richInteractions",
|
|
21
|
+
"enabled",
|
|
22
|
+
],
|
|
23
|
+
resolveAllowFrom: (account) => account.allowFrom,
|
|
24
|
+
formatAllowFrom: () => [],
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Invite-code setup adapter used by OpenClaw setup surfaces.
|
|
28
|
+
*
|
|
29
|
+
* `channels add --token` passes the invite code as setup input. The first
|
|
30
|
+
* config write only enables the channel; `afterAccountConfigWritten` exchanges
|
|
31
|
+
* the invite code and persists token/userId through the host runtime mutator.
|
|
32
|
+
*/
|
|
33
|
+
const setupAdapter = {
|
|
34
|
+
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
35
|
+
validateInput: ({ input }) => {
|
|
36
|
+
const inviteCode = typeof input.code === "string" && input.code.trim()
|
|
37
|
+
? input.code.trim()
|
|
38
|
+
: typeof input.token === "string"
|
|
39
|
+
? input.token.trim()
|
|
40
|
+
: "";
|
|
41
|
+
if (!inviteCode) {
|
|
42
|
+
return "ClawChat invite code is required.";
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
},
|
|
46
|
+
applyAccountConfig: ({ cfg }) => {
|
|
47
|
+
const channels = (cfg.channels ?? {});
|
|
48
|
+
const current = (channels[CHANNEL_ID] ?? {});
|
|
49
|
+
return mergeOpenclawClawchatToolAllow({
|
|
50
|
+
...cfg,
|
|
51
|
+
channels: {
|
|
52
|
+
...channels,
|
|
53
|
+
[CHANNEL_ID]: { ...current, enabled: true },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
afterAccountConfigWritten: async ({ cfg, input, runtime }) => {
|
|
58
|
+
runtime.log("[default] openclaw-clawchat setup afterAccountConfigWritten invoked");
|
|
59
|
+
const code = typeof input.code === "string" && input.code.trim()
|
|
60
|
+
? input.code.trim()
|
|
61
|
+
: typeof input.token === "string"
|
|
62
|
+
? input.token.trim()
|
|
63
|
+
: "";
|
|
64
|
+
if (!code) {
|
|
65
|
+
runtime.log("[default] openclaw-clawchat setup afterAccountConfigWritten skipped: empty invite code");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const { runOpenclawClawlingLogin } = await import("./login.runtime.js");
|
|
69
|
+
await runOpenclawClawlingLogin({
|
|
70
|
+
cfg,
|
|
71
|
+
accountId: null,
|
|
72
|
+
runtime: { log: (message) => runtime.log(message) },
|
|
73
|
+
readInviteCode: async () => code,
|
|
74
|
+
mutateConfigFile: mutateConfigFile,
|
|
75
|
+
});
|
|
76
|
+
runtime.log("[default] openclaw-clawchat setup afterAccountConfigWritten completed");
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
export const openclawClawlingSetupPlugin = {
|
|
80
|
+
id: CHANNEL_ID,
|
|
81
|
+
meta: {
|
|
82
|
+
id: CHANNEL_ID,
|
|
83
|
+
label: "Clawling Chat",
|
|
84
|
+
selectionLabel: "Clawling Chat",
|
|
85
|
+
docsPath: "/channels/openclaw-clawchat",
|
|
86
|
+
docsLabel: "openclaw-clawchat",
|
|
87
|
+
blurb: "Clawling Protocol v2 over WebSocket (chat-sdk).",
|
|
88
|
+
order: 110,
|
|
89
|
+
},
|
|
90
|
+
capabilities: {
|
|
91
|
+
chatTypes: ["direct", "group"],
|
|
92
|
+
media: true,
|
|
93
|
+
reactions: false,
|
|
94
|
+
threads: false,
|
|
95
|
+
polls: false,
|
|
96
|
+
blockStreaming: true,
|
|
97
|
+
},
|
|
98
|
+
reload: {
|
|
99
|
+
configPrefixes: [`channels.${CHANNEL_ID}`],
|
|
100
|
+
},
|
|
101
|
+
configSchema: {
|
|
102
|
+
schema: openclawClawlingConfigSchema,
|
|
103
|
+
},
|
|
104
|
+
config: {
|
|
105
|
+
...configAdapter,
|
|
106
|
+
isConfigured: (account) => account.configured,
|
|
107
|
+
describeAccount: (account) => ({
|
|
108
|
+
accountId: account.accountId,
|
|
109
|
+
name: account.name,
|
|
110
|
+
enabled: account.enabled,
|
|
111
|
+
configured: account.configured,
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
setup: setupAdapter,
|
|
115
|
+
status: createComputedAccountStatusAdapter({
|
|
116
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, {
|
|
117
|
+
connected: false,
|
|
118
|
+
lastInboundAt: null,
|
|
119
|
+
lastOutboundAt: null,
|
|
120
|
+
}),
|
|
121
|
+
resolveAccountSnapshot: ({ account }) => ({
|
|
122
|
+
accountId: account.accountId,
|
|
123
|
+
name: account.name,
|
|
124
|
+
enabled: account.enabled,
|
|
125
|
+
configured: account.configured,
|
|
126
|
+
extra: {
|
|
127
|
+
websocketUrl: account.websocketUrl || null,
|
|
128
|
+
baseUrl: account.baseUrl || null,
|
|
129
|
+
userId: account.userId || null,
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
}),
|
|
133
|
+
};
|
package/dist/src/config.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
|
|
2
2
|
export const CHANNEL_ID = "openclaw-clawchat";
|
|
3
|
+
export const CLAWCHAT_TOKEN_ENV = "CLAWCHAT_TOKEN";
|
|
4
|
+
export const CLAWCHAT_USER_ID_ENV = "CLAWCHAT_USER_ID";
|
|
5
|
+
export const CLAWCHAT_REFRESH_TOKEN_ENV = "CLAWCHAT_REFRESH_TOKEN";
|
|
6
|
+
export const CLAWCHAT_BASE_URL_ENV = "CLAWCHAT_BASE_URL";
|
|
7
|
+
export const CLAWCHAT_WEBSOCKET_URL_ENV = "CLAWCHAT_WEBSOCKET_URL";
|
|
3
8
|
/**
|
|
4
9
|
* Built-in defaults for the Clawling Chat endpoints so `openclaw channel
|
|
5
10
|
* login` works out of the box without requiring a prior `openclaw channel
|
|
@@ -134,6 +139,9 @@ function readChannelSection(cfg) {
|
|
|
134
139
|
function readOptionalString(value) {
|
|
135
140
|
return typeof value === "string" ? value.trim() : "";
|
|
136
141
|
}
|
|
142
|
+
function readEnvString(env, key) {
|
|
143
|
+
return readOptionalString(env[key]);
|
|
144
|
+
}
|
|
137
145
|
function readReplyMode(value) {
|
|
138
146
|
return value === "stream" ? "stream" : "static";
|
|
139
147
|
}
|
|
@@ -175,13 +183,17 @@ function readAck(raw) {
|
|
|
175
183
|
: DEFAULT_ACK.autoResendOnTimeout,
|
|
176
184
|
};
|
|
177
185
|
}
|
|
178
|
-
export function resolveOpenclawClawlingAccount(cfg) {
|
|
186
|
+
export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
179
187
|
const channel = readChannelSection(cfg);
|
|
180
188
|
// Apply built-in defaults so login/gateway work without prior setup.
|
|
181
|
-
const websocketUrl = readOptionalString(channel.websocketUrl) ||
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
189
|
+
const websocketUrl = readOptionalString(channel.websocketUrl) ||
|
|
190
|
+
readEnvString(env, CLAWCHAT_WEBSOCKET_URL_ENV) ||
|
|
191
|
+
DEFAULT_WEBSOCKET_URL;
|
|
192
|
+
const baseUrl = readOptionalString(channel.baseUrl) ||
|
|
193
|
+
readEnvString(env, CLAWCHAT_BASE_URL_ENV) ||
|
|
194
|
+
DEFAULT_BASE_URL;
|
|
195
|
+
const token = readOptionalString(channel.token) || readEnvString(env, CLAWCHAT_TOKEN_ENV);
|
|
196
|
+
const userId = readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV);
|
|
185
197
|
const enabled = typeof channel.enabled === "boolean" ? channel.enabled : true;
|
|
186
198
|
const replyMode = readReplyMode(channel.replyMode);
|
|
187
199
|
const groupMode = readGroupMode(channel.groupMode);
|
|
@@ -54,22 +54,31 @@ function buildLoginConfig(cfg, result) {
|
|
|
54
54
|
}
|
|
55
55
|
async function persistLoginConfig(params, result) {
|
|
56
56
|
if (params.mutateConfigFile) {
|
|
57
|
+
params.runtime.log(`Persisting ClawChat credentials for userId=${result.agent.user_id} with Gateway restart intent.`);
|
|
57
58
|
await params.mutateConfigFile({
|
|
58
|
-
afterWrite: {
|
|
59
|
+
afterWrite: {
|
|
60
|
+
mode: "restart",
|
|
61
|
+
reason: "openclaw-clawchat credentials changed",
|
|
62
|
+
},
|
|
59
63
|
mutate(draft) {
|
|
60
64
|
Object.assign(draft, buildLoginConfig(draft, result));
|
|
61
65
|
},
|
|
62
66
|
});
|
|
67
|
+
params.runtime.log(`ClawChat credentials persisted for userId=${result.agent.user_id}.`);
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
65
70
|
if (params.persistConfig) {
|
|
71
|
+
params.runtime.log(`Persisting ClawChat credentials for userId=${result.agent.user_id}.`);
|
|
66
72
|
await params.persistConfig(buildLoginConfig(params.cfg, result));
|
|
73
|
+
params.runtime.log(`ClawChat credentials persisted for userId=${result.agent.user_id}.`);
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
69
76
|
throw new Error("openclaw-clawchat: mutateConfigFile is required to persist login credentials");
|
|
70
77
|
}
|
|
71
78
|
/**
|
|
72
|
-
* Run the
|
|
79
|
+
* Run the invite-code credential exchange used by `clawchat_activate`,
|
|
80
|
+
* `openclaw channels add --channel openclaw-clawchat --token <invite-code>`,
|
|
81
|
+
* and `openclaw channels login --channel openclaw-clawchat`:
|
|
73
82
|
* 1. Read the existing channel section; require `baseUrl` to be set so we
|
|
74
83
|
* know which server to hit.
|
|
75
84
|
* 2. Prompt the user for an invite code on stdin.
|
package/dist/src/runtime.js
CHANGED
|
@@ -76,9 +76,11 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
76
76
|
// Obtain PluginRuntime from the stored runtime set via setOpenclawClawlingRuntime.
|
|
77
77
|
const runtime = getOpenclawClawlingRuntime();
|
|
78
78
|
const accountId = account.accountId;
|
|
79
|
+
log?.info?.(`[${accountId}] openclaw-clawchat runtime start entered configured=${account.configured} enabled=${account.enabled} hasToken=${Boolean(account.token)} hasUserId=${Boolean(account.userId)} websocketUrl=${account.websocketUrl || "(empty)"}`);
|
|
79
80
|
const client = createOpenclawClawlingClient(account, {
|
|
80
81
|
...(params.transport ? { transport: params.transport } : {}),
|
|
81
82
|
});
|
|
83
|
+
log?.info?.(`[${accountId}] openclaw-clawchat runtime client created`);
|
|
82
84
|
client.on("state", ({ from, to }) => {
|
|
83
85
|
log?.info?.(`[${accountId}] openclaw-clawchat state ${from} -> ${to}`);
|
|
84
86
|
const next = { ...getStatus(), ...mapClawlingStateToStatus(to) };
|
|
@@ -250,7 +252,9 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
250
252
|
// return without throwing (which would make the gateway supervisor
|
|
251
253
|
// restart us immediately in a tight loop).
|
|
252
254
|
try {
|
|
255
|
+
log?.info?.(`[${accountId}] openclaw-clawchat runtime calling client.connect()`);
|
|
253
256
|
await client.connect();
|
|
257
|
+
log?.info?.(`[${accountId}] openclaw-clawchat runtime client.connect() resolved`);
|
|
254
258
|
}
|
|
255
259
|
catch (err) {
|
|
256
260
|
const classified = classifyClawlingClientError(err);
|
|
@@ -265,6 +269,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
265
269
|
return;
|
|
266
270
|
}
|
|
267
271
|
activeClients.set(accountId, client);
|
|
272
|
+
log?.info?.(`[${accountId}] openclaw-clawchat runtime active client registered`);
|
|
268
273
|
setStatus({
|
|
269
274
|
...getStatus(),
|
|
270
275
|
connected: true,
|
|
@@ -273,6 +278,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
273
278
|
});
|
|
274
279
|
log?.info?.(`[${accountId}] openclaw-clawchat connected`);
|
|
275
280
|
await waitUntilAbort(abortSignal, async () => {
|
|
281
|
+
log?.info?.(`[${accountId}] openclaw-clawchat runtime abort received; closing client`);
|
|
276
282
|
activeClients.delete(accountId);
|
|
277
283
|
client.close();
|
|
278
284
|
setStatus({
|
package/dist/src/tools.js
CHANGED
|
@@ -118,8 +118,8 @@ export function registerOpenclawClawlingTools(api) {
|
|
|
118
118
|
function buildClient() {
|
|
119
119
|
const acct = resolveCurrent();
|
|
120
120
|
// `baseUrl` always resolves via the built-in default in config.ts, so we
|
|
121
|
-
// only need to gate on `token` here (which is populated by
|
|
122
|
-
//
|
|
121
|
+
// only need to gate on `token` here (which is populated by ClawChat
|
|
122
|
+
// activation/login).
|
|
123
123
|
if (!acct.token) {
|
|
124
124
|
return { ok: false, error: configError("openclaw-clawchat: token is required") };
|
|
125
125
|
}
|
package/index.ts
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
3
2
|
import { openclawClawlingPlugin } from "./src/channel.ts";
|
|
4
3
|
import { registerOpenclawClawlingCommands } from "./src/commands.ts";
|
|
4
|
+
import { openclawClawlingConfigSchema } from "./src/config.ts";
|
|
5
5
|
import { setOpenclawClawlingRuntime } from "./src/runtime.ts";
|
|
6
6
|
import { registerOpenclawClawlingTools } from "./src/tools.ts";
|
|
7
|
-
import { openclawClawlingConfigSchema } from "./src/config.ts";
|
|
8
7
|
|
|
9
|
-
export default {
|
|
8
|
+
export default defineChannelPluginEntry({
|
|
10
9
|
id: "openclaw-clawchat",
|
|
11
10
|
name: "Clawling Chat",
|
|
12
11
|
description: "Clawling Chat Protocol v2 channel plugin (chat-sdk)",
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
plugin: openclawClawlingPlugin,
|
|
13
|
+
configSchema: { schema: openclawClawlingConfigSchema },
|
|
14
|
+
setRuntime: setOpenclawClawlingRuntime,
|
|
15
|
+
registerFull(api) {
|
|
17
16
|
registerOpenclawClawlingCommands(api);
|
|
18
17
|
registerOpenclawClawlingTools(api);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// export default defineChannelPluginEntry({
|
|
23
|
-
// id: "openclaw-clawchat",
|
|
24
|
-
// name: "Clawling Chat",
|
|
25
|
-
// description: "Clawling Chat Protocol v2 channel plugin (chat-sdk)",
|
|
26
|
-
// plugin: openclawClawlingPlugin,
|
|
27
|
-
// setRuntime: setOpenclawClawlingRuntime,
|
|
28
|
-
// registerFull(api) {
|
|
29
|
-
// registerOpenclawClawlingTools(api);
|
|
30
|
-
// },
|
|
31
|
-
// });
|
|
18
|
+
},
|
|
19
|
+
});
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-clawchat",
|
|
3
|
+
"kind": "channel",
|
|
3
4
|
"channels": ["openclaw-clawchat"],
|
|
4
5
|
"skills": ["./skills"],
|
|
5
6
|
"activation": {
|
|
@@ -7,6 +8,15 @@
|
|
|
7
8
|
"onChannels": ["openclaw-clawchat"],
|
|
8
9
|
"onCommands": ["clawchat-login"]
|
|
9
10
|
},
|
|
11
|
+
"channelEnvVars": {
|
|
12
|
+
"openclaw-clawchat": [
|
|
13
|
+
"CLAWCHAT_TOKEN",
|
|
14
|
+
"CLAWCHAT_USER_ID",
|
|
15
|
+
"CLAWCHAT_REFRESH_TOKEN",
|
|
16
|
+
"CLAWCHAT_BASE_URL",
|
|
17
|
+
"CLAWCHAT_WEBSOCKET_URL"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
10
20
|
"commandAliases": [
|
|
11
21
|
{ "name": "clawchat-login", "kind": "runtime-slash" }
|
|
12
22
|
],
|