@clawrent/openclaw-channel 0.3.2 → 0.3.4

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
@@ -1,149 +1,208 @@
1
1
  # @clawrent/openclaw-channel
2
2
 
3
- OpenClaw **channel plugin** that turns ClawRent rental sessions into native
4
- OpenClaw conversations, so a local **ClawRent provider agent** can answer rental
5
- requests autonomously using its own model and identity.
3
+ OpenClaw **channel plugin** that turns ClawRent rental sessions into native OpenClaw conversations, so a local **ClawRent provider agent** can answer rental requests autonomously using its own model and identity.
6
4
 
7
- > 官方维护的通用 ClawRent 频道模板。由 ClawRent 官方基于 PinkBo 移交的原型接手开发
8
- > (接管记录见 [CLAWRENT_HANDOFF.md](CLAWRENT_HANDOFF.md) 的 R1–R5 需求清单)。
5
+ OpenClaw **频道插件**:把 ClawRent 租赁会话桥接成 OpenClaw 原生对话,让本地 **ClawRent provider 智能体**用自有模型与身份自动应答租户请求。
9
6
 
10
- ## 它做什么
7
+ > 官方维护的通用 ClawRent 频道模板。由 ClawRent 官方基于 PinkBo 移交的原型接手开发(接管记录见 [CLAWRENT_HANDOFF.md](CLAWRENT_HANDOFF.md) 的 R1–R5 需求清单)。
8
+ >
9
+ > Officially-maintained generic ClawRent channel template. ClawRent took over the prototype handed off by PinkBo (see [CLAWRENT_HANDOFF.md](CLAWRENT_HANDOFF.md) for the handoff record and R1–R5 requirements).
10
+
11
+ ## What it does / 它做什么
12
+
13
+ Bridges a ClawRent rental session into a native OpenClaw conversation channel:
11
14
 
12
15
  把一个 ClawRent 租赁会话桥接成一个 OpenClaw 原生对话频道:
13
16
 
17
+ - Receives tenant messages via `@clawrent/provider`'s `ProviderClient` **WS push** (no CLI daemon / polling needed).
18
+ - Dangerous instructions are intercepted by guardrails and never drive the agent.
19
+ - Drives the OpenClaw agent for one inbound turn, then **sends the model reply back** to the ClawRent session.
20
+ - Enforces approval policy at the `onPendingApproval` end-side gate (guardrails take highest priority: dangerous categories always go to manual review).
14
21
  - 用 `@clawrent/provider` 的 `ProviderClient` **WS push 接收**租户消息(无需 CLI daemon / 轮询)。
15
22
  - 危险指令走护栏拦截,不驱动 agent。
16
23
  - 驱动 OpenClaw agent 跑一个 inbound turn,把模型回复**回发**到 ClawRent 会话。
17
24
  - 在 `onPendingApproval` 端侧门执行批准策略(护栏最高优先级:危险类永远转人工)。
18
25
 
19
- ## 数据流
26
+ ## Data flow / 数据流
20
27
 
21
28
  ```
22
- ClawRent WS 推送
29
+ ClawRent WS push
23
30
  → ProviderClient.onMessage(session, message) [@clawrent/provider]
24
- → extractDialogue() payload.content / type
25
- → checkGuardrails() 护栏判定(危险拦截,不驱动 agent
31
+ → extractDialogue() read payload.content / type
32
+ → checkGuardrails() guardrail verdict (dangerous block, don't drive agent)
26
33
  → runChannelInboundEvent({ channel:"clawrent", raw, adapter }) [openclaw/plugin-sdk]
27
34
  adapter.resolveTurn → { ctxPayload, recordInboundSession, runDispatch }
28
- runDispatch → dispatchReplyWithBufferedBlockDispatcher(跑 agent
29
- → providerClient.send(sessionId, { type, payload:{content} }) // 回发 ClawRent
35
+ runDispatch → dispatchReplyWithBufferedBlockDispatcher (run agent)
36
+ → providerClient.send(sessionId, { type, payload:{content} }) // reply back to ClawRent
30
37
  ```
31
38
 
32
- ## 目录结构
39
+ ## Directory structure / 目录结构
33
40
 
34
41
  ```
35
42
  openclaw-channel/
36
- ├─ openclaw.plugin.json # 插件 manifest(严格 schema,含 channelConfigs.clawrent.schema
37
- ├─ package.json # openclaw 扩展入口声明
38
- ├─ tsconfig.json # tsc 配置(moduleResolution: Bundler,靠 node_modules 解析)
43
+ ├─ openclaw.plugin.json # plugin manifest (strict schema, includes channelConfigs.clawrent.schema)
44
+ ├─ package.json # openclaw extension entry declaration
45
+ ├─ tsconfig.json # tsc config (moduleResolution: Bundler, resolves via node_modules)
39
46
  ├─ src/
40
- │ ├─ index.ts # 频道入口:defineChannelPluginEntry + createChatChannelPlugin + startProvider
41
- │ ├─ provider.ts # ProviderClient 驱动无人值守 provideronPendingApproval 批准策略 + onMessage 对话路由
42
- │ ├─ guardrails.ts # 护栏判定(内置最小示例 + guardrailsFile 外置追加)
43
- │ └─ setup/setup.ts # 配置 setup 入口(testConnectiontoken 校验)
47
+ │ ├─ index.ts # channel entry: defineChannelPluginEntry + createChatChannelPlugin + startProvider
48
+ │ ├─ provider.ts # ProviderClient driving the unattended provider: onPendingApproval policy + onMessage routing
49
+ │ ├─ guardrails.ts # guardrail verdict (built-in minimal example + external guardrailsFile)
50
+ │ └─ setup/setup.ts # config setup entry (testConnection: token validation)
44
51
  ├─ docs/
45
- │ └─ openclaw-sdk-notes.md # OpenClaw SDK 踩坑笔记(文档 vs 实际,开发必读)
46
- └─ CLAWRENT_HANDOFF.md # 移交说明 + R1–R5 需求清单
52
+ │ └─ openclaw-sdk-notes.md # OpenClaw SDK gotchas notes (docs vs reality, required reading)
53
+ └─ CLAWRENT_HANDOFF.md # handoff notes + R1–R5 requirements list
47
54
  ```
48
55
 
49
- ## 构建 / 校验
56
+ ## Build / verify / 构建 / 校验
50
57
 
51
58
  ```bash
52
- npm install # openclawSDK 类型)+ @clawrent/provider
59
+ npm install # pulls openclaw (SDK types) + @clawrent/provider
53
60
  npm run build # tsc -p tsconfig.json → dist/
54
- npm run typecheck # 不产物的类型校验
61
+ npm run typecheck # type-only check, no output
55
62
  ```
56
63
 
64
+ After `npm install`, `openclaw` lands in the local `node_modules`; `moduleResolution: "Bundler"` resolves
65
+ `openclaw/plugin-sdk/*` via its `exports` allowlist, **independent of any globally-installed openclaw path**.
66
+
57
67
  `npm install` 后 `openclaw` 进本地 `node_modules`,`moduleResolution: "Bundler"` 经其
58
68
  `exports` 白名单解析 `openclaw/plugin-sdk/*`,**不依赖全局安装的 openclaw 路径**。
59
69
 
60
- ## 配置
70
+ ## Configuration / 配置
71
+
72
+ `plugins.entries.clawrent.config` fields:
61
73
 
62
74
  `plugins.entries.clawrent.config` 字段:
63
75
 
64
- | 字段 | 必填 | 说明 |
76
+ | Field / 字段 | Required / 必填 | Description / 说明 |
65
77
  |---|---|---|
66
- | `token` | 否* | ClawRent agent token。缺失时回退读 `~/.clawrent/config.json` 的 `token`/`agentToken`,避免密钥写入 openclaw.json |
67
- | `apiBaseUrl` | 否 | ClawRent API 地址,默认 `https://clawrent.cloud` |
68
- | `wsUrl` | 否 | ClawRent WebSocket 地址 |
69
- | `agentId` | 否 | ClawRent agent id(UUID),留空由 token 自动解析 |
70
- | `autoApproveSessions` | 否 | 端侧自动批准开关(**仅当会话已是 `pending_approval` 时生效**,与平台 `approvalMode` 是两层,详见 [approval-modes.md](docs/approval-modes.md))。`true`(默认)= SDK 自动批准挂起会话(不跑批准级护栏;**消息级护栏始终生效**);`false` = 全部转人工 |
71
- | `guardrailsFile` | 否 | 外置护栏策略文件(每行 `/regex/ \|\| 原因`,`#` 注释),规则追加在内置护栏之后 |
72
- | `cursorPath` | 否 | 消息游标存储路径,默认 `~/.clawrent/openclaw-provider-cursor.json` |
73
-
78
+ | `token` | No* / 否* | ClawRent agent token. Falls back to `~/.clawrent/config.json`'s `token`/`agentToken` if absent, to keep secrets out of openclaw.json. / ClawRent agent token。缺失时回退读 `~/.clawrent/config.json` 的 `token`/`agentToken`,避免密钥写入 openclaw.json |
79
+ | `apiBaseUrl` | No / 否 | ClawRent API base, default `https://clawrent.cloud`. / ClawRent API 地址,默认 `https://clawrent.cloud`。 |
80
+ | `wsUrl` | No / 否 | ClawRent WebSocket URL. / ClawRent WebSocket 地址。 |
81
+ | `agentId` | No / 否 | ClawRent agent id (UUID); auto-resolved from token if omitted. / ClawRent agent id(UUID),留空由 token 自动解析。 |
82
+ | `autoApproveSessions` | No / 否 | End-side auto-approve toggle (**only effective when a session is `pending_approval`**; a separate layer from the platform `approvalMode`, see [approval-modes.md](docs/approval-modes.md)). `true` (default) = SDK auto-approves pending sessions (no approval-level guardrail run; **message-level guardrails always apply**); `false` = everything goes to manual review. / 端侧自动批准开关(**仅当会话已是 `pending_approval` 时生效**,与平台 `approvalMode` 是两层,详见 [approval-modes.md](docs/approval-modes.md))。`true`(默认)= SDK 自动批准挂起会话(不跑批准级护栏;**消息级护栏始终生效**);`false` = 全部转人工。 |
83
+ | `guardrailsFile` | No / 否 | External guardrail policy file (`/regex/ \|\| reason` per line, `#` comments), rules appended after the built-ins. / 外置护栏策略文件(每行 `/regex/ \|\| 原因`,`#` 注释),规则追加在内置护栏之后。 |
84
+ | `cursorPath` | No / 否 | Message cursor storage path, default `~/.clawrent/openclaw-provider-cursor.json`. / 消息游标存储路径,默认 `~/.clawrent/openclaw-provider-cursor.json`。 |
85
+
86
+ \* The token must exist in either config or `~/.clawrent/config.json`, otherwise the channel is not activated (warn only).
74
87
  \* token 必须在 config 或 `~/.clawrent/config.json` 二者之一中存在,否则频道不激活(仅 warn)。
75
88
 
76
- ### openclaw.json 示例
77
-
78
- ```json
79
- "plugins": {
80
- "entries": {
81
- "clawrent": {
82
- "enabled": true,
83
- "config": {
84
- "apiBaseUrl": "https://clawrent.cloud",
85
- "agentId": "019f35a8-...",
86
- "autoApproveSessions": true,
87
- "guardrailsFile": "/path/to/clawrent-guardrails.md"
89
+ ### openclaw.json example / openclaw.json 示例
90
+
91
+ ```jsonc
92
+ {
93
+ "plugins": {
94
+ "entries": {
95
+ "clawrent": {
96
+ "enabled": true,
97
+ "config": {
98
+ "apiBaseUrl": "https://clawrent.cloud",
99
+ "agentId": "019f35a8-...",
100
+ "autoApproveSessions": true,
101
+ "guardrailsFile": "/path/to/clawrent-guardrails.md"
102
+ }
88
103
  }
89
104
  }
105
+ },
106
+ "channels": {
107
+ "clawrent": {
108
+ "agentId": "019f35a8-...",
109
+ "autoApproveSessions": true,
110
+ "guardrailsFile": "/path/to/clawrent-guardrails.md"
111
+ }
90
112
  }
91
113
  }
92
114
  ```
93
115
 
94
- ### 护栏(guardrailsFile)
116
+ > `plugins.entries.clawrent.config` is what the plugin reads at runtime; `channels.clawrent` is for OpenClaw's channel-instance detection (channel-triggered loading needs it to fire). **Configure both blocks.** Put the token in `~/.clawrent/config.json`, not in openclaw.json.
117
+ >
118
+ > `plugins.entries.clawrent.config` 是 plugin 运行时读的配置;`channels.clawrent` 是 OpenClaw channel 实例检测用的(channel 触发式加载需要它才触发)。**两块都要配**。token 放 `~/.clawrent/config.json`,不写进 openclaw.json。
119
+
120
+ ### Guardrails (guardrailsFile) / 护栏(guardrailsFile)
121
+
122
+ External guardrail file format: `/regex/ || reason` per line, `#` for comments, case-insensitive. Rules are **appended** after the built-ins (the built-ins always intercept `instruction.exec` / `read_file` / `write_file` and cannot be disabled). A message that hits a guardrail does not drive the agent; it replies with a "needs manual review" notice and routes to human review.
95
123
 
96
124
  外置护栏文件格式:每行 `/正则/ || 原因`,`#` 开头为注释,正则大小写不敏感。规则**追加**在内置护栏之后(内置始终拦截 `instruction.exec` / `read_file` / `write_file`,不可关闭)。命中护栏的消息不驱动 agent,直接回「需人工介入」并转人工。
97
125
 
126
+ See [guardrails.example.md](guardrails.example.md) for a full example. Recommended minimum baseline covers at least: command execution, file read/write, delete/destruction, data exfiltration, payment/billing, publish/activate, credential harvesting, and guardrail-bypass prompts (category reference: [R3-GUARDRAILS-MATERIAL.md](R3-GUARDRAILS-MATERIAL.md)).
127
+
98
128
  完整示例见 [guardrails.example.md](guardrails.example.md)。建议最低护栏基线至少覆盖:命令执行、文件读写、删除/破坏、数据外发、付费/账单、发布/激活、凭据索取、绕过护栏的 prompt(类别参考 [R3-GUARDRAILS-MATERIAL.md](R3-GUARDRAILS-MATERIAL.md))。
99
129
 
130
+ > Guardrail policy currently lives entirely end-side (built-in + `guardrailsFile`). "Platform-side authoritative delivery" is the R3 goal — at that point `guardrailsFile` demotes to local override/append.
131
+ >
100
132
  > 护栏策略目前完全在端侧(内置 + `guardrailsFile`)。「平台侧权威下发」是 R3 的目标——届时 `guardrailsFile` 降为本地覆盖/追加。
101
133
 
102
- ## 启用
134
+ ## Enable / 启用
135
+
136
+ 1. `openclaw plugins install @clawrent/openclaw-channel` (npm, recommended). For development, `openclaw plugins install --link <this dir>`.
137
+ 2. Configure two blocks in `~/.openclaw/openclaw.json`: `plugins.entries.clawrent.config` (read at runtime) + `channels.clawrent` (channel-instance detection), see the example above; put the token in `~/.clawrent/config.json`.
138
+ 3. Restart the Gateway → the plugin loads via **channel-triggered loading** (`onStartup:false`) → provider WS comes online → auto-accepts sessions.
139
+
140
+ 1. `openclaw plugins install @clawrent/openclaw-channel`(npm,推荐)。开发可用 `openclaw plugins install --link <本目录>`。
141
+ 2. 在 `~/.openclaw/openclaw.json` 配置两块:`plugins.entries.clawrent.config`(plugin 运行时读)+ `channels.clawrent`(channel 实例检测),见上方示例;token 放 `~/.clawrent/config.json`。
142
+ 3. 重启 Gateway → plugin 走 **channel 触发式加载**(`onStartup:false`)→ provider WS 上线 → 自动接单。
103
143
 
104
- 1. `openclaw plugins install clawhub:@clawrent/openclaw-channel`(或 `--link <本目录>` 开发)。
105
- 2. 在 `plugins.entries.clawrent.config` 填配置(token / apiBaseUrl / agentId / autoApproveSessions / guardrailsFile,见上方示例;token 可放 `~/.clawrent/config.json`)。
106
- 3. 重启 Gateway plugin `onStartup:true` 在启动时 **full-mode 加载** → `registerFull` 执行 provider WS 上线 → `openclaw channels status` 应显示 running。
144
+ > ⚠️ **Do NOT add `plugins.allow: ["clawrent"]`.** In testing, adding `allow` switches the load path to strict mode and actually *blocks* `registerFull` from executing (hit in 0.3.1). Staying in auto-load (no `allow`) is the config that works in 0.3.2.
145
+ >
146
+ > ⚠️ **不要加 `plugins.allow: ["clawrent"]`**。实测加 `allow` 会把加载路径切到严格模式,反而阻止 `registerFull` 执行(0.3.1 撞过)。保持 auto-load(无 allow)是 0.3.2 跑通的配置。
107
147
 
108
- > ⚠️ **v0.2.6 修复(activation)**:≤0.2.5 manifest `onStartup:false` 导致 plugin discovery mode 加载、`registerFull` **不执行** channel not-running(PinkBo 根因分析证实)。**升级到 0.2.6**(新版本刷 install record onStartup 快照)。改磁盘 manifest + `registry --refresh` 不够(install record 缓存了旧值)。
148
+ > ⚠️ **`activation.onStartup` must be `false`** (this is the default; do not change it). OpenClaw 2026.7.1's startup validation does not recognize the export shape produced by `defineChannelPluginEntry` (reports `missing register/activate`); `onStartup:true` hits this loader bug and the plugin fails to load. `onStartup:false` takes the channel-triggered path and bypasses it (this is exactly why 0.1.0 worked before handoff; flipping it to `true` in 0.2.6–0.3.1 was the regression). After changing `onStartup` in the manifest you **must reinstall** (the install record caches the old onStartup snapshot; `registry --refresh` is not enough). See [docs/openclaw-sdk-notes.md](docs/openclaw-sdk-notes.md).
149
+ >
150
+ > ⚠️ **`activation.onStartup` 必须为 `false`**(默认即如此,勿改)。OpenClaw 2026.7.1 的 startup validation 不认 `defineChannelPluginEntry` 产出的导出形态(报 `missing register/activate`),`onStartup:true` 会撞这个 loader bug 导致 plugin 不加载。`onStartup:false` 走 channel 触发式路径绕开它(这正是 0.1.0 移交前能跑的原因;0.2.6–0.3.1 改成 true 是回归元凶)。改 manifest 的 onStartup 后**必须 reinstall**(install record 缓存了旧 onStartup 快照,`registry --refresh` 不够)。详见 [docs/openclaw-sdk-notes.md](docs/openclaw-sdk-notes.md)。
109
151
 
110
- > `channels.clawrent` 块是**可选的**(给 OpenClaw channel 实例检测 / status 用;plugin 运行时读的是 `plugins.entries.clawrent.config`,**不读** `channels.clawrent`)。如配,不加 `enabled` 字段(channel schema `additionalProperties:false`)。
152
+ > ⚠️ **`channels status --deep` may falsely report `not-running` / `disabled` when there is no `plugins.allow`** (an OpenClaw CLI display-layer bug, not a plugin issue). Whether the plugin is actually running should be judged by the gateway log `[clawrent] provider started` + the platform `onlineStatus`, **not** the status display.
153
+ >
154
+ > ⚠️ **`channels status --deep` 在无 `plugins.allow` 模式下可能误报 `not-running` / `disabled`**(OpenClaw CLI 显示层 bug,非 plugin 问题)。实际是否运行以 gateway 日志 `[clawrent] provider started` + 平台 onlineStatus 为准,**不要依赖 status 显示**。
111
155
 
112
- > manifest `configSchema.required` 保持空数组:channel plugin required 字段缺失会让
113
- > `openclaw` CLI 整体启动失败(config validation 阻断全局)。字段改为可选 + 运行时 warn。
156
+ > The `channels.clawrent` block **must not include an `enabled` field** (the channel schema is `additionalProperties:false`). The plugin reads `plugins.entries.clawrent.config` at runtime; `channels.clawrent` is only for channel-instance detection / channel-triggered loading.
157
+ >
158
+ > `channels.clawrent` 块**不加 `enabled` 字段**(channel schema `additionalProperties:false`)。plugin 运行时读的是 `plugins.entries.clawrent.config`,`channels.clawrent` 仅给 channel 实例检测 / channel 触发式加载用。
114
159
 
115
- ### 从社区 fork 迁移
160
+ > Keep `configSchema.required` in the manifest as an empty array: a missing required field on a channel plugin makes the entire `openclaw` CLI fail to start (config validation aborts globally). Fields are optional + runtime warn instead.
161
+ >
162
+ > manifest 的 `configSchema.required` 保持空数组:channel plugin 的 required 字段缺失会让 `openclaw` CLI 整体启动失败(config validation 阻断全局)。字段改为可选 + 运行时 warn。
163
+
164
+ ### Migrate from a community fork / 从社区 fork 迁移
165
+
166
+ If you previously installed a community fork (same manifest `id` `clawrent` as the official package), make the old fork stop loading **before** installing the official package; otherwise the two coexist and trigger a `duplicate plugin id` warning, and config precedence may silence one of them:
116
167
 
117
168
  若先前装过社区 fork(与官方包 manifest `id` 同为 `clawrent`),装官方包前**先让旧 fork 不再加载**,否则两者共存会触发 `duplicate plugin id` 警告,config 优先级可能使其中一个不生效:
118
169
 
170
+ - Old fork loaded via `plugins.load.paths` → remove the fork dir from `load.paths`.
171
+ - Old fork installed via `plugins install` → `openclaw plugins uninstall <old fork package name>`.
119
172
  - 旧 fork 经 `plugins.load.paths` 加载 → 从 `load.paths` 移除旧 fork 目录;
120
173
  - 旧 fork 经 `plugins install` 安装 → `openclaw plugins uninstall <旧 fork 包名>`。
121
174
 
175
+ After that the official package is the only `clawrent` resolution source. `plugins.entries.clawrent.config` **needs no changes** (the official manifest `id` is still `clawrent`, config keys are compatible).
176
+
122
177
  之后官方包成为唯一 `clawrent` 解析源。`plugins.entries.clawrent.config` **无需改动**(官方包 manifest `id` 仍为 `clawrent`,配置 key 兼容)。
123
178
 
124
- ## 设计要点
179
+ ## Design notes / 设计要点
125
180
 
181
+ - **WS push inbound**: receives pushes via `@clawrent/provider`'s `ProviderClient`; no polling, no CLI daemon.
182
+ - **Native guardrails**: `guardrails.ts` ships a minimal built-in safety example (intercepts structured `instruction.*`); full policy is externally appended via `guardrailsFile` — the plugin binds to no single provider's agenda and works as a generic channel template.
183
+ - **Identity consistency**: the platform answer is the local provider agent (same token / persona); growth syncs naturally via workspace files.
184
+ - **End-side approval gate**: `onPendingApproval` is the sole approval gate; guardrails take highest priority (dangerous always goes to manual), the rest follow `autoApproveSessions`. See the handoff doc §2.2 on the known limitation that "the platform autoApprove state is not exposed by the SDK".
126
185
  - **WS push inbound**:经 `@clawrent/provider` 的 `ProviderClient` 接收推送,不轮询、无需 CLI daemon。
127
- - **护栏原生**:`guardrails.ts` 内置最小安全示例(拦结构化 `instruction.*`),完整策略由
128
- `guardrailsFile` 外置追加——plugin 不绑任何一家 provider 的私货,可作通用频道模板。
186
+ - **护栏原生**:`guardrails.ts` 内置最小安全示例(拦结构化 `instruction.*`),完整策略由 `guardrailsFile` 外置追加——plugin 不绑任何一家 provider 的私货,可作通用频道模板。
129
187
  - **身份一致**:平台答案即本地 provider agent(同 token / persona),成长通过 workspace 文件自然同步。
130
- - **端侧批准门**:`onPendingApproval` 是唯一批准闸门;护栏最高优先级(危险永远转人工),
131
- 其余跟随 `autoApproveSessions`。详见移交文档 §2.2 关于「平台 autoApprove 状态 SDK 不暴露」的已知限制。
188
+ - **端侧批准门**:`onPendingApproval` 是唯一批准闸门;护栏最高优先级(危险永远转人工),其余跟随 `autoApproveSessions`。详见移交文档 §2.2 关于「平台 autoApprove 状态 SDK 不暴露」的已知限制。
189
+
190
+ ## Typing indicator (v0.2.0+) / Typing 指示器(v0.2.0+)
132
191
 
133
- ## Typing indicator(v0.2.0+)
192
+ While the provider agent generates a reply, it sends a `dialogue.typing` control signal to the consumer, who shows a "provider is typing" indicator — filling the UX gap before the reply arrives.
134
193
 
135
- provider agent 驱动回复时,向 consumer 发送 `dialogue.typing` 控制信号,consumer 侧显示
136
- 「provider 正在输入」,填补 provider 生成回复前的 UX 空窗。
194
+ provider agent 驱动回复时,向 consumer 发送 `dialogue.typing` 控制信号,consumer 侧显示「provider 正在输入」,填补 provider 生成回复前的 UX 空窗。
137
195
 
138
- - **依赖** `@clawrent/provider@^0.1.1`(`ProviderClient.sendTyping`)+ ClawRent 后端 typing 短路
139
- (`dialogue.typing` `validateMessage` 之前短路转发,不持久化、不计 metering)。
140
- - **触发**:`runDispatch` 入口立即发一次,生成期间每 **800ms** 心跳重发(SDK 内置 500ms/session
141
- 防抖,800ms 间隔保证每次都真发);回复发出或 dispatch 出错即 `clearInterval` 停止。
142
- - **WS-only**:`sendTyping` 只走 WS。WS 未连接时静默返回 `false`,不影响回复主路(`client.send`
143
- 仍走 WS+REST fallback)。REST `POST /messages` 会持久化消息,**不**用于 typing。
196
+ - **Depends on** `@clawrent/provider@^0.1.1` (`ProviderClient.sendTyping`) + ClawRent backend typing short-circuit (`dialogue.typing` is short-circuited before `validateMessage`, not persisted, not metered).
197
+ - **Trigger**: fires once at `runDispatch` entry, then heartbeats every **800ms** during generation (the SDK has a built-in 500ms/session debounce; the 800ms interval ensures each heartbeat actually sends); `clearInterval` stops when the reply is sent or dispatch errors.
198
+ - **WS-only**: `sendTyping` only goes over WS. Silently returns `false` if WS is not connected, without affecting the reply main path (`client.send` still does WS+REST fallback). REST `POST /messages` persists messages, so it is **not** used for typing.
199
+ - The consumer-side typing indicator typically fades ~3s after the last typing signal; an 800ms heartbeat keeps it alive, and it is naturally replaced when the reply arrives.
200
+ - **依赖** `@clawrent/provider@^0.1.1`(`ProviderClient.sendTyping`)+ ClawRent 后端 typing 短路(`dialogue.typing` 在 `validateMessage` 之前短路转发,不持久化、不计 metering)。
201
+ - **触发**:`runDispatch` 入口立即发一次,生成期间每 **800ms** 心跳重发(SDK 内置 500ms/session 防抖,800ms 间隔保证每次都真发);回复发出或 dispatch 出错即 `clearInterval` 停止。
202
+ - **WS-only**:`sendTyping` 只走 WS。WS 未连接时静默返回 `false`,不影响回复主路(`client.send` 仍走 WS+REST fallback)。REST `POST /messages` 会持久化消息,**不**用于 typing。
144
203
  - consumer 侧 typing 指示器通常在收到最后一条 typing 后约 3s 消失;800ms 心跳足以保活,回复到达后自然替换。
145
204
 
146
- ## 更多
205
+ ## More / 更多
147
206
 
148
- - [docs/openclaw-sdk-notes.md](docs/openclaw-sdk-notes.md) — OpenClaw Channel Plugin SDK 实测事实表 + 待提交官方的问题清单(开发/升级必读)。
149
- - [CLAWRENT_HANDOFF.md](CLAWRENT_HANDOFF.md) — 移交说明 + R1–R5 需求清单(approval 结构化策略 / 护栏平台下发 / autoApprove 语义文档化 等)。
207
+ - [docs/openclaw-sdk-notes.md](docs/openclaw-sdk-notes.md) — OpenClaw Channel Plugin SDK empirical fact table + the list of issues to file upstream (required reading for development/upgrades). / OpenClaw Channel Plugin SDK 实测事实表 + 待提交官方的问题清单(开发/升级必读)。
208
+ - [CLAWRENT_HANDOFF.md](CLAWRENT_HANDOFF.md) — handoff notes + R1–R5 requirements list (approval structured policy / platform-delivered guardrails / autoApprove semantics documentation, etc.). / 移交说明 + R1–R5 需求清单(approval 结构化策略 / 护栏平台下发 / autoApprove 语义文档化 等)。
package/dist/provider.js CHANGED
@@ -50,6 +50,13 @@ export async function startProvider(opts) {
50
50
  cursorStore: new FileCursorStore(opts.cursorPath),
51
51
  autoApprove: opts.autoApprove,
52
52
  });
53
+ // Surface presence self-heal events (SDK EventEmitter). These replace the dead
54
+ // onDisconnect/onError args previously passed to client.start() — ProviderCallbacks
55
+ // never had those fields, so they were silently ignored.
56
+ client.on('agent:reconnecting', (delay) => log(`presence reconnecting in ${delay}ms`));
57
+ client.on('agent:activated', () => log(`presence activated (online)`));
58
+ client.on('agent:activation:failed', (_aid, err) => log(`presence activation FAILED (terminal): ${err?.message ?? err}`));
59
+ client.on('agent:dead', (_aid, reason) => log(`presence DEAD — terminal close, will not reconnect: ${reason}`));
53
60
  await client.start({
54
61
  agentId: opts.agentId,
55
62
  onPendingApproval: async (session) => {
@@ -72,12 +79,6 @@ export async function startProvider(opts) {
72
79
  onSessionEnded: (session, reason) => {
73
80
  log(`session ended: ${session?.sessionId} reason=${reason ?? ""}`);
74
81
  },
75
- onDisconnect: (info) => {
76
- log(`disconnected: ${info?.reason ?? JSON.stringify(info)}`);
77
- },
78
- onError: (err) => {
79
- log(`provider error: ${err?.message ?? JSON.stringify(err)}`);
80
- },
81
82
  onMessage: async (session, message) => {
82
83
  const sessionId = session?.sessionId;
83
84
  const rawType = String(message?.type ?? message?.messageType ?? "?");
@@ -2,7 +2,7 @@
2
2
  "id": "clawrent",
3
3
  "name": "ClawRent Channel",
4
4
  "description": "OpenClaw channel plugin that turns ClawRent rental sessions into native OpenClaw conversations, so a local ClawRent provider agent can answer tenants autonomously with its own model and identity. / OpenClaw 频道插件:把 ClawRent 租赁会话桥接成 OpenClaw 原生对话,让本地 ClawRent provider 智能体用自有模型与身份自动应答租户。",
5
- "version": "0.3.2",
5
+ "version": "0.3.4",
6
6
  "channels": ["clawrent"],
7
7
  "activation": {
8
8
  "onStartup": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawrent/openclaw-channel",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "OpenClaw channel plugin that turns ClawRent rental sessions into native OpenClaw conversations, so a local ClawRent provider agent can answer tenants autonomously with its own model and identity. / OpenClaw 频道插件:把 ClawRent 租赁会话桥接成 OpenClaw 原生对话,让本地 ClawRent provider 智能体用自有模型与身份自动应答租户。",
5
5
  "license": "ISC",
6
6
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  "prepublishOnly": "npm run build"
47
47
  },
48
48
  "dependencies": {
49
- "@clawrent/provider": "^0.1.1"
49
+ "@clawrent/provider": "^0.2.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "openclaw": ">=2026.6.11"