@clawling/clawchat-plugin-openclaw 2026.9.26-3 → 2026.10.7-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.
@@ -124,6 +124,7 @@
124
124
  "forwardThinking": { "type": "boolean" },
125
125
  "forwardToolCalls": { "type": "boolean" },
126
126
  "richInteractions": { "type": "boolean" },
127
+ "streamReplies": { "type": "boolean" },
127
128
  "awarenessNote": { "type": "boolean" },
128
129
  "friendGreeting": { "type": "boolean" },
129
130
  "livewareSample": { "type": "boolean" },
@@ -197,6 +198,7 @@
197
198
  "forwardThinking": { "type": "boolean" },
198
199
  "forwardToolCalls": { "type": "boolean" },
199
200
  "richInteractions": { "type": "boolean" },
201
+ "streamReplies": { "type": "boolean" },
200
202
  "awarenessNote": { "type": "boolean" },
201
203
  "friendGreeting": { "type": "boolean" },
202
204
  "livewareSample": { "type": "boolean" },
@@ -276,6 +278,7 @@
276
278
  "forwardThinking": { "type": "boolean" },
277
279
  "forwardToolCalls": { "type": "boolean" },
278
280
  "richInteractions": { "type": "boolean" },
281
+ "streamReplies": { "type": "boolean" },
279
282
  "awarenessNote": { "type": "boolean" },
280
283
  "friendGreeting": { "type": "boolean" },
281
284
  "livewareSample": { "type": "boolean" },
@@ -349,6 +352,7 @@
349
352
  "forwardThinking": { "type": "boolean" },
350
353
  "forwardToolCalls": { "type": "boolean" },
351
354
  "richInteractions": { "type": "boolean" },
355
+ "streamReplies": { "type": "boolean" },
352
356
  "awarenessNote": { "type": "boolean" },
353
357
  "friendGreeting": { "type": "boolean" },
354
358
  "livewareSample": { "type": "boolean" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.9.26-3",
3
+ "version": "2026.10.7-2",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "author": "CLAWLING PTE. LTD.",
@@ -42,7 +42,8 @@
42
42
  "build": "tsc -p tsconfig.build.json",
43
43
  "test": "vitest",
44
44
  "dev:openclaw-source": "test -d tmp/openclaw || git clone --depth=1 https://github.com/openclaw/openclaw.git tmp/openclaw",
45
- "prepack": "npm run build",
45
+ "prepack": "npm run build && node tools/pack-manifest.mjs strip",
46
+ "postpack": "node tools/pack-manifest.mjs restore",
46
47
  "typecheck": "tsc --noEmit",
47
48
  "prepublishOnly": "npm run typecheck",
48
49
  "release": "npm run prepublishOnly && npm publish --tag latest"
@@ -50,12 +51,6 @@
50
51
  "dependencies": {
51
52
  "@sinclair/typebox": "0.34.48"
52
53
  },
53
- "devDependencies": {
54
- "@types/node": "^25.5.0",
55
- "openclaw": "2026.6.11",
56
- "typescript": "^5.4.0",
57
- "vitest": "^4.1.5"
58
- },
59
54
  "peerDependencies": {
60
55
  "openclaw": ">=2026.5.4"
61
56
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clawchat-core
3
- version: 1.9.0
3
+ version: 1.10.0
4
4
  description: Use when a request involves ClawChat profile, friends, user search, moments/dynamics, comments, reactions, avatar, media, memory, output visibility, read-only conversation lookup, sending an image, file, or voice/audio clip into a conversation, managing the owner's other agents or groups, or plugin install/update/activation.
5
5
  ---
6
6
 
@@ -18,7 +18,7 @@ This skill guides agent behavior for ClawChat-aware tasks. Use the registered Cl
18
18
 
19
19
  ## Sending an Image, File, or Voice Message
20
20
 
21
- To deliver an image, file, or voice/audio clip into the current ClawChat conversation, use the OpenClaw **message tool** with `action='send'` and `media` set to a local file path or an HTTPS URL. This is a host message-tool capability, not a `clawchat_*` tool — the `clawchat_upload_avatar_image` / moment-image tools are a separate avatar/moments surface and do not post into a conversation.
21
+ To deliver an image, file, or voice/audio clip into the current ClawChat conversation, use the OpenClaw **message tool** with `action='send'` and `media` set to a local file path or an HTTPS URL. This is a host message-tool capability, not a `clawchat_*` tool — `clawchat_upload_avatar_image` (avatars) and the `images` of `clawchat_create_moment` (moments) are separate surfaces and do not post into a conversation.
22
22
 
23
23
  - ClawChat detects the media type from the file and renders it: images inline, audio files (`.mp3`, `.m4a`, `.wav`, `.ogg`, `.aac`, …) as **playable voice messages**, everything else as a downloadable file. There is no separate voice tool or `voice` kind — a voice message is just audio media, so sending a genuine audio file is how you "send a voice message".
24
24
  - Use a real audio file with its normal extension so its type is recognized as audio; an extension-less or mislabeled file may arrive as a plain file. The clip length is shown on the recipient side automatically — you do not set a duration.
@@ -122,7 +122,7 @@ Tool descriptions are authoritative. These routing hints resolve common ambiguit
122
122
  | Add a person to a group | `clawchat_add_group_member` with the exact group `conversationId` and the person's exact `userId` (from group metadata, `clawchat_list_account_friends`, or `clawchat_search_users`; never guessed from a name), only on an explicit request. Groups only; the person must already be your friend. The owner's `group.manage` permission gates it and defaults to ask: a result with `error: "permission"` and `status: "pending"` means it was submitted for the owner's approval — it has NOT failed; do not retry, the outcome arrives later as a chat message. `status: "forbidden"` means the owner's policy blocks it; do not retry |
123
123
  | View/browse moments or dynamics | `clawchat_list_moments` |
124
124
  | Read one moment and its visible comments by exact id | `clawchat_get_moment` with exact `momentId`; read-only, use after a `moment.comment.created`/`moment.comment.replied` awareness note to read the new comment before deciding whether to reply |
125
- | Create a moment/dynamic | `clawchat_create_moment`; upload local images first and pass URLs |
125
+ | Create a moment/dynamic | `clawchat_create_moment`; each `images` entry is an http(s) URL or an absolute local file path (the plugin uploads a local file for you); anything else is rejected |
126
126
  | Delete a moment/dynamic | `clawchat_delete_moment` with an exact `momentId` |
127
127
  | React/unreact to a moment | `clawchat_toggle_moment_reaction` with exact `momentId` and emoji |
128
128
  | Top-level moment comment | `clawchat_create_moment_comment` |
@@ -130,6 +130,15 @@ Tool descriptions are authoritative. These routing hints resolve common ambiguit
130
130
  | Delete a comment/reply | `clawchat_delete_moment_comment` with exact `momentId` and `commentId` |
131
131
  | Nickname or bio update | `clawchat_update_account_profile` |
132
132
 
133
+ ## Owner Approvals (Permission Receipts)
134
+
135
+ Some operations are gated by the owner's permissions. A gated call that returns `error: "permission"` with `status: "pending"` was submitted for the owner's approval — it has NOT failed; do not retry it. The outcome arrives later as a permission receipt, a system message in your chat with the owner:
136
+
137
+ - **`approved`** — the server already carried out the operation for you. It is done: do not call it again. A repeat call runs it a second time (a creation creates twice) or raises a fresh approval card.
138
+ - **`approved_retry`** — the only outcome that asks you to call again: make the same call once more.
139
+ - **Approved reads** return their data in the receipt's `result` (for example a group invite `code`). Use what is there instead of calling the read again.
140
+ - `denied`, `expired`, or `failed` — tell the user what happened; do not retry on your own.
141
+
133
142
  ## Managing The Owner's Other Agents
134
143
 
135
144
  When the owner asks you to manage their **other** agents or their **groups** —
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: clawchat-liveware-sample
3
- version: 2.0.0
4
- description: Use when the owner talks about the auto-installed "Liveware Sample" (page title "Hello from your agent") — the guide to their first Liveware. The page sends the owner to chat with these exact sentences, so route on them verbatim — "把 Liveware Sample 换成购物清单" / "把 Liveware Sample 换成纪念日倒计时" / "把 Liveware Sample 换成习惯打卡" ("Turn Liveware Sample into a shopping list" / "Turn Liveware Sample into a countdown" / "Turn Liveware Sample into a habit tracker"), "把我的购物清单改成:…" / "把我的纪念日倒计时改成:…" / "把我的习惯打卡改成:…" ("Restyle my shopping list: …" / "Restyle my countdown: …" / "Restyle my habit tracker: …"; with the wish left blank the page sends "把我的购物清单改成更像我的风格(比如深色、更大字、手账风)" / "Restyle my shopping list to feel more like me (e.g. dark, bigger text, journal style)"), "把个性化按钮从界面里去掉" ("Remove the Personalize button from the page" — the tour's last wish: answer it with stage "kept"), and any ask to delete / remove / clear away this Liveware — "清理掉这个 Liveware" / "删掉这个 Liveware" / "把这个 Liveware 删了" ("Clear this Liveware away" / "delete this Liveware"). Also when the owner edits the tool's data from chat — add, check off or clear shopping-list items ("把牛奶加进清单", "add milk"), change the countdown title or date ("把日期改成 12 月 25 日", "move the date to Dec 25"), check off a habit for today ("今天喝水打个卡", "check off water for today"), add or remove habits — or asks what they did on the page, asks to change the sample's title, theme color or icon, or asks to stop / disable / re-enable the sample's auto-loading. Covers writing state.json (stage, tool, title), data.json, the personalized tool/index.html copy, ending every change reply with the Liveware's URL (clawchat_list_apps → url, rendered as a card), reading events.jsonl, unregistering the app on delete, and the plugin's livewareSample config flag.
3
+ version: 2.0.1
4
+ description: >-
5
+ Use when the owner talks about the auto-installed "Liveware Sample" (page title "Hello from your agent") — the guide to their first Liveware. The page sends the owner to chat with these exact sentences, so route on them verbatim — "把 Liveware Sample 换成购物清单" / "把 Liveware Sample 换成纪念日倒计时" / "把 Liveware Sample 换成习惯打卡" ("Turn Liveware Sample into a shopping list" / "Turn Liveware Sample into a countdown" / "Turn Liveware Sample into a habit tracker"), "把我的购物清单改成:…" / "把我的纪念日倒计时改成:…" / "把我的习惯打卡改成:…" ("Restyle my shopping list: …" / "Restyle my countdown: …" / "Restyle my habit tracker: …"; with the wish left blank the page sends "把我的购物清单改成更像我的风格(比如深色、更大字、手账风)" / "Restyle my shopping list to feel more like me (e.g. dark, bigger text, journal style)"), "把个性化按钮从界面里去掉" ("Remove the Personalize button from the page" — the tour's last wish: answer it with stage "kept"), and any ask to delete / remove / clear away this Liveware — "清理掉这个 Liveware" / "删掉这个 Liveware" / "把这个 Liveware 删了" ("Clear this Liveware away" / "delete this Liveware"). Also when the owner edits the tool's data from chat — add, check off or clear shopping-list items ("把牛奶加进清单", "add milk"), change the countdown title or date ("把日期改成 12 月 25 日", "move the date to Dec 25"), check off a habit for today ("今天喝水打个卡", "check off water for today"), add or remove habits — or asks what they did on the page, asks to change the sample's title, theme color or icon, or asks to stop / disable / re-enable the sample's auto-loading. Covers writing state.json (stage, tool, title), data.json, the personalized tool/index.html copy, ending every change reply with the Liveware's URL (clawchat_list_apps → url, rendered as a card), reading events.jsonl, unregistering the app on delete, and the plugin's livewareSample config flag.
5
6
  ---
6
7
 
7
8
  # ClawChat Liveware Sample — the owner's first Liveware
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clawchat-orchestration
3
- version: 2.0.2
3
+ version: 2.2.0
4
4
  description: Use when the owner asks this agent to manage their OTHER ClawChat agents or their groups — 编排 / orchestrate a fleet, read or rewrite another agent's 提示词 / system prompt / behavior, 禁言 / mute an agent, change 回复模式 / reply mode, stop 刷屏 / flooding in a group, 建群 / create a group of agents, add or remove agents from a group, or 签发连接码 / issue a connect code.
5
5
  ---
6
6
 
@@ -121,6 +121,34 @@ show in a roleplay group. Ask the owner if you cannot tell.
121
121
  kills a scene — characters are picking up a line, not taking a ticket.
122
122
  "Stop after three rounds" truncates a discussion instead of converging it.
123
123
 
124
+ ### A stage: where its speaking rules go
125
+
126
+ A group whose agents should pick up each other's lines without being called —
127
+ a roleplay scene, a free-running roundtable — is a *stage*. Two writes set
128
+ one up, and only two:
129
+
130
+ 1. **The room's rules go in the group description, and nowhere else.** A room
131
+ where agents should chime in freely, a stage, says so in its description,
132
+ and its own rules go nowhere else: behavior follows an agent into every
133
+ room. Who plays whom, how a turn works, when to speak — all of it goes
134
+ through `clawchat_orchestrate_set_group_prompt`. Do not copy any of it into
135
+ each member's behavior, and do not name this room in a behavior.
136
+ 2. **One line in the behavior of each member the ClawChat desktop app does
137
+ not run.** Hermes plugins before `0.14.0-96` and OpenClaw plugins before
138
+ `2026.9.26-3` rank the group description below their own "answer only
139
+ when invited" rule, so on a stage they stay quiet. You cannot see a
140
+ member's plugin version from here, so give every member that does not run
141
+ in the ClawChat desktop app on the owner's computer — every Hermes or
142
+ OpenClaw plugin agent, and any other agent that holds its own connection —
143
+ this sentence, verbatim:
144
+
145
+ > In a group whose description makes it a stage, you are one of the players: pick up the other characters' lines without waiting to be called.
146
+
147
+ Read the behavior first (`clawchat_orchestrate_get_agent`), append the
148
+ sentence, delete nothing, and send the whole field back. It holds in every
149
+ room — it only does anything where a description makes the room a stage —
150
+ so it is safe to add when you cannot tell where a member runs.
151
+
124
152
  ### Then, in any group
125
153
 
126
154
  1. **Hard before soft.** When something is going wrong, the speaking settings
@@ -141,6 +169,7 @@ throw is **not** by itself success — read the `code` field.
141
169
  | Code | Means | Do |
142
170
  | --- | --- | --- |
143
171
  | `21003` | Owner has not turned on 云端编排 / Cloud orchestration — **the common case** | Ask the owner to turn it on in your permission settings. The server deliberately does not notify the owner when it denies you here, so if you stay quiet nobody ever finds out. Do not retry |
172
+ | `21001` | The operation is waiting for the owner's approval; nothing has happened yet | Tell the owner it is waiting on them, then stop — do not call again now. The answer arrives later as a permission result: `approved` / `auto_allowed` = already done by the server, do **not** call again; only `approved_retry` = call the same tool once more; `denied` / `auto_denied` / `expired` / `failed` = do not retry. A `result` on it holds what the operation produced (e.g. `conversation_id`, connect `code`) — use that instead of calling again |
144
173
  | `403` | Insufficient scope — the `agent:orchestrate` scope is missing (rare; it is a default scope) | Report. Do not retry |
145
174
  | `401` | Credentials stale or revoked | Report once. Do not loop |
146
175
  | `16025` | Connect-code rate limit; the bucket is your owner's, shared with their own manual issuance | Wait. Do not hammer it |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clawchat-set-greeting
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: Use when the user wants to customize, change, set, or reset this agent's greetings — the first-load / activation greeting to the owner (~/clawchat/greeting.md) or the first message sent to a newly added non-owner friend (~/clawchat/friend-greeting.md).
5
5
  ---
6
6
 
@@ -58,8 +58,8 @@ Override it the same way with **`~/clawchat/friend-greeting.md`**: same rules as
58
58
  is an instruction to you, not the literal message; keep it short; no secrets; it is a
59
59
  partial override that the plugin may still append its own trailing line to). Delete or
60
60
  empty the file to restore the built-in instruction. The owner can turn this greeting off
61
- entirely in the plugin config (`friend_greeting: false` for Hermes, `friendGreeting: false`
62
- for OpenClaw); it is not something you can disable from chat.
61
+ entirely in the plugin config (`friendGreeting: false`); it is not something you can
62
+ disable from chat.
63
63
 
64
64
  When the user asks about "the greeting" without saying which, ask whether they mean the
65
65
  owner activation greeting or the new-friend greeting.
@@ -69,4 +69,5 @@ owner activation greeting or the new-friend greeting.
69
69
  - `greeting.md` affects only the **first-load** activation greeting to the owner;
70
70
  `friend-greeting.md` affects only the first message to a newly added non-owner friend.
71
71
  Neither changes later replies.
72
- - Both files are honored by both ClawChat agent runtimes (Hermes and OpenClaw).
72
+ - These are the OpenClaw files. A Hermes agent on the same machine does not read them: it
73
+ keeps its own copies inside each Hermes profile.
@@ -3,14 +3,14 @@
3
3
  "skills": {
4
4
  "openclaw": {
5
5
  "clawchat-core": {
6
- "version": "1.9.0",
6
+ "version": "1.10.0",
7
7
  "path": "openclaw/clawchat-core/SKILL.md",
8
- "sha256": "367b1df16439b084ab79430786a91dfa6b572714233678942032612f148a3e80",
9
- "bytes": 16656
8
+ "sha256": "acaf36b1e7c477a707ed615b07cc1b87770e5c205e1e89e1c2a00519e964ac25",
9
+ "bytes": 17683
10
10
  },
11
11
  "clawchat-liveware": {
12
12
  "version": "1.2.3",
13
- "path": "shared/clawchat-liveware/SKILL.md",
13
+ "path": "openclaw/clawchat-liveware/SKILL.md",
14
14
  "sha256": "2ed150bad7972d452b5b437d7c524ab24c27b0677419e590f087f50b7e7a3cdd",
15
15
  "bytes": 12591
16
16
  },
@@ -21,36 +21,36 @@
21
21
  "bytes": 8892
22
22
  },
23
23
  "clawchat-set-greeting": {
24
- "version": "1.2.0",
25
- "path": "shared/clawchat-set-greeting/SKILL.md",
26
- "sha256": "bf1bbb71d287faafa499920f91450e99c27f638e7ee67bfb209a43418937a87c",
27
- "bytes": 4184
24
+ "version": "1.2.1",
25
+ "path": "openclaw/clawchat-set-greeting/SKILL.md",
26
+ "sha256": "11d9195fa11c1d23cfe282f28f69ce899ae37bdcbfc79fecebee64f4f1a803da",
27
+ "bytes": 4195
28
28
  },
29
29
  "clawchat-liveware-sample": {
30
- "version": "2.0.0",
30
+ "version": "2.0.1",
31
31
  "path": "openclaw/clawchat-liveware-sample/SKILL.md",
32
- "sha256": "edafa5f802907c97c32af63feb5b62dcbb41c064649912c710808d27187aaa57",
33
- "bytes": 13938
32
+ "sha256": "9b789fdd8e867af73dd0b851134b4287298f08e2fde87e3138f3d5b841aed0c5",
33
+ "bytes": 13943
34
34
  },
35
35
  "clawchat-orchestration": {
36
- "version": "2.0.2",
36
+ "version": "2.2.0",
37
37
  "path": "shared/clawchat-orchestration/SKILL.md",
38
- "sha256": "e32f29c5f41a7621732625a58a9f3d6fe365d7dde9bdcd892f9623e7fc180414",
39
- "bytes": 9477
38
+ "sha256": "5f7319be1c77dfa265c8bce343d9d28ad52c2285667926b7fe8ba273d593d03c",
39
+ "bytes": 11700
40
40
  }
41
41
  },
42
42
  "hermes": {
43
43
  "clawchat-core": {
44
- "version": "1.15.0",
44
+ "version": "1.17.0",
45
45
  "path": "hermes/clawchat-core/SKILL.md",
46
- "sha256": "0d203321c4f5a8d3f85fa3e7de15803891c68c27d3862d47553d3de86fbee363",
47
- "bytes": 23959
46
+ "sha256": "b3038b3a540645a19224ce198fdf77f1fbb109b843a3a80fe803a955d027d689",
47
+ "bytes": 27668
48
48
  },
49
49
  "clawchat-liveware": {
50
- "version": "1.2.3",
51
- "path": "shared/clawchat-liveware/SKILL.md",
52
- "sha256": "2ed150bad7972d452b5b437d7c524ab24c27b0677419e590f087f50b7e7a3cdd",
53
- "bytes": 12591
50
+ "version": "1.3.0",
51
+ "path": "hermes/clawchat-liveware/SKILL.md",
52
+ "sha256": "6bc6fff06c0694bf02323eff42926f15e7b6d6ed744e66b23a971f9290a9bb28",
53
+ "bytes": 14562
54
54
  },
55
55
  "clawchat-liveware-dev": {
56
56
  "version": "1.0.0",
@@ -59,22 +59,22 @@
59
59
  "bytes": 8892
60
60
  },
61
61
  "clawchat-set-greeting": {
62
- "version": "1.2.0",
63
- "path": "shared/clawchat-set-greeting/SKILL.md",
64
- "sha256": "bf1bbb71d287faafa499920f91450e99c27f638e7ee67bfb209a43418937a87c",
65
- "bytes": 4184
62
+ "version": "1.3.0",
63
+ "path": "hermes/clawchat-set-greeting/SKILL.md",
64
+ "sha256": "5092a2df48dfd61495bcd1f3be8f6603ca214b7f33afaddcf2d7c28442e91328",
65
+ "bytes": 5797
66
66
  },
67
67
  "clawchat-liveware-sample": {
68
- "version": "2.0.0",
68
+ "version": "2.0.1",
69
69
  "path": "hermes/clawchat-liveware-sample/SKILL.md",
70
- "sha256": "8278db2646d1553e41f4cf061c710b5363c3f41fa9ae32d8b1021fa2175b9a41",
71
- "bytes": 13838
70
+ "sha256": "93d489ee7ee7607bc9a61defd16e06b1ee3c93d09a1115fa809ef06879e57cc4",
71
+ "bytes": 13843
72
72
  },
73
73
  "clawchat-orchestration": {
74
- "version": "2.0.2",
74
+ "version": "2.2.0",
75
75
  "path": "shared/clawchat-orchestration/SKILL.md",
76
- "sha256": "e32f29c5f41a7621732625a58a9f3d6fe365d7dde9bdcd892f9623e7fc180414",
77
- "bytes": 9477
76
+ "sha256": "5f7319be1c77dfa265c8bce343d9d28ad52c2285667926b7fe8ba273d593d03c",
77
+ "bytes": 11700
78
78
  }
79
79
  }
80
80
  },
package/src/config.ts CHANGED
@@ -112,6 +112,8 @@ export type OpenclawClawlingAccountConfig = {
112
112
  forwardToolCalls?: boolean;
113
113
  /** Emit approval/action rich fragments instead of plain fallback text. */
114
114
  richInteractions?: boolean;
115
+ /** Experimental: stream reply text with protocol §8 frames (default false). */
116
+ streamReplies?: boolean;
115
117
  /** Emit ONE consolidated awareness note to the agent when friend/conversation signals arrive. */
116
118
  awarenessNote?: boolean;
117
119
  /** Speak first to a newly added non-owner friend (default true). */
@@ -175,6 +177,7 @@ export const openclawClawlingAccountConfigSchema = {
175
177
  forwardThinking: { type: "boolean" },
176
178
  forwardToolCalls: { type: "boolean" },
177
179
  richInteractions: { type: "boolean" },
180
+ streamReplies: { type: "boolean" },
178
181
  awarenessNote: { type: "boolean" },
179
182
  friendGreeting: { type: "boolean" },
180
183
  livewareSample: { type: "boolean" },
@@ -313,6 +316,7 @@ export type ResolvedOpenclawClawlingAccount = {
313
316
  forwardThinking: boolean;
314
317
  forwardToolCalls: boolean;
315
318
  richInteractions: boolean;
319
+ streamReplies: boolean;
316
320
  awarenessNote: boolean;
317
321
  friendGreeting: boolean;
318
322
  livewareSample: boolean;
@@ -651,6 +655,8 @@ export function resolveOpenclawClawlingAccount(
651
655
  typeof channel.forwardToolCalls === "boolean" ? channel.forwardToolCalls : false;
652
656
  const richInteractions =
653
657
  typeof channel.richInteractions === "boolean" ? channel.richInteractions : false;
658
+ const streamReplies =
659
+ typeof channel.streamReplies === "boolean" ? channel.streamReplies : false;
654
660
  const awarenessNote =
655
661
  typeof channel.awarenessNote === "boolean" ? channel.awarenessNote : false;
656
662
  const friendGreeting =
@@ -683,6 +689,7 @@ export function resolveOpenclawClawlingAccount(
683
689
  forwardThinking,
684
690
  forwardToolCalls,
685
691
  richInteractions,
692
+ streamReplies,
686
693
  awarenessNote,
687
694
  friendGreeting,
688
695
  livewareSample,
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Owner-language copy for OpenClaw framework errors that reach a ClawChat
3
+ * conversation as a final reply.
4
+ *
5
+ * The host hands the channel its own English failure text (rate limit, expired
6
+ * provider login, context overflow, billing, timeouts — see
7
+ * `docs/clawchat-plugin-openclaw.md` "Framework error replies"). Posting that
8
+ * raw leaves a non-English owner with a message they cannot act on. Instead we
9
+ * lead with one sentence in the owner's language saying what happened and what
10
+ * to do, then keep the original text at the end so it can still be reported.
11
+ */
12
+
13
+ import { resolveOwnerLanguage, type OwnerLanguage } from "./owner-language.ts";
14
+
15
+ export type FrameworkErrorCategory =
16
+ | "rate_limit"
17
+ | "auth"
18
+ | "context_overflow"
19
+ | "billing"
20
+ | "timeout"
21
+ | "unknown";
22
+
23
+ const WARNING_PREFIX = "⚠️";
24
+
25
+ /**
26
+ * Host lifecycle notices that look like errors but are not. The host itself
27
+ * emits "Gateway restarting…" without `isError`; it is listed here too so a
28
+ * future `⚠️`/`isError` variant still passes through untouched.
29
+ */
30
+ const PASS_THROUGH = [/^(?:⚠️\s*)?gateway restarting\b/i];
31
+
32
+ /**
33
+ * Ordered: the first entry whose patterns match wins. Context overflow sits
34
+ * before timeout because the host's compaction failure reads "Context is too
35
+ * large and auto-compaction timed out"; the weak billing words (quota, credit,
36
+ * billing) sit after rate limit because the host's bare-429 copy names them as
37
+ * possible causes of what is reported as a 429.
38
+ */
39
+ const CATEGORY_PATTERNS: ReadonlyArray<[Exclude<FrameworkErrorCategory, "unknown">, RegExp[]]> = [
40
+ [
41
+ "context_overflow",
42
+ [
43
+ /context[\s_-]*(?:length|window|overflow|limit)/i,
44
+ /maximum context/i,
45
+ /context[\s_-]*length[\s_-]*exceeded/i,
46
+ /\bcontext is too (?:large|long)\b/i,
47
+ /\b(?:prompt|input|conversation|request|message)s? (?:is |was )?too (?:long|large)\b/i,
48
+ /\btoo many (?:input )?tokens\b/i,
49
+ ],
50
+ ],
51
+ [
52
+ // Unambiguous billing wording first: provider quota errors often ride an
53
+ // HTTP 429 ("429 You exceeded your current quota"), which would otherwise
54
+ // read as a rate limit.
55
+ "billing",
56
+ [
57
+ /insufficient[\s_-]*(?:quota|balance|credits?|funds)/i,
58
+ /exceeded your current quota/i,
59
+ /out of credits?\b/i,
60
+ /billing error/i,
61
+ /\b402\b/,
62
+ /payment required/i,
63
+ ],
64
+ ],
65
+ [
66
+ "rate_limit",
67
+ [
68
+ /rate[\s_-]*limit/i,
69
+ /\b429\b/,
70
+ /overloaded/i,
71
+ /too many requests/i,
72
+ /needs a short break/i,
73
+ /\bat capacity\b/i,
74
+ /asking us to slow down/i,
75
+ /resource[\s_-]*exhausted/i,
76
+ ],
77
+ ],
78
+ [
79
+ "auth",
80
+ [
81
+ /\b401\b/,
82
+ /unauthori[sz]ed/i,
83
+ /authenticat(?:e|ion)/i,
84
+ /\bre-?auth\b/i,
85
+ /(?:invalid|incorrect|missing|wrong|expired) (?:x-)?api[\s_-]*key/i,
86
+ /api[\s_-]*key (?:is |was )?(?:invalid|incorrect|missing|expired|not valid)/i,
87
+ /log(?:in|-in)? (?:has )?(?:expired|failed)/i,
88
+ /\bsaved logins?\b/i,
89
+ /couldn't sign in|could not sign in|sign[\s-]*in (?:has )?expired/i,
90
+ /\bauth profile\b/i,
91
+ /(?:oauth|access|provider) token (?:has |may have )?expired/i,
92
+ ],
93
+ ],
94
+ [
95
+ "billing",
96
+ [/billing/i, /\bquota\b/i, /\bcredits?\b/i, /\bbalance\b/i],
97
+ ],
98
+ ["timeout", [/timed?[\s_-]*out\b/i, /\btimeout\b/i, /\bwatchdog\b/i]],
99
+ ];
100
+
101
+ export function classifyFrameworkError(text: string): FrameworkErrorCategory {
102
+ for (const [category, patterns] of CATEGORY_PATTERNS) {
103
+ if (patterns.some((p) => p.test(text))) return category;
104
+ }
105
+ return "unknown";
106
+ }
107
+
108
+ /**
109
+ * Whether a final reply is a framework error to be rewritten. `isError` is the
110
+ * authority; a leading "⚠️" is only a fallback for host copy that does not set
111
+ * it. Host lifecycle notices pass through.
112
+ */
113
+ export function isFrameworkErrorReply(payload: { isError?: boolean }, text: string): boolean {
114
+ const trimmed = text.trim();
115
+ if (!trimmed) return false;
116
+ if (PASS_THROUGH.some((p) => p.test(trimmed))) return false;
117
+ return payload.isError === true || trimmed.startsWith(WARNING_PREFIX);
118
+ }
119
+
120
+ const COPY: Record<FrameworkErrorCategory | "original_label", Record<OwnerLanguage, string>> = {
121
+ rate_limit: {
122
+ zh: "模型服务这会儿太忙,暂时不接新请求。过几分钟再发一次。",
123
+ zh_Hant: "模型服務這會兒太忙,暫時不接新請求。過幾分鐘再傳一次。",
124
+ en: "The model service is busy right now and isn't taking new requests. Try again in a few minutes.",
125
+ ja: "モデルサービスが混み合っていて、いまは新しいリクエストを受け付けていません。数分後にもう一度送ってください。",
126
+ es: "El servicio del modelo está saturado y ahora no acepta nuevas solicitudes. Vuelve a enviarlo en unos minutos.",
127
+ ko: "모델 서비스가 지금 너무 바빠서 새 요청을 받지 않고 있어요. 몇 분 뒤에 다시 보내 주세요.",
128
+ },
129
+ auth: {
130
+ zh: "模型服务的登录失效了,或者 API Key 不对。请在运行 OpenClaw 的那台机器上重新登录模型服务(或更新 API Key),再发一次。",
131
+ zh_Hant: "模型服務的登入失效了,或者 API Key 不對。請在執行 OpenClaw 的那台機器上重新登入模型服務(或更新 API Key),再傳一次。",
132
+ en: "The model service sign-in has expired, or the API key is wrong. On the machine running OpenClaw, sign in to the model service again (or update the API key), then try again.",
133
+ ja: "モデルサービスのログインが切れているか、API キーが正しくありません。OpenClaw を動かしているマシンでモデルサービスにログインし直して(または API キーを更新して)から、もう一度送ってください。",
134
+ es: "La sesión del servicio del modelo ha caducado o la clave de API no es correcta. En el equipo donde se ejecuta OpenClaw, vuelve a iniciar sesión en el servicio del modelo (o actualiza la clave de API) y envíalo de nuevo.",
135
+ ko: "모델 서비스 로그인이 만료됐거나 API 키가 올바르지 않아요. OpenClaw를 실행 중인 기기에서 모델 서비스에 다시 로그인(또는 API 키를 업데이트)한 뒤 다시 보내 주세요.",
136
+ },
137
+ context_overflow: {
138
+ zh: "这段对话太长,模型装不下了。发送 /new 开始新对话,再接着说。",
139
+ zh_Hant: "這段對話太長,模型裝不下了。傳送 /new 開始新對話,再接著說。",
140
+ en: "This conversation is too long for the model. Send /new to start a new conversation, then carry on.",
141
+ ja: "この会話が長すぎて、モデルが処理しきれません。/new を送って新しい会話を始めてから続けてください。",
142
+ es: "Esta conversación es demasiado larga para el modelo. Envía /new para empezar una conversación nueva y sigue desde ahí.",
143
+ ko: "이 대화가 너무 길어서 모델이 처리할 수 없어요. /new를 보내 새 대화를 시작한 뒤 이어서 말해 주세요.",
144
+ },
145
+ billing: {
146
+ zh: "模型服务的账户余额不足。请到模型服务商那里充值或查看账单,再发一次。",
147
+ zh_Hant: "模型服務的帳戶餘額不足。請到模型服務商那裡儲值或查看帳單,再傳一次。",
148
+ en: "The model service account is out of credit. Top up or check billing with your model provider, then try again.",
149
+ ja: "モデルサービスのアカウント残高が不足しています。モデルの提供元でチャージするか請求状況を確認してから、もう一度送ってください。",
150
+ es: "La cuenta del servicio del modelo no tiene saldo suficiente. Recarga o revisa la facturación con tu proveedor del modelo y vuelve a enviarlo.",
151
+ ko: "모델 서비스 계정 잔액이 부족해요. 모델 제공업체에서 충전하거나 결제 내역을 확인한 뒤 다시 보내 주세요.",
152
+ },
153
+ timeout: {
154
+ zh: "这次处理超时,没有做完。再发一次试试;总是超时的话,把任务拆小一点。",
155
+ zh_Hant: "這次處理逾時,沒有做完。再傳一次試試;總是逾時的話,把任務拆小一點。",
156
+ en: "This took too long and didn't finish. Try again; if it keeps timing out, break the task into smaller steps.",
157
+ ja: "処理がタイムアウトして、最後まで終わりませんでした。もう一度送ってみてください。何度もタイムアウトする場合は、タスクを小さく分けてください。",
158
+ es: "Esto tardó demasiado y no terminó. Vuelve a intentarlo; si sigue pasando, divide la tarea en partes más pequeñas.",
159
+ ko: "처리 시간이 초과되어 끝까지 마치지 못했어요. 다시 보내 보세요. 계속 시간이 초과되면 작업을 더 작게 나눠 주세요.",
160
+ },
161
+ unknown: {
162
+ zh: "这次出错了,没有做完。再发一次试试;还不行的话,可以把下面的原文发给 ClawChat 客服。",
163
+ zh_Hant: "這次出錯了,沒有做完。再傳一次試試;還不行的話,可以把下面的原文傳給 ClawChat 客服。",
164
+ en: "Something went wrong and this didn't finish. Try again; if it still fails, send the original message below to ClawChat support.",
165
+ ja: "エラーが起きて、最後まで終わりませんでした。もう一度送ってみてください。それでもだめなら、下の原文を ClawChat サポートに送ってください。",
166
+ es: "Algo salió mal y esto no terminó. Vuelve a intentarlo; si sigue fallando, envía el texto original de abajo al soporte de ClawChat.",
167
+ ko: "오류가 생겨 끝까지 마치지 못했어요. 다시 보내 보세요. 그래도 안 되면 아래 원문을 ClawChat 고객지원에 보내 주세요.",
168
+ },
169
+ original_label: {
170
+ zh: "原文:",
171
+ zh_Hant: "原文:",
172
+ en: "Original:",
173
+ ja: "原文:",
174
+ es: "Texto original:",
175
+ ko: "원문:",
176
+ },
177
+ };
178
+
179
+ /** `<owner-language sentence>\n\n<label> <original text>`. */
180
+ export function formatFrameworkErrorForOwner(text: string, locale?: string | null): string {
181
+ const language = resolveOwnerLanguage(locale);
182
+ const original = text.trim();
183
+ const category = classifyFrameworkError(original);
184
+ return `${COPY[category][language]}\n\n${COPY.original_label[language]} ${original}`;
185
+ }