@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 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 channel login, plus always-registered `clawchat_*` account/media tools
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.29` as a peer host.
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 OpenClaw hot-reloads the updated `channels.*`
42
- credentials. If the tool is not available yet, use channel login as the CLI
43
- fallback:
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
- openclaw channels login --channel openclaw-clawchat
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 login:
74
+ running, start it after activation:
59
75
 
60
76
  ```bash
61
77
  openclaw gateway run
62
78
  ```
63
79
 
64
- The invite code is not a token; token fields are written only after login. The
65
- plugin registers `clawchat_activate` and the six account/media tools at plugin
66
- load time so they stay visible before activation. Before login, account/media
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 hot-reloaded token/userId.
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
- import { openclawClawlingConfigSchema } from "./src/config.js";
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
- configSchema: openclawClawlingConfigSchema,
11
- register(api) {
12
- setOpenclawClawlingRuntime(api.runtime);
13
- api.registerChannel({ plugin: openclawClawlingPlugin });
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
+ });
@@ -0,0 +1,3 @@
1
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
2
+ import { openclawClawlingSetupPlugin } from "./src/channel.setup.js";
3
+ export default defineSetupPluginEntry(openclawClawlingSetupPlugin);
@@ -1,145 +1,15 @@
1
- import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
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 { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
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
- const configAdapter = createTopLevelChannelConfigAdapter({
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
- id: CHANNEL_ID,
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
- return await startOpenclawClawlingGateway({
159
- cfg: ctx.cfg,
160
- account,
161
- abortSignal: ctx.abortSignal,
162
- setStatus: ctx.setStatus,
163
- getStatus: ctx.getStatus,
164
- log: ctx.log,
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
+ };
@@ -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) || DEFAULT_WEBSOCKET_URL;
182
- const baseUrl = readOptionalString(channel.baseUrl) || DEFAULT_BASE_URL;
183
- const token = readOptionalString(channel.token);
184
- const userId = readOptionalString(channel.userId);
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: { mode: "auto" },
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 `openclaw channels login --channel openclaw-clawchat` flow:
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.
@@ -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 `openclaw
122
- // channel login`).
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
- // import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
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
- configSchema: openclawClawlingConfigSchema,
14
- register(api: OpenClawPluginApi) {
15
- setOpenclawClawlingRuntime(api.runtime);
16
- api.registerChannel({ plugin: openclawClawlingPlugin });
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
+ });
@@ -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
  ],