@dhf-openclaw/grix 0.4.10 → 0.4.11

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,75 +1,46 @@
1
- # OpenClaw Grix Unified Plugin
1
+ # OpenClaw Grix 插件
2
2
 
3
- This plugin connects OpenClaw to [https://grix.dhf.pub/](https://grix.dhf.pub/) and provides a single, unified integration for:
3
+ OpenClaw 接到 Grix(`grix.dhf.pub`)的统一插件,包含:
4
4
 
5
- - Grix channel transport (WebSocket Agent API)
6
- - native message actions (`unsend` / `delete`)
7
- - typed Grix admin tools
8
- - operator CLI commands
9
- - bundled Grix workflow skills
5
+ - Grix Channel(收发消息、流式回复、`unsend`、`delete`)
6
+ - 管理工具:`grix_query`、`grix_group`、`grix_agent_admin`
7
+ - 运维命令:`openclaw grix doctor`、`openclaw grix create-agent`
8
+ - 内置技能包(`skills/`)
10
9
 
11
- Compatibility:
10
+ ## 兼容性
12
11
 
13
- - Requires `OpenClaw >= 2026.3.13`
12
+ - `OpenClaw >= 2026.3.23-1`
14
13
 
15
- ## Included Capability
14
+ ## 5 分钟安装(推荐)
16
15
 
17
- After installation, one plugin covers all major Grix workflows:
18
-
19
- - Channel runtime:
20
- - inbound message receive
21
- - outbound reply / media / streaming chunk send
22
- - native channel actions (`unsend`, `delete`)
23
- - Typed admin tools:
24
- - `grix_query`
25
- - `grix_group`
26
- - `grix_agent_admin`
27
- - Operator CLI:
28
- - `openclaw grix doctor`
29
- - `openclaw grix create-agent ...`
30
- - Bundled skills:
31
- - `message-send`
32
- - `message-unsend`
33
- - `egg-install`
34
- - `grix-query`
35
- - `grix-group-governance`
36
- - `grix-agent-admin`
37
-
38
- ## Install
16
+ ### 1) 安装并启用插件
39
17
 
40
18
  ```bash
41
19
  openclaw plugins install @dhf-openclaw/grix
42
20
  openclaw plugins enable grix
43
- openclaw gateway restart
44
21
  ```
45
22
 
46
- ### Local Source Checkout
23
+ ### 2) 绑定 Grix Channel
47
24
 
48
- If you load from a local checkout:
25
+ 用你已有的 Grix API agent 信息执行:
49
26
 
50
27
  ```bash
51
- npm install
52
- openclaw plugins install ./grix.ts
28
+ openclaw channels add \
29
+ --channel grix \
30
+ --name grix-main \
31
+ --http-url "wss://grix.dhf.pub/v1/agent-api/ws?agent_id={agent_id}" \
32
+ --user-id "<YOUR_AGENT_ID>" \
33
+ --token "<YOUR_API_KEY>"
53
34
  ```
54
35
 
55
- ## Required OpenClaw Config
36
+ 说明:
56
37
 
57
- ### Channel Config (`channels.grix`)
38
+ - `--http-url` 可以带 `agent_id`,也可以不带。不带时会自动按 `--user-id` 补上。
39
+ - `--name` 是本地账户名,可自定义(如 `ops`、`prod`)。
58
40
 
59
- ```json
60
- {
61
- "channels": {
62
- "grix": {
63
- "enabled": true,
64
- "wsUrl": "wss://grix.dhf.pub/v1/agent-api/ws?agent_id=<YOUR_AGENT_ID>",
65
- "agentId": "<YOUR_AGENT_ID>",
66
- "apiKey": "<YOUR_API_KEY>"
67
- }
68
- }
69
- }
70
- ```
41
+ ### 3) 开放工具权限
71
42
 
72
- ### Tool Exposure Config
43
+ OpenClaw 配置里确保有:
73
44
 
74
45
  ```json
75
46
  {
@@ -88,7 +59,31 @@ openclaw plugins install ./grix.ts
88
59
  }
89
60
  ```
90
61
 
91
- Full example:
62
+ ### 4) 重启网关
63
+
64
+ ```bash
65
+ openclaw gateway restart
66
+ ```
67
+
68
+ ### 5) 验证安装结果
69
+
70
+ ```bash
71
+ openclaw plugins info grix --json
72
+ openclaw grix doctor
73
+ openclaw skills list
74
+ ```
75
+
76
+ 预期:
77
+
78
+ - `grix` 插件已启用
79
+ - `doctor` 能看到可用账户
80
+ - `skills list` 能看到本插件内置技能
81
+
82
+ ## 配置参数说明(完整)
83
+
84
+ `channels.grix` 支持“单账户”或“多账户(accounts)”两种写法。
85
+
86
+ ### 最小可用配置(单账户)
92
87
 
93
88
  ```json
94
89
  {
@@ -99,66 +94,92 @@ Full example:
99
94
  "agentId": "<YOUR_AGENT_ID>",
100
95
  "apiKey": "<YOUR_API_KEY>"
101
96
  }
102
- },
103
- "tools": {
104
- "profile": "coding",
105
- "alsoAllow": [
106
- "message",
107
- "grix_query",
108
- "grix_group",
109
- "grix_agent_admin"
110
- ],
111
- "sessions": {
112
- "visibility": "agent"
113
- }
114
97
  }
115
98
  }
116
99
  ```
117
100
 
118
- After any config change:
101
+ ### 多账户配置示例
119
102
 
120
- ```bash
121
- openclaw gateway restart
103
+ ```json
104
+ {
105
+ "channels": {
106
+ "grix": {
107
+ "enabled": true,
108
+ "defaultAccount": "ops",
109
+ "accounts": {
110
+ "ops": {
111
+ "enabled": true,
112
+ "name": "Ops",
113
+ "wsUrl": "wss://grix.dhf.pub/v1/agent-api/ws?agent_id=<OPS_AGENT_ID>",
114
+ "agentId": "<OPS_AGENT_ID>",
115
+ "apiKey": "<OPS_API_KEY>"
116
+ },
117
+ "prod": {
118
+ "enabled": true,
119
+ "wsUrl": "wss://grix.dhf.pub/v1/agent-api/ws?agent_id=<PROD_AGENT_ID>",
120
+ "agentId": "<PROD_AGENT_ID>",
121
+ "apiKey": "<PROD_API_KEY>"
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
122
127
  ```
123
128
 
124
- ## Typed Tools
125
-
126
- ### `grix_query`
127
-
128
- Supported actions:
129
-
130
- - `contact_search`
131
- - `session_search`
132
- - `message_history`
133
-
134
- ### `grix_group`
135
-
136
- Supported actions:
137
-
138
- - `create`
139
- - `detail`
140
- - `add_members`
141
- - `remove_members`
142
- - `update_member_role`
143
- - `update_all_members_muted`
144
- - `update_member_speaking`
145
- - `dissolve`
146
-
147
- ### `grix_agent_admin`
148
-
149
- Creates `provider_type=3` API agents with typed parameters.
150
-
151
- This tool creates the remote Grix API agent only. It does not directly mutate local OpenClaw channel config.
152
-
153
- ## Operator CLI
154
-
155
- ### Inspect Grix accounts
129
+ ### 字段说明
130
+
131
+ | 字段 | 必填 | 默认值 | 说明 |
132
+ | --- | --- | --- | --- |
133
+ | `enabled` | 否 | `true` | 全局开关。设为 `false` 后该通道停用。 |
134
+ | `defaultAccount` | 否 | 自动选择 | 多账户时默认账户 ID。 |
135
+ | `accounts.<id>` | 否 | - | 多账户配置项,`<id>` 自定义(如 `ops`)。 |
136
+ | `name` | 否 | - | 账户显示名。 |
137
+ | `wsUrl` | 是(可用环境变量兜底) | `ws://127.0.0.1:27189/...`(当有 `agentId` 且未填时) | Grix WebSocket 地址。 |
138
+ | `agentId` | 是(可用环境变量兜底) | - | Grix agent ID。 |
139
+ | `apiKey` | 是(可用环境变量兜底) | - | Grix API Key。 |
140
+ | `reconnectMs` | 否 | `2000` | 重连基础延迟(毫秒)。 |
141
+ | `reconnectMaxMs` | 否 | `max(30000, reconnectMs*8)` | 重连最大延迟(毫秒)。 |
142
+ | `reconnectStableMs` | 否 | `30000` | 连接保持多久算“稳定”(毫秒)。 |
143
+ | `connectTimeoutMs` | 否 | `10000` | 建连超时(毫秒)。 |
144
+ | `keepalivePingMs` | 否 | 自动计算 | 心跳发送间隔(毫秒)。 |
145
+ | `keepaliveTimeoutMs` | 否 | 自动计算 | 心跳超时阈值(毫秒)。 |
146
+ | `upstreamRetryMaxAttempts` | 否 | `3` | 上游发送失败重试次数(1-5)。 |
147
+ | `upstreamRetryBaseDelayMs` | 否 | `300` | 上游重试基础延迟(毫秒)。 |
148
+ | `upstreamRetryMaxDelayMs` | 否 | `2000` | 上游重试最大延迟(毫秒)。 |
149
+ | `maxChunkChars` | 否 | `1200` | 普通回复分片长度上限(最大 2000)。 |
150
+ | `streamChunkChars` | 否 | `48` | 流式回复分片长度上限(最大 2000)。 |
151
+ | `streamChunkDelayMs` | 否 | `0` | 流式分片发送间隔(毫秒)。 |
152
+ | `dmPolicy` | 否 | `open` | 私聊策略:`open` / `pairing` / `allowlist` / `disabled`。 |
153
+ | `allowFrom` | 否 | `[]` | 白名单发送者列表(配合 `dmPolicy=allowlist`)。 |
154
+ | `defaultTo` | | - | 默认发送目标会话。 |
155
+ | `execApprovals.enabled` | 否 | `false` | 是否启用聊天内执行审批。 |
156
+ | `execApprovals.approvers` | 条件必填 | `[]` | 审批人 sender ID 列表。启用审批时需填写。 |
157
+
158
+ ### 环境变量兜底
159
+
160
+ 如果配置文件没填,插件会按下列环境变量读取:
161
+
162
+ - `GRIX_WS_URL`
163
+ - `GRIX_AGENT_ID`
164
+ - `GRIX_API_KEY`
165
+
166
+ ## 工具与命令
167
+
168
+ ### Agent 可调用工具
169
+
170
+ - `grix_query`:`contact_search`、`session_search`、`message_history`
171
+ - `grix_group`:`create`、`detail`、`add_members`、`remove_members`、`update_member_role`、`update_all_members_muted`、`update_member_speaking`、`dissolve`
172
+ - `grix_agent_admin`:创建 `provider_type=3` 的 Grix API agent(只创建远端 agent,不会直接改本地 `channels.grix`)
173
+
174
+ ### 运维命令
175
+
176
+ 查看账户:
156
177
 
157
178
  ```bash
158
179
  openclaw grix doctor
159
180
  ```
160
181
 
161
- ### Create API agent
182
+ 创建 API agent
162
183
 
163
184
  ```bash
164
185
  openclaw grix create-agent \
@@ -166,19 +187,18 @@ openclaw grix create-agent \
166
187
  --describe-message-tool '{"actions":["unsend","delete"]}'
167
188
  ```
168
189
 
169
- `create-agent` prints:
190
+ 参数说明:
170
191
 
171
- - created agent payload
172
- - one-time API key in result payload
173
- - safe next-step channel binding command template
192
+ - `--agent-name`:必填,小写字母开头,只允许小写字母、数字、`-`,长度 3-32。
193
+ - `--describe-message-tool`:必填,JSON 对象,至少包含 `actions` 数组。
194
+ - `--account-id`:可选,指定用哪个本地 Grix 账户发起创建。
195
+ - `--avatar-url`:可选,给新 agent 设置头像地址。
174
196
 
175
- `--describe-message-tool` is required and must follow OpenClaw `describeMessageTool` discovery structure.
197
+ 命令输出里会给出一次性 `api_key` 和下一步绑定命令模板。
176
198
 
177
- ## Exec Approvals
199
+ ## 聊天内 Exec 审批(可选)
178
200
 
179
- Grix can approve OpenClaw host `exec` requests in chat.
180
-
181
- ### 1. Configure Grix approvers
201
+ 先在 Grix 账户里配置审批人:
182
202
 
183
203
  ```json
184
204
  {
@@ -193,26 +213,7 @@ Grix can approve OpenClaw host `exec` requests in chat.
193
213
  }
194
214
  ```
195
215
 
196
- If using named accounts:
197
-
198
- ```json
199
- {
200
- "channels": {
201
- "grix": {
202
- "accounts": {
203
- "ops": {
204
- "execApprovals": {
205
- "enabled": true,
206
- "approvers": ["<GRIX_SENDER_ID>"]
207
- }
208
- }
209
- }
210
- }
211
- }
212
- }
213
- ```
214
-
215
- ### 2. Enable OpenClaw exec approvals
216
+ 再开启 OpenClaw exec 审批:
216
217
 
217
218
  ```json
218
219
  {
@@ -228,58 +229,18 @@ If using named accounts:
228
229
  "enabled": true,
229
230
  "mode": "session"
230
231
  }
231
- },
232
- "channels": {
233
- "grix": {
234
- "execApprovals": {
235
- "enabled": true,
236
- "approvers": ["<GRIX_SENDER_ID>"]
237
- }
238
- }
239
232
  }
240
233
  }
241
234
  ```
242
235
 
243
- Mode choices:
236
+ `mode` 说明:
244
237
 
245
- - `session`: send approval prompt to current Grix chat
246
- - `targets`: send approval prompt to configured `approvals.exec.targets`
247
- - `both`: send to both
238
+ - `session`:发到当前会话
239
+ - `targets`:发到 `approvals.exec.targets`
240
+ - `both`:两边都发
248
241
 
249
- ### 3. Restart
242
+ 配置改完后重启:
250
243
 
251
244
  ```bash
252
245
  openclaw gateway restart
253
246
  ```
254
-
255
- ### 4. Approve in chat
256
-
257
- Flow:
258
-
259
- 1. Ask OpenClaw to run an `exec` command that needs approval.
260
- 2. OpenClaw sends approval prompt to Grix.
261
- 3. An allowed approver can:
262
- - click `Allow Once`, `Allow Always`, or `Deny`
263
- - or send `/approve <id> allow-once|allow-always|deny`
264
- 4. OpenClaw continues or denies execution.
265
-
266
- Notes:
267
-
268
- - approvers must be Grix sender IDs, not OpenClaw agent IDs
269
- - configure approvers under the serving account
270
- - approval requests and results are posted in chat
271
-
272
- ## Verification
273
-
274
- ```bash
275
- openclaw plugins info grix --json
276
- openclaw skills list
277
- openclaw grix doctor
278
- ```
279
-
280
- Expected:
281
-
282
- - plugin `grix` is enabled and loaded
283
- - typed tools are callable when `tools.alsoAllow` is configured
284
- - bundled skills are visible in skills list
285
- - `openclaw grix doctor` can read configured `channels.grix` accounts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhf-openclaw/grix",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Unified Grix OpenClaw plugin with channel transport, typed admin tools, and operator CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: grix-admin
3
+ description: 负责 OpenClaw 本地配置与后续 agent 管理;支持接收 grix-register 交接参数直接落地,也支持在已有主密钥下新建 agent 再落地。
4
+ ---
5
+
6
+ # Grix Agent Admin
7
+
8
+ `grix-admin` 只负责本地配置和管理动作。支持两个入口模式,二选一执行。
9
+
10
+ ## Mode A: bind-local(来自 grix-register 的首次交接)
11
+
12
+ 输入参数(全必填):
13
+
14
+ 1. `mode=bind-local`
15
+ 2. `agent_name`
16
+ 3. `agent_id`
17
+ 4. `api_endpoint`
18
+ 5. `api_key`
19
+
20
+ 执行规则:
21
+
22
+ 1. 不做远端创建,直接执行本地绑定:
23
+
24
+ ```bash
25
+ scripts/grix_agent_bind.py configure-local-openclaw \
26
+ --agent-name <agent_name> \
27
+ --agent-id <agent_id> \
28
+ --api-endpoint '<api_endpoint>' \
29
+ --api-key '<api_key>' \
30
+ --apply
31
+ ```
32
+
33
+ 2. 可选执行检查:
34
+
35
+ ```bash
36
+ scripts/grix_agent_bind.py inspect-local-openclaw --agent-name <agent_name>
37
+ ```
38
+
39
+ ## Mode B: create-and-bind(已有主密钥时的后续管理)
40
+
41
+ 输入参数:
42
+
43
+ 1. `agentName`(必填):`^[a-z][a-z0-9-]{2,31}$`
44
+ 2. `describeMessageTool`(必填):`actions` 非空
45
+ 3. `accountId`(可选)
46
+ 4. `avatarUrl`(可选)
47
+
48
+ 执行规则:
49
+
50
+ 1. 先检查 `~/.openclaw/openclaw.json` 的 `channels.grix.apiKey`。
51
+ 2. 若缺失或为空,说明主通道还没完成,不做本模式,立刻切回 `grix-register`。
52
+ 3. 若已存在,再调用 `grix_agent_admin` 创建远端 agent(仅一次,不自动重试)。
53
+ 4. 创建成功后,执行本地绑定命令(同 Mode A)。
54
+
55
+ ## Guardrails(两种模式都适用)
56
+
57
+ 1. Never ask user for website account/password.
58
+ 2. `bind-local` 模式禁止再次回调 `grix-register`,避免循环路由。
59
+ 3. 远端创建(Mode B)视为非幂等,不确认不自动重试。
60
+ 4. 完整 `api_key` 仅一次性回传,不要重复明文回显。
61
+ 5. 本地 `--apply` 没成功前,不得宣称配置完成。
62
+
63
+ ## Error Handling Rules
64
+
65
+ 1. `bind-local` 缺少字段:明确指出缺哪个字段并停止。
66
+ 2. invalid name(Mode B):要求用户提供合法小写英文名。
67
+ 3. `403/20011`:提示 owner 授权 `agent.api.create`。
68
+ 4. `401/10001`:检查本地 `agent_api_key` 或 grix 账号配置。
69
+ 5. `409/20002`:要求更换 agent 名称。
70
+ 6. 本地 apply 失败:返回失败命令与结果并停止。
71
+
72
+ ## Response Style
73
+
74
+ 1. 明确写出当前执行的是 `bind-local` 还是 `create-and-bind`。
75
+ 2. 分阶段汇报:远端(如有)+ 本地绑定。
76
+ 3. 明确说明本地是否已生效,失败则给具体原因。
77
+
78
+ ## References
79
+
80
+ 1. [references/api-contract.md](references/api-contract.md)
81
+ 2. [scripts/grix_agent_bind.py](scripts/grix_agent_bind.py)
@@ -2,7 +2,10 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- Map remote provisioning action to Aibot Agent API HTTP route, then hand over to local OpenClaw binding.
5
+ `grix-admin` 负责本地绑定,支持两种入口:
6
+
7
+ 1. `bind-local`:接收 `grix-register` 交接参数,直接本地绑定。
8
+ 2. `create-and-bind`:在已有主密钥下先远端创建,再本地绑定。
6
9
 
7
10
  ## Base Rules
8
11
 
@@ -12,7 +15,7 @@ Map remote provisioning action to Aibot Agent API HTTP route, then hand over to
12
15
  4. Route must pass scope middleware before service business checks.
13
16
  5. Do not ask users to provide website account/password for this flow.
14
17
 
15
- ## Action Mapping (v1)
18
+ ## Action Mapping (create-and-bind only)
16
19
 
17
20
  | Tool | Method | Route | Required Scope |
18
21
  |---|---|---|---|
@@ -72,7 +75,7 @@ Map remote provisioning action to Aibot Agent API HTTP route, then hand over to
72
75
 
73
76
  ## Post-Create Handover
74
77
 
75
- After `code=0`, continue with local OpenClaw binding via bundled script:
78
+ After `code=0` (or when using `bind-local` mode), continue with local OpenClaw binding via bundled script:
76
79
 
77
80
  1. apply local changes directly:
78
81
  - `scripts/grix_agent_bind.py configure-local-openclaw --agent-name <agent_name> --agent-id <agent_id> --api-endpoint '<api_endpoint>' --api-key '<api_key>' --apply`
@@ -85,3 +88,19 @@ Local apply writes:
85
88
  2. `channels.grix.accounts.<agent_name>` entry
86
89
  3. `bindings` route for `channel=grix`
87
90
  4. required tools config and gateway restart
91
+
92
+ ## bind-local Input Contract
93
+
94
+ When called from `grix-register`, `grix-admin` should accept:
95
+
96
+ ```json
97
+ {
98
+ "mode": "bind-local",
99
+ "agent_name": "grix-main",
100
+ "agent_id": "2029786829095440384",
101
+ "api_endpoint": "wss://grix.dhf.pub/v1/agent-api/ws?agent_id=2029786829095440384",
102
+ "api_key": "ak_xxx"
103
+ }
104
+ ```
105
+
106
+ In this mode, skip remote create and execute local bind directly.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: egg-install
2
+ name: grix-egg
3
3
  description: 在虾塘触发的安装私聊中处理 egg 安装。适用于主 OpenClaw agent 收到包含 install_id、egg、install、main_agent 的安装上下文时,负责与用户多轮确认、执行 persona.zip 到 OpenClaw agent 或 skill.zip 到 Claude agent 的正规安装流程,并在当前私聊里持续回报进度、失败原因和最终结果。
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: grix-group-governance
2
+ name: grix-group
3
3
  description: Use the typed `grix_group` tool for Grix group lifecycle and membership operations. Trigger when users ask to create, inspect, update, or dissolve groups, or when these operations fail with scope or permission errors.
4
4
  ---
5
5
 
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: grix-register
3
+ description: 仅用于初次安装阶段,完成 grix.dhf.pub 账号注册/登录并拿到第一个 provider_type=3 Agent 的参数;本技能不做任何本地 OpenClaw 配置。
4
+ ---
5
+
6
+ # Grix Register
7
+
8
+ 这个技能只负责“初次安装”的云端准备:账号注册/登录 + 生成首个 `provider_type=3` Agent 参数。
9
+ 你(AI)在终端里全自动操作,**不需要用户打开浏览器**。拿到参数后,必须移交给 `grix-admin` 做本地配置。
10
+
11
+ ## Workflow
12
+
13
+ ### 0. 角色边界(先声明再执行)
14
+
15
+ 1. 本技能**只能**做账号与云端 Agent 参数准备。
16
+ 2. 本技能**不能**执行 `openclaw` 命令,也不能修改本地 `openclaw.json`。
17
+ 3. 涉及本地配置、插件安装、工具权限、重启网关,一律交给 `grix-admin`。
18
+
19
+ ### 1. 询问邮箱并发送验证码
20
+
21
+ 1. 向用户询问 Email 地址。**不要让用户去网页端注册**,明确表示你会在对话里完成。
22
+ 2. 拿到邮箱后,在终端执行发送验证码的命令:
23
+ ```bash
24
+ scripts/grix_auth.py send-email-code --email "<用户的email>" --scene "register"
25
+ ```
26
+ 3. 等待命令执行成功后,提示用户去邮箱查收验证码,并询问该验证码。
27
+
28
+ ### 2. 执行自动注册(获取 Token)
29
+
30
+ 1. 用户提供验证码后,你需要为用户生成一个复杂度高且随机安全的密码(建议生成一个12位的强密码,包含大小写、数字和特殊字符)。
31
+ 2. 使用收集到的信息,执行注册命令:
32
+ ```bash
33
+ scripts/grix_auth.py register --email "<邮箱>" --password "<生成的随机密码>" --email-code "<验证码>"
34
+ ```
35
+ 3. 这个命令成功后会返回用户的 `access_token`。请在回复中安全地**将生成的密码告知用户**,建议他们妥善保存。
36
+
37
+ 注:如果注册提示邮箱已注册,可切换 `scripts/grix_auth.py login` 路径继续获取 `access_token`。
38
+
39
+ ### 3. 创建首个云端 Agent 参数
40
+
41
+ 拿到 `access_token` 后,询问 Agent 名称(如果上下文已有就直接用),执行:
42
+
43
+ ```bash
44
+ scripts/grix_auth.py create-api-agent --access-token "<token>" --agent-name "<agent名称>"
45
+ ```
46
+
47
+ 若同名 `provider_type=3` Agent 已存在,脚本会自动轮换 API Key 后复用。
48
+
49
+ ### 4. 强制移交给 grix-admin
50
+
51
+ 第三步执行成功后,脚本会返回一些关键设定:
52
+ - `agent_id`
53
+ - `agent_name`
54
+ - `api_endpoint`
55
+ - `api_key`
56
+
57
+ 然后立刻交给 `grix-admin`,并传递如下 payload:
58
+
59
+ ```json
60
+ {
61
+ "mode": "bind-local",
62
+ "agent_name": "<agent_name>",
63
+ "agent_id": "<agent_id>",
64
+ "api_endpoint": "<api_endpoint>",
65
+ "api_key": "<api_key>"
66
+ }
67
+ ```
68
+
69
+ ## Guardrails
70
+
71
+ 1. 不要求用户去网页注册或手动点页面。
72
+ 2. 不修改任何本地 OpenClaw 配置。
73
+ 3. 不安装插件、不改工具权限、不重启 gateway。
74
+ 4. 创建或复用出参数后,必须交接给 `grix-admin`。
75
+
76
+ ## References
77
+
78
+ 1. [references/api-contract.md](references/api-contract.md)
79
+ 2. [references/handoff-contract.md](references/handoff-contract.md)
80
+ 3. [scripts/grix_auth.py](scripts/grix_auth.py)
@@ -0,0 +1,71 @@
1
+ # API Contract
2
+
3
+ ## Responsibility Boundary
4
+
5
+ 1. `grix-register` 仅负责账号鉴权与云端 `provider_type=3` Agent 参数产出。
6
+ 2. 本技能不负责本地 OpenClaw 配置。
7
+ 3. 本地配置由 `grix-admin` 接手。
8
+
9
+ ## Base
10
+
11
+ 1. Website: `https://grix.dhf.pub/`
12
+ 2. Public Grix API base: `https://grix.dhf.pub/v1`
13
+
14
+ ## Route Mapping
15
+
16
+ ### Agent bootstrap action
17
+
18
+ | Action | Method | Route | Auth |
19
+ |---|---|---|---|
20
+ | `create-api-agent` | `POST` | `/agents/create` | `Authorization: Bearer <access_token>` |
21
+ | `list-agents` (internal helper) | `GET` | `/agents/list` | `Authorization: Bearer <access_token>` |
22
+ | `rotate-api-agent-key` (internal helper) | `POST` | `/agents/:id/api/key/rotate` | `Authorization: Bearer <access_token>` |
23
+
24
+ ## Payloads
25
+
26
+ ### `create-api-agent`
27
+
28
+ ```json
29
+ {
30
+ "agent_name": "grix-main",
31
+ "provider_type": 3
32
+ }
33
+ ```
34
+
35
+ `provider_type=3` means Agent API type.
36
+
37
+ ## Reuse flow
38
+
39
+ When the same-name `provider_type=3` agent already exists, the skill should:
40
+
41
+ 1. read `/agents/list`
42
+ 2. find the exact-name API agent
43
+ 3. rotate its key through `/agents/:id/api/key/rotate`
44
+ 4. reuse the returned `api_endpoint` and fresh `api_key`
45
+
46
+ ## Success Highlights
47
+
48
+ ### `create-api-agent`
49
+
50
+ The bundled script lifts these fields to the top level:
51
+
52
+ 1. `agent_id`
53
+ 2. `agent_name`
54
+ 3. `provider_type`
55
+ 4. `api_endpoint`
56
+ 5. `api_key`
57
+ 6. `api_key_hint`
58
+ 7. `session_id`
59
+
60
+ ## Common Errors
61
+
62
+ 1. create-agent or rotate-key returns missing `api_endpoint` or `api_key`
63
+
64
+ ## Handoff
65
+
66
+ 成功后输出这些字段,直接交给 `grix-admin`:
67
+
68
+ 1. `agent_id`
69
+ 2. `agent_name`
70
+ 3. `api_endpoint`
71
+ 4. `api_key`
@@ -0,0 +1,26 @@
1
+ # Grix Concepts
2
+
3
+ ## Canonical Explanation
4
+
5
+ 这个插件接入是为了在 `https://grix.dhf.pub/` 网站管理 OpenClaw,并支持移动端 PWA 页面。
6
+
7
+ ## Feature Highlights
8
+
9
+ 1. `grix-register` 负责初次账号准备与首个 agent 参数生成
10
+ 2. `grix-admin` 负责 OpenClaw 本地配置与后续管理
11
+ 3. 两者串联后,用户可在 `https://grix.dhf.pub/` 使用和管理
12
+
13
+ ## Default User-Facing Framing
14
+
15
+ ### One sentence
16
+
17
+ `grix-register` 只做“注册账号并拿到第一个 agent 参数”,本地配置统一交给 `grix-admin`。
18
+
19
+ ### Short paragraph
20
+
21
+ `grix-register` 只负责初次安装中的云端准备:注册/登录账号并生成第一个 `provider_type=3` agent 参数;随后必须把参数交给 `grix-admin`,由 `grix-admin` 负责本地 OpenClaw 配置。
22
+
23
+ ## After Setup
24
+
25
+ 1. `grix-register` 产出参数后,直接交接给 `grix-admin`。
26
+ 2. `grix-register` 不执行任何本地配置动作。
@@ -0,0 +1,24 @@
1
+ # Handoff Contract to grix-admin
2
+
3
+ ## Purpose
4
+
5
+ `grix-register` 完成账号与首个 Agent 参数准备后,统一把本地配置工作交给 `grix-admin`。
6
+
7
+ ## Required Payload
8
+
9
+ ```json
10
+ {
11
+ "mode": "bind-local",
12
+ "agent_name": "grix-main",
13
+ "agent_id": "2029786829095440384",
14
+ "api_endpoint": "wss://grix.dhf.pub/v1/agent-api/ws?agent_id=2029786829095440384",
15
+ "api_key": "ak_xxx"
16
+ }
17
+ ```
18
+
19
+ ## Rules
20
+
21
+ 1. `mode` 固定为 `bind-local`。
22
+ 2. `agent_name`、`agent_id`、`api_endpoint`、`api_key` 必填。
23
+ 3. `grix-register` 只负责生成以上参数,不执行本地配置命令。
24
+ 4. 本地写入、插件处理、工具权限、gateway 重启都由 `grix-admin` 负责。
@@ -0,0 +1,6 @@
1
+ # OpenClaw Setup Ownership
2
+
3
+ `grix-register` 不负责 OpenClaw 本地配置。
4
+ 所有 OpenClaw 配置相关动作(插件安装、channel 写入、tools 权限、gateway 重启)都属于 `grix-admin`。
5
+
6
+ 本目录保留此文件仅用于职责说明,避免误用。
@@ -0,0 +1,25 @@
1
+ # User Replies
2
+
3
+ ## One-liner Pitch
4
+
5
+ 我会先帮你完成注册并拿到第一个 agent 参数,然后交给 `grix-admin` 完成本地配置。
6
+
7
+ ## Short Intro
8
+
9
+ `grix-register` 只负责账号和云端参数准备,不改本地配置;本地配置由 `grix-admin` 接手。
10
+
11
+ ## Ready Reply
12
+
13
+ 账号和首个 agent 参数已经准备好,接下来我会把参数交给 `grix-admin` 做本地配置。
14
+
15
+ ## Main Ready, Admin Pending Reply
16
+
17
+ `grix-register` 阶段已完成,我现在只做参数移交,后续本地配置请由 `grix-admin` 继续。
18
+
19
+ ## Configured Now Reply
20
+
21
+ 参数已交接给 `grix-admin`,接下来由它完成本地配置。
22
+
23
+ ## Needs Setup Reply
24
+
25
+ 当前仍在 `grix-register` 阶段,我会继续完成注册并拿到第一个 agent 参数。
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import base64
4
+ import json
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ import urllib.error
9
+ import urllib.parse
10
+ import urllib.request
11
+ import uuid
12
+
13
+
14
+ DEFAULT_BASE_URL = "https://grix.dhf.pub"
15
+ DEFAULT_TIMEOUT_SECONDS = 15
16
+ DEFAULT_PORTAL_URL = "https://grix.dhf.pub/"
17
+
18
+
19
+ class GrixAuthError(RuntimeError):
20
+ def __init__(self, message, status=0, code=-1, payload=None):
21
+ super().__init__(message)
22
+ self.status = status
23
+ self.code = code
24
+ self.payload = payload
25
+
26
+
27
+ def normalize_base_url(raw_base_url: str) -> str:
28
+ base = (raw_base_url or "").strip() or DEFAULT_BASE_URL
29
+ parsed = urllib.parse.urlparse(base)
30
+ if not parsed.scheme or not parsed.netloc:
31
+ raise ValueError(f"Invalid base URL: {base}")
32
+
33
+ path = parsed.path.rstrip("/")
34
+ if not path:
35
+ path = "/v1"
36
+ elif not path.endswith("/v1"):
37
+ path = f"{path}/v1"
38
+
39
+ normalized = parsed._replace(path=path, params="", query="", fragment="")
40
+ return urllib.parse.urlunparse(normalized).rstrip("/")
41
+
42
+
43
+ def request_json(method: str, path: str, base_url: str, body=None, headers=None):
44
+ api_base_url = normalize_base_url(base_url)
45
+ url = f"{api_base_url}{path if path.startswith('/') else '/' + path}"
46
+ data = None
47
+ final_headers = dict(headers or {})
48
+ if body is not None:
49
+ data = json.dumps(body).encode("utf-8")
50
+ final_headers["Content-Type"] = "application/json"
51
+
52
+ req = urllib.request.Request(url=url, data=data, headers=final_headers, method=method)
53
+ try:
54
+ with urllib.request.urlopen(req, timeout=DEFAULT_TIMEOUT_SECONDS) as resp:
55
+ status = getattr(resp, "status", 200)
56
+ raw = resp.read().decode("utf-8")
57
+ except urllib.error.HTTPError as exc:
58
+ status = exc.code
59
+ raw = exc.read().decode("utf-8", errors="replace")
60
+ except urllib.error.URLError as exc:
61
+ raise GrixAuthError(f"network error: {exc.reason}") from exc
62
+
63
+ try:
64
+ payload = json.loads(raw)
65
+ except json.JSONDecodeError as exc:
66
+ raise GrixAuthError(f"invalid json response: {raw[:256]}", status=status) from exc
67
+
68
+ code = int(payload.get("code", -1))
69
+ msg = str(payload.get("msg", "")).strip() or "unknown error"
70
+ if status >= 400 or code != 0:
71
+ raise GrixAuthError(msg, status=status, code=code, payload=payload)
72
+
73
+ return {
74
+ "api_base_url": api_base_url,
75
+ "status": status,
76
+ "data": payload.get("data"),
77
+ "payload": payload,
78
+ }
79
+
80
+
81
+ def maybe_write_captcha_image(b64s: str):
82
+ text = (b64s or "").strip()
83
+ if not text.startswith("data:image/"):
84
+ return ""
85
+
86
+ marker = ";base64,"
87
+ idx = text.find(marker)
88
+ if idx < 0:
89
+ return ""
90
+
91
+ encoded = text[idx + len(marker) :]
92
+ try:
93
+ content = base64.b64decode(encoded)
94
+ except Exception:
95
+ return ""
96
+
97
+ fd, path = tempfile.mkstemp(prefix="grix-captcha-", suffix=".png")
98
+ try:
99
+ with os.fdopen(fd, "wb") as handle:
100
+ handle.write(content)
101
+ except Exception:
102
+ try:
103
+ os.unlink(path)
104
+ except OSError:
105
+ pass
106
+ return ""
107
+ return path
108
+
109
+
110
+ def print_json(payload):
111
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
112
+ sys.stdout.write("\n")
113
+
114
+
115
+ def build_auth_result(action: str, result: dict):
116
+ data = result.get("data") or {}
117
+ user = data.get("user") or {}
118
+ return {
119
+ "ok": True,
120
+ "action": action,
121
+ "api_base_url": result["api_base_url"],
122
+ "access_token": data.get("access_token", ""),
123
+ "refresh_token": data.get("refresh_token", ""),
124
+ "expires_in": data.get("expires_in", 0),
125
+ "user_id": user.get("id", ""),
126
+ "portal_url": DEFAULT_PORTAL_URL,
127
+ "data": data,
128
+ }
129
+
130
+
131
+ def build_agent_result(action: str, result: dict):
132
+ data = result.get("data") or {}
133
+ agent_id = str(data.get("id", "")).strip()
134
+ api_endpoint = str(data.get("api_endpoint", "")).strip()
135
+ api_key = str(data.get("api_key", "")).strip()
136
+
137
+ return {
138
+ "ok": True,
139
+ "action": action,
140
+ "api_base_url": result["api_base_url"],
141
+ "agent_id": agent_id,
142
+ "agent_name": data.get("agent_name", ""),
143
+ "provider_type": data.get("provider_type", 0),
144
+ "api_endpoint": api_endpoint,
145
+ "api_key": api_key,
146
+ "api_key_hint": data.get("api_key_hint", ""),
147
+ "session_id": data.get("session_id", ""),
148
+ "handoff": {
149
+ "target_skill": "grix-admin",
150
+ "payload": {
151
+ "agent_id": agent_id,
152
+ "agent_name": data.get("agent_name", ""),
153
+ "api_endpoint": api_endpoint,
154
+ "api_key": api_key,
155
+ },
156
+ },
157
+ "data": data,
158
+ }
159
+
160
+
161
+ def login_with_credentials(base_url: str, account: str, password: str, device_id: str, platform: str):
162
+ result = request_json(
163
+ "POST",
164
+ "/auth/login",
165
+ base_url,
166
+ body={
167
+ "account": account,
168
+ "password": password,
169
+ "device_id": device_id,
170
+ "platform": platform,
171
+ },
172
+ )
173
+ return build_auth_result("login", result)
174
+
175
+
176
+ def create_api_agent(base_url: str, access_token: str, agent_name: str, avatar_url: str):
177
+ request_body = {
178
+ "agent_name": agent_name.strip(),
179
+ "provider_type": 3,
180
+ "is_main": True,
181
+ }
182
+ normalized_avatar_url = (avatar_url or "").strip()
183
+ if normalized_avatar_url:
184
+ request_body["avatar_url"] = normalized_avatar_url
185
+
186
+ result = request_json(
187
+ "POST",
188
+ "/agents/create",
189
+ base_url,
190
+ body=request_body,
191
+ headers={
192
+ "Authorization": f"Bearer {access_token.strip()}",
193
+ },
194
+ )
195
+ return build_agent_result("create-api-agent", result)
196
+
197
+
198
+ def list_agents(base_url: str, access_token: str):
199
+ result = request_json(
200
+ "GET",
201
+ "/agents/list",
202
+ base_url,
203
+ headers={
204
+ "Authorization": f"Bearer {access_token.strip()}",
205
+ },
206
+ )
207
+ data = result.get("data") or {}
208
+ items = data.get("list") or []
209
+ if not isinstance(items, list):
210
+ items = []
211
+ return items
212
+
213
+
214
+ def rotate_api_agent_key(base_url: str, access_token: str, agent_id: str):
215
+ result = request_json(
216
+ "POST",
217
+ f"/agents/{str(agent_id).strip()}/api/key/rotate",
218
+ base_url,
219
+ body={},
220
+ headers={
221
+ "Authorization": f"Bearer {access_token.strip()}",
222
+ },
223
+ )
224
+ return build_agent_result("rotate-api-agent-key", result)
225
+
226
+
227
+ def find_existing_api_agent(agents, agent_name: str):
228
+ normalized_name = (agent_name or "").strip()
229
+ if not normalized_name:
230
+ return None
231
+
232
+ for item in agents:
233
+ if not isinstance(item, dict):
234
+ continue
235
+ if str(item.get("agent_name", "")).strip() != normalized_name:
236
+ continue
237
+ if int(item.get("provider_type", 0) or 0) != 3:
238
+ continue
239
+ if int(item.get("status", 0) or 0) == 3:
240
+ continue
241
+ return item
242
+ return None
243
+
244
+
245
+ def create_or_reuse_api_agent(
246
+ base_url: str,
247
+ access_token: str,
248
+ agent_name: str,
249
+ avatar_url: str,
250
+ prefer_existing: bool,
251
+ rotate_on_reuse: bool,
252
+ ):
253
+ if prefer_existing:
254
+ agents = list_agents(base_url, access_token)
255
+ existing = find_existing_api_agent(agents, agent_name)
256
+ if existing is not None:
257
+ if not rotate_on_reuse:
258
+ raise GrixAuthError(
259
+ "existing provider_type=3 agent found but rotate-on-reuse is disabled; cannot obtain api_key safely",
260
+ payload={"existing_agent": existing},
261
+ )
262
+ rotated = rotate_api_agent_key(base_url, access_token, str(existing.get("id", "")).strip())
263
+ rotated["source"] = "reused_existing_agent_with_rotated_key"
264
+ rotated["existing_agent"] = existing
265
+ return rotated
266
+
267
+ created = create_api_agent(base_url, access_token, agent_name, avatar_url)
268
+ created["source"] = "created_new_agent"
269
+ return created
270
+
271
+
272
+ def default_device_id(platform: str) -> str:
273
+ normalized_platform = (platform or "").strip() or "web"
274
+ return f"{normalized_platform}_{uuid.uuid4()}"
275
+
276
+
277
+ def handle_fetch_captcha(args):
278
+ result = request_json("GET", "/auth/captcha", args.base_url)
279
+ data = result.get("data") or {}
280
+ image_path = maybe_write_captcha_image(str(data.get("b64s", "")))
281
+ payload = {
282
+ "ok": True,
283
+ "action": "fetch-captcha",
284
+ "api_base_url": result["api_base_url"],
285
+ "captcha_id": data.get("captcha_id", ""),
286
+ "b64s": data.get("b64s", ""),
287
+ }
288
+ if image_path:
289
+ payload["captcha_image_path"] = image_path
290
+ print_json(payload)
291
+
292
+
293
+ def handle_send_email_code(args):
294
+ scene = args.scene.strip()
295
+ payload = {
296
+ "email": args.email.strip(),
297
+ "scene": scene,
298
+ }
299
+
300
+ captcha_id = (args.captcha_id or "").strip()
301
+ captcha_value = (args.captcha_value or "").strip()
302
+ if scene in {"reset", "change_password"}:
303
+ if not captcha_id or not captcha_value:
304
+ raise GrixAuthError("captcha_id and captcha_value are required for reset/change_password")
305
+ if captcha_id:
306
+ payload["captcha_id"] = captcha_id
307
+ if captcha_value:
308
+ payload["captcha_value"] = captcha_value
309
+
310
+ result = request_json(
311
+ "POST",
312
+ "/auth/send-code",
313
+ args.base_url,
314
+ body=payload,
315
+ )
316
+ print_json(
317
+ {
318
+ "ok": True,
319
+ "action": "send-email-code",
320
+ "api_base_url": result["api_base_url"],
321
+ "data": result.get("data"),
322
+ }
323
+ )
324
+
325
+
326
+ def handle_register(args):
327
+ platform = (args.platform or "").strip() or "web"
328
+ device_id = (args.device_id or "").strip() or default_device_id(platform)
329
+ result = request_json(
330
+ "POST",
331
+ "/auth/register",
332
+ args.base_url,
333
+ body={
334
+ "email": args.email.strip(),
335
+ "password": args.password.strip(),
336
+ "email_code": args.email_code.strip(),
337
+ "device_id": device_id,
338
+ "platform": platform,
339
+ },
340
+ )
341
+ print_json(build_auth_result("register", result))
342
+
343
+
344
+ def handle_login(args):
345
+ account = (args.email or args.account or "").strip()
346
+ if not account:
347
+ raise GrixAuthError("either --email or --account is required")
348
+
349
+ platform = (args.platform or "").strip() or "web"
350
+ device_id = (args.device_id or "").strip() or default_device_id(platform)
351
+ print_json(
352
+ login_with_credentials(
353
+ args.base_url,
354
+ account,
355
+ args.password.strip(),
356
+ device_id,
357
+ platform,
358
+ )
359
+ )
360
+
361
+
362
+ def handle_create_api_agent(args):
363
+ print_json(
364
+ create_or_reuse_api_agent(
365
+ args.base_url,
366
+ args.access_token.strip(),
367
+ args.agent_name.strip(),
368
+ args.avatar_url,
369
+ not bool(args.no_reuse_existing_agent),
370
+ not bool(args.no_rotate_key_on_reuse),
371
+ )
372
+ )
373
+
374
+
375
+ def build_parser():
376
+ parser = argparse.ArgumentParser(description="Grix public auth API helper")
377
+ parser.add_argument("--base-url", default=DEFAULT_BASE_URL, help="Grix web base URL")
378
+
379
+ subparsers = parser.add_subparsers(dest="action", required=True)
380
+
381
+ fetch_captcha = subparsers.add_parser("fetch-captcha", help="Fetch captcha image")
382
+ fetch_captcha.set_defaults(handler=handle_fetch_captcha)
383
+
384
+ send_email_code = subparsers.add_parser("send-email-code", help="Send email verification code")
385
+ send_email_code.add_argument("--email", required=True)
386
+ send_email_code.add_argument("--scene", required=True, choices=["register", "reset", "change_password"])
387
+ send_email_code.add_argument("--captcha-id", default="")
388
+ send_email_code.add_argument("--captcha-value", default="")
389
+ send_email_code.set_defaults(handler=handle_send_email_code)
390
+
391
+ register = subparsers.add_parser("register", help="Register by email verification code")
392
+ register.add_argument("--email", required=True)
393
+ register.add_argument("--password", required=True)
394
+ register.add_argument("--email-code", required=True)
395
+ register.add_argument("--device-id", default="")
396
+ register.add_argument("--platform", default="web")
397
+ register.set_defaults(handler=handle_register)
398
+
399
+ login = subparsers.add_parser("login", help="Login and obtain tokens")
400
+ login_identity = login.add_mutually_exclusive_group(required=True)
401
+ login_identity.add_argument("--account")
402
+ login_identity.add_argument("--email")
403
+ login.add_argument("--password", required=True)
404
+ login.add_argument("--device-id", default="")
405
+ login.add_argument("--platform", default="web")
406
+ login.set_defaults(handler=handle_login)
407
+
408
+ create_api_agent_parser = subparsers.add_parser(
409
+ "create-api-agent",
410
+ help="Create a provider_type=3 API agent with a user access token",
411
+ )
412
+ create_api_agent_parser.add_argument("--access-token", required=True)
413
+ create_api_agent_parser.add_argument("--agent-name", required=True)
414
+ create_api_agent_parser.add_argument("--avatar-url", default="")
415
+ create_api_agent_parser.add_argument("--no-reuse-existing-agent", action="store_true")
416
+ create_api_agent_parser.add_argument("--no-rotate-key-on-reuse", action="store_true")
417
+ create_api_agent_parser.set_defaults(handler=handle_create_api_agent)
418
+
419
+ return parser
420
+
421
+
422
+ def main():
423
+ parser = build_parser()
424
+ args = parser.parse_args()
425
+ try:
426
+ args.handler(args)
427
+ except GrixAuthError as exc:
428
+ print_json(
429
+ {
430
+ "ok": False,
431
+ "action": args.action,
432
+ "status": exc.status,
433
+ "code": exc.code,
434
+ "error": str(exc),
435
+ "payload": exc.payload,
436
+ }
437
+ )
438
+ raise SystemExit(1)
439
+ except Exception as exc:
440
+ print_json(
441
+ {
442
+ "ok": False,
443
+ "action": args.action,
444
+ "status": 0,
445
+ "code": -1,
446
+ "error": str(exc),
447
+ }
448
+ )
449
+ raise SystemExit(1)
450
+
451
+
452
+ if __name__ == "__main__":
453
+ main()
@@ -1,85 +0,0 @@
1
- ---
2
- name: grix-agent-admin
3
- description: 通过 Grix Agent API 协议创建 `provider_type=3` 的 API agent,并直接完成本地 OpenClaw agent 与 Grix 渠道绑定配置(默认直接应用并返回结果)。
4
- ---
5
-
6
- # Grix Agent Admin
7
-
8
- Create a remote `provider_type=3` API agent, then complete local OpenClaw agent + grix channel binding in one flow.
9
-
10
- ## Security + Auth Path
11
-
12
- 1. This skill does **not** ask the user for website account/password.
13
- 2. Remote create action uses local `channels.grix` credentials and `Authorization: Bearer <agent_api_key>`.
14
- 3. Local OpenClaw config is handled by `scripts/grix_agent_bind.py`.
15
-
16
- ## Required Input
17
-
18
- 1. `agentName` (required): regex `^[a-z][a-z0-9-]{2,31}$`
19
- 2. `describeMessageTool` (required): must contain non-empty `actions`
20
- 3. `accountId` (optional)
21
- 4. `avatarUrl` (optional)
22
-
23
- ## Full Workflow
24
-
25
- ### A. Create Remote API Agent
26
-
27
- 1. Validate `agentName` and `describeMessageTool`.
28
- 2. Call `grix_agent_admin` once.
29
- 3. Read result fields: `id`, `agent_name`, `api_endpoint`, `api_key`, `api_key_hint`.
30
-
31
- ### B. Apply Local OpenClaw Binding Directly
32
-
33
- Run with `--apply` directly:
34
-
35
- ```bash
36
- scripts/grix_agent_bind.py configure-local-openclaw \
37
- --agent-name <agent_name> \
38
- --agent-id <agent_id> \
39
- --api-endpoint '<api_endpoint>' \
40
- --api-key '<api_key>' \
41
- --apply
42
- ```
43
-
44
- This applies:
45
-
46
- 1. upsert `agents.list` for `<agent_name>`
47
- 2. upsert `channels.grix.accounts.<agent_name>`
48
- 3. upsert `bindings` route to `channel=grix`, `accountId=<agent_name>`
49
- 4. ensure required tools (`message`, `grix_group`, `grix_agent_admin`)
50
- 5. create workspace defaults under `~/.openclaw/workspace-<agent_name>/`
51
- 6. run `openclaw gateway restart`
52
-
53
- ### C. Optional Verification
54
-
55
- If you need explicit post-check state, run:
56
-
57
- ```bash
58
- scripts/grix_agent_bind.py inspect-local-openclaw --agent-name <agent_name>
59
- ```
60
-
61
- ## Guardrails
62
-
63
- 1. Never ask user for website account/password.
64
- 2. Treat remote create as non-idempotent; do not auto-retry without confirmation.
65
- 3. Keep full `api_key` one-time only; do not repeatedly echo it.
66
- 4. Do not claim success before apply command returns success.
67
-
68
- ## Error Handling Rules
69
-
70
- 1. invalid name: ask user for a valid English lowercase name.
71
- 2. `403/20011`: ask owner to grant `agent.api.create` scope.
72
- 3. `401/10001`: verify local `agent_api_key` / grix account config.
73
- 4. `409/20002`: ask for another agent name.
74
- 5. local apply failed: return concrete failed command/result and stop.
75
-
76
- ## Response Style
77
-
78
- 1. Report two stages separately: remote create status + local binding status.
79
- 2. Include created `agent_id`, `agent_name`, `api_endpoint`, `api_key_hint`.
80
- 3. Clearly state local config has been applied (or failed with concrete reason).
81
-
82
- ## References
83
-
84
- 1. Load [references/api-contract.md](references/api-contract.md).
85
- 2. Use [scripts/grix_agent_bind.py](scripts/grix_agent_bind.py) for local binding apply.
@@ -1,4 +0,0 @@
1
- interface:
2
- display_name: "Grix Agent Admin"
3
- short_description: "Create Grix API agents and finish local OpenClaw binding."
4
- default_prompt: "Use this skill when users ask to create a new Grix API agent and finish OpenClaw binding. Never ask for website account/password. Validate `agentName` and require `describeMessageTool` (`actions` must be non-empty), then call `grix_agent_admin` once. After successful create, directly run `scripts/grix_agent_bind.py configure-local-openclaw ... --apply` and return final local binding result (success or exact failure reason)."
@@ -1,4 +0,0 @@
1
- interface:
2
- display_name: "Grix Group Governance"
3
- short_description: "Use the typed grix_group tool for Grix group operations."
4
- default_prompt: "Use this skill when users ask to create, inspect, update, or dissolve Grix groups. Validate parameters, call grix_group exactly once per action, and return clear remediation for scope/auth/parameter failures."
@@ -1,4 +0,0 @@
1
- version: 1
2
- agent:
3
- name: grix-query
4
- default_prompt: "Use this skill when users ask to find Grix contacts, locate sessions, or inspect a known session's message history. Validate required parameters, call grix_query exactly once per action, and do not fabricate a sessionId."