@dhf-openclaw/grix 0.4.11 → 0.4.14
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 +61 -6
- package/dist/index.js +347 -87
- package/package.json +2 -2
- package/skills/grix-group/SKILL.md +24 -4
- package/skills/grix-group/references/api-contract.md +15 -0
- package/skills/grix-register/SKILL.md +1 -1
- package/skills/grix-register/references/api-contract.md +3 -2
- package/skills/grix-register/scripts/grix_auth.py +28 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenClaw Grix 插件
|
|
2
2
|
|
|
3
|
-
把 OpenClaw 接到 Grix
|
|
3
|
+
把 OpenClaw 接到 Grix 服务的统一插件,默认支持正式环境,也支持本地开发地址,包含:
|
|
4
4
|
|
|
5
5
|
- Grix Channel(收发消息、流式回复、`unsend`、`delete`)
|
|
6
6
|
- 管理工具:`grix_query`、`grix_group`、`grix_agent_admin`
|
|
@@ -28,7 +28,7 @@ openclaw plugins enable grix
|
|
|
28
28
|
openclaw channels add \
|
|
29
29
|
--channel grix \
|
|
30
30
|
--name grix-main \
|
|
31
|
-
--http-url "wss
|
|
31
|
+
--http-url "wss://<YOUR_GRIX_HOST>/v1/agent-api/ws?agent_id={agent_id}" \
|
|
32
32
|
--user-id "<YOUR_AGENT_ID>" \
|
|
33
33
|
--token "<YOUR_API_KEY>"
|
|
34
34
|
```
|
|
@@ -85,12 +85,31 @@ openclaw skills list
|
|
|
85
85
|
|
|
86
86
|
### 最小可用配置(单账户)
|
|
87
87
|
|
|
88
|
+
线上发布示例:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"channels": {
|
|
93
|
+
"grix": {
|
|
94
|
+
"enabled": true,
|
|
95
|
+
"wsUrl": "wss://<YOUR_GRIX_HOST>/v1/agent-api/ws?agent_id=<YOUR_AGENT_ID>",
|
|
96
|
+
"apiBaseUrl": "https://<YOUR_GRIX_HOST>/v1/agent-api",
|
|
97
|
+
"agentId": "<YOUR_AGENT_ID>",
|
|
98
|
+
"apiKey": "<YOUR_API_KEY>"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
本地开发示例:
|
|
105
|
+
|
|
88
106
|
```json
|
|
89
107
|
{
|
|
90
108
|
"channels": {
|
|
91
109
|
"grix": {
|
|
92
110
|
"enabled": true,
|
|
93
|
-
"wsUrl": "
|
|
111
|
+
"wsUrl": "ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=<YOUR_AGENT_ID>",
|
|
112
|
+
"apiBaseUrl": "http://127.0.0.1:27180/v1/agent-api",
|
|
94
113
|
"agentId": "<YOUR_AGENT_ID>",
|
|
95
114
|
"apiKey": "<YOUR_API_KEY>"
|
|
96
115
|
}
|
|
@@ -110,13 +129,15 @@ openclaw skills list
|
|
|
110
129
|
"ops": {
|
|
111
130
|
"enabled": true,
|
|
112
131
|
"name": "Ops",
|
|
113
|
-
"wsUrl": "
|
|
132
|
+
"wsUrl": "ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=<OPS_AGENT_ID>",
|
|
133
|
+
"apiBaseUrl": "http://127.0.0.1:27180/v1/agent-api",
|
|
114
134
|
"agentId": "<OPS_AGENT_ID>",
|
|
115
135
|
"apiKey": "<OPS_API_KEY>"
|
|
116
136
|
},
|
|
117
137
|
"prod": {
|
|
118
138
|
"enabled": true,
|
|
119
|
-
"wsUrl": "wss
|
|
139
|
+
"wsUrl": "wss://<YOUR_GRIX_HOST>/v1/agent-api/ws?agent_id=<PROD_AGENT_ID>",
|
|
140
|
+
"apiBaseUrl": "https://<YOUR_GRIX_HOST>/v1/agent-api",
|
|
120
141
|
"agentId": "<PROD_AGENT_ID>",
|
|
121
142
|
"apiKey": "<PROD_API_KEY>"
|
|
122
143
|
}
|
|
@@ -135,6 +156,7 @@ openclaw skills list
|
|
|
135
156
|
| `accounts.<id>` | 否 | - | 多账户配置项,`<id>` 自定义(如 `ops`)。 |
|
|
136
157
|
| `name` | 否 | - | 账户显示名。 |
|
|
137
158
|
| `wsUrl` | 是(可用环境变量兜底) | `ws://127.0.0.1:27189/...`(当有 `agentId` 且未填时) | Grix WebSocket 地址。 |
|
|
159
|
+
| `apiBaseUrl` | 否(可用环境变量兜底) | 优先使用显式配置;否则按同一账号的 `wsUrl` 推导;本地 `ws://127.0.0.1:27189/...` 会默认映射成 `http://127.0.0.1:27180/v1/agent-api`;只有账号自己既没配 `apiBaseUrl` 也没配 `wsUrl` 时,才回退环境变量 | Grix HTTP API 地址。开发时可单独指向本地后端。 |
|
|
138
160
|
| `agentId` | 是(可用环境变量兜底) | - | Grix agent ID。 |
|
|
139
161
|
| `apiKey` | 是(可用环境变量兜底) | - | Grix API Key。 |
|
|
140
162
|
| `reconnectMs` | 否 | `2000` | 重连基础延迟(毫秒)。 |
|
|
@@ -160,17 +182,50 @@ openclaw skills list
|
|
|
160
182
|
如果配置文件没填,插件会按下列环境变量读取:
|
|
161
183
|
|
|
162
184
|
- `GRIX_WS_URL`
|
|
185
|
+
- `GRIX_AGENT_API_BASE`
|
|
186
|
+
- `GRIX_WEB_BASE_URL`
|
|
163
187
|
- `GRIX_AGENT_ID`
|
|
164
188
|
- `GRIX_API_KEY`
|
|
165
189
|
|
|
190
|
+
注册脚本默认使用正式环境地址;如果要切到本地或其他部署,可额外设置:
|
|
191
|
+
|
|
192
|
+
- `GRIX_WEB_BASE_URL`
|
|
193
|
+
|
|
194
|
+
说明:
|
|
195
|
+
|
|
196
|
+
- `grix_query`、`grix_group`、`grix_agent_admin` 这些 HTTP 请求会优先使用当前账号自己的 `apiBaseUrl`。
|
|
197
|
+
- 如果当前账号没配 `apiBaseUrl`,会先按当前账号自己的 `wsUrl` 自动推导。
|
|
198
|
+
- 只有当前账号自己既没配 `apiBaseUrl`,也没提供可用的 `wsUrl` 时,才会回退到 `GRIX_AGENT_API_BASE` 或 `GRIX_WEB_BASE_URL`。
|
|
199
|
+
- `skills/grix-register/scripts/grix_auth.py` 会优先读取 `GRIX_WEB_BASE_URL`,再回落到正式环境地址;插件运行时也会把它当作 HTTP 基地址兜底。
|
|
200
|
+
- 多账号混用不同环境时,不建议设置全局 `GRIX_AGENT_API_BASE` / `GRIX_WEB_BASE_URL`,否则容易把一个账号的 HTTP 请求导到另一个环境。
|
|
201
|
+
- 本地开发最稳妥的写法是同时配置:
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"channels": {
|
|
206
|
+
"grix": {
|
|
207
|
+
"wsUrl": "ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=<YOUR_AGENT_ID>",
|
|
208
|
+
"apiBaseUrl": "http://127.0.0.1:27180/v1/agent-api",
|
|
209
|
+
"agentId": "<YOUR_AGENT_ID>",
|
|
210
|
+
"apiKey": "<YOUR_API_KEY>"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
166
216
|
## 工具与命令
|
|
167
217
|
|
|
168
218
|
### Agent 可调用工具
|
|
169
219
|
|
|
170
220
|
- `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`
|
|
221
|
+
- `grix_group`:`create`、`detail`、`leave`、`add_members`、`remove_members`、`update_member_role`、`update_all_members_muted`、`update_member_speaking`、`dissolve`
|
|
172
222
|
- `grix_agent_admin`:创建 `provider_type=3` 的 Grix API agent(只创建远端 agent,不会直接改本地 `channels.grix`)
|
|
173
223
|
|
|
224
|
+
工具调用约束:
|
|
225
|
+
|
|
226
|
+
- 以上三个工具都必须显式传入 `accountId`。
|
|
227
|
+
- 如果工具调用上下文存在当前连接账号,则 `accountId` 必须与上下文账号一致;不一致会直接拒绝执行。
|
|
228
|
+
|
|
174
229
|
### 运维命令
|
|
175
230
|
|
|
176
231
|
查看账户:
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,17 @@ function resolveWsUrl(merged, agentId) {
|
|
|
90
90
|
}
|
|
91
91
|
return `ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=${encodeURIComponent(agentId)}`;
|
|
92
92
|
}
|
|
93
|
+
function resolveAgentAPIBaseUrl(merged) {
|
|
94
|
+
const cfgBase = normalizeNonEmpty(merged.apiBaseUrl);
|
|
95
|
+
if (cfgBase) {
|
|
96
|
+
return cfgBase;
|
|
97
|
+
}
|
|
98
|
+
if (normalizeNonEmpty(merged.wsUrl)) {
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
const envBase = normalizeNonEmpty(process.env.GRIX_AGENT_API_BASE);
|
|
102
|
+
return envBase;
|
|
103
|
+
}
|
|
93
104
|
function redactAibotWsUrl(wsUrl) {
|
|
94
105
|
if (!wsUrl) {
|
|
95
106
|
return "";
|
|
@@ -113,6 +124,7 @@ function resolveAibotAccount(params) {
|
|
|
113
124
|
const agentId = normalizeAgentId(merged.agentId || process.env.GRIX_AGENT_ID);
|
|
114
125
|
const apiKey = normalizeNonEmpty(merged.apiKey || process.env.GRIX_API_KEY);
|
|
115
126
|
const wsUrl = resolveWsUrl(merged, agentId);
|
|
127
|
+
const apiBaseUrl = resolveAgentAPIBaseUrl(merged);
|
|
116
128
|
const configured = Boolean(wsUrl && agentId && apiKey);
|
|
117
129
|
return {
|
|
118
130
|
accountId,
|
|
@@ -120,6 +132,7 @@ function resolveAibotAccount(params) {
|
|
|
120
132
|
enabled,
|
|
121
133
|
configured,
|
|
122
134
|
wsUrl,
|
|
135
|
+
apiBaseUrl,
|
|
123
136
|
agentId,
|
|
124
137
|
apiKey,
|
|
125
138
|
config: merged
|
|
@@ -467,6 +480,9 @@ var AibotWsClient = class {
|
|
|
467
480
|
`reconnect scheduled in ${params.delayMs}ms attempt=${params.attempt} stable=${params.stable} authRejected=${params.authRejected} penaltyFloor=${params.penaltyFloor} suppressed=${suppressed}`
|
|
468
481
|
);
|
|
469
482
|
}
|
|
483
|
+
shouldLogInboundPacket(cmd) {
|
|
484
|
+
return cmd !== "ping" && cmd !== "pong" && cmd !== "send_ack";
|
|
485
|
+
}
|
|
470
486
|
getStatus() {
|
|
471
487
|
return { ...this.status };
|
|
472
488
|
}
|
|
@@ -542,9 +558,6 @@ var AibotWsClient = class {
|
|
|
542
558
|
if (!normalizedChannel || !normalizedAccountID || !normalizedRouteSessionKey || !normalizedSessionID) {
|
|
543
559
|
throw new Error("grix session_route_bind requires channel/account_id/route_session_key/session_id");
|
|
544
560
|
}
|
|
545
|
-
this.logInfo(
|
|
546
|
-
`session_route_bind request channel=${normalizedChannel} accountId=${normalizedAccountID} routeSessionKey=${normalizedRouteSessionKey} sessionId=${normalizedSessionID}`
|
|
547
|
-
);
|
|
548
561
|
const packet = await this.request(
|
|
549
562
|
"session_route_bind",
|
|
550
563
|
{
|
|
@@ -564,9 +577,6 @@ var AibotWsClient = class {
|
|
|
564
577
|
);
|
|
565
578
|
throw this.packetError(packet);
|
|
566
579
|
}
|
|
567
|
-
this.logInfo(
|
|
568
|
-
`session_route_bind ack channel=${normalizedChannel} accountId=${normalizedAccountID} routeSessionKey=${normalizedRouteSessionKey} sessionId=${normalizedSessionID}`
|
|
569
|
-
);
|
|
570
580
|
return packet.payload;
|
|
571
581
|
}
|
|
572
582
|
async resolveSessionRoute(channel, accountId, routeSessionKey, opts = {}) {
|
|
@@ -577,9 +587,6 @@ var AibotWsClient = class {
|
|
|
577
587
|
if (!normalizedChannel || !normalizedAccountID || !normalizedRouteSessionKey) {
|
|
578
588
|
throw new Error("grix session_route_resolve requires channel/account_id/route_session_key");
|
|
579
589
|
}
|
|
580
|
-
this.logInfo(
|
|
581
|
-
`session_route_resolve request channel=${normalizedChannel} accountId=${normalizedAccountID} routeSessionKey=${normalizedRouteSessionKey}`
|
|
582
|
-
);
|
|
583
590
|
const packet = await this.request(
|
|
584
591
|
"session_route_resolve",
|
|
585
592
|
{
|
|
@@ -603,9 +610,6 @@ var AibotWsClient = class {
|
|
|
603
610
|
if (!normalizedSessionID) {
|
|
604
611
|
throw new Error("grix session_route_resolve ack missing session_id");
|
|
605
612
|
}
|
|
606
|
-
this.logInfo(
|
|
607
|
-
`session_route_resolve ack channel=${normalizedChannel} accountId=${normalizedAccountID} routeSessionKey=${normalizedRouteSessionKey} sessionId=${normalizedSessionID}`
|
|
608
|
-
);
|
|
609
613
|
return {
|
|
610
614
|
...payload,
|
|
611
615
|
channel: String(payload.channel ?? normalizedChannel),
|
|
@@ -1058,7 +1062,7 @@ var AibotWsClient = class {
|
|
|
1058
1062
|
}
|
|
1059
1063
|
const cmd = String(packet.cmd ?? "").trim();
|
|
1060
1064
|
const seq = Number(packet.seq ?? 0);
|
|
1061
|
-
if (cmd
|
|
1065
|
+
if (this.shouldLogInboundPacket(cmd)) {
|
|
1062
1066
|
this.logInfo(
|
|
1063
1067
|
`inbound packet conn=${resolvedConnSerial} cmd=${cmd || "-"} seq=${seq} bytes=${text.length}`
|
|
1064
1068
|
);
|
|
@@ -3656,9 +3660,6 @@ async function deliverAibotStreamBlock(params) {
|
|
|
3656
3660
|
messageSid: params.messageSid,
|
|
3657
3661
|
clientMsgId: params.clientMsgId
|
|
3658
3662
|
});
|
|
3659
|
-
params.runtime.log(
|
|
3660
|
-
`[grix:${params.account.accountId}] stream block split into ${chunks.length} chunk(s) ${context} textLen=${params.text.length} chunkDelayMs=${chunkDelayMs}`
|
|
3661
|
-
);
|
|
3662
3663
|
for (let index = 0; index < chunks.length; index++) {
|
|
3663
3664
|
if (params.abortSignal?.aborted) {
|
|
3664
3665
|
params.runtime.log(
|
|
@@ -3671,9 +3672,6 @@ async function deliverAibotStreamBlock(params) {
|
|
|
3671
3672
|
if (!normalized) {
|
|
3672
3673
|
continue;
|
|
3673
3674
|
}
|
|
3674
|
-
params.runtime.log(
|
|
3675
|
-
`[grix:${params.account.accountId}] stream chunk send ${context} chunkIndex=${index + 1}/${chunks.length} deltaLen=${normalized.length}`
|
|
3676
|
-
);
|
|
3677
3675
|
await params.client.sendStreamChunk(params.sessionId, normalized, {
|
|
3678
3676
|
eventId: params.eventId,
|
|
3679
3677
|
clientMsgId: params.clientMsgId,
|
|
@@ -3726,9 +3724,6 @@ async function bindSessionRouteMapping(params) {
|
|
|
3726
3724
|
return;
|
|
3727
3725
|
}
|
|
3728
3726
|
try {
|
|
3729
|
-
params.runtime.log(
|
|
3730
|
-
`[grix:${params.account.accountId}] session route bind begin routeSessionKey=${routeSessionKey} sessionId=${sessionId}`
|
|
3731
|
-
);
|
|
3732
3727
|
await params.client.bindSessionRoute(
|
|
3733
3728
|
"grix",
|
|
3734
3729
|
params.account.accountId,
|
|
@@ -3958,9 +3953,6 @@ async function processEvent(params) {
|
|
|
3958
3953
|
sessionId,
|
|
3959
3954
|
controller: runAbortController
|
|
3960
3955
|
});
|
|
3961
|
-
runtime2.log(
|
|
3962
|
-
`[grix:${account.accountId}] active reply run registered eventId=${eventId || `${sessionId}:${messageSid}`} sessionId=${sessionId} messageSid=${messageSid} activeRun=${activeRun ? "true" : "false"}`
|
|
3963
|
-
);
|
|
3964
3956
|
try {
|
|
3965
3957
|
const route = core.channel.routing.resolveAgentRoute({
|
|
3966
3958
|
cfg: config,
|
|
@@ -4186,22 +4178,10 @@ async function processEvent(params) {
|
|
|
4186
4178
|
}
|
|
4187
4179
|
hasSentBlock = false;
|
|
4188
4180
|
try {
|
|
4189
|
-
const finishContext = buildEventLogContext({
|
|
4190
|
-
eventId,
|
|
4191
|
-
sessionId,
|
|
4192
|
-
messageSid,
|
|
4193
|
-
clientMsgId: streamClientMsgId
|
|
4194
|
-
});
|
|
4195
4181
|
const finishDelayMs = resolveStreamFinishDelayMs(account);
|
|
4196
4182
|
if (finishDelayMs > 0) {
|
|
4197
|
-
runtime2.log(
|
|
4198
|
-
`[grix:${account.accountId}] stream finish delay ${finishContext} delayMs=${finishDelayMs}`
|
|
4199
|
-
);
|
|
4200
4183
|
await sleep2(finishDelayMs);
|
|
4201
4184
|
}
|
|
4202
|
-
runtime2.log(
|
|
4203
|
-
`[grix:${account.accountId}] stream finish ${finishContext}`
|
|
4204
|
-
);
|
|
4205
4185
|
await client.sendStreamChunk(sessionId, "", {
|
|
4206
4186
|
eventId,
|
|
4207
4187
|
clientMsgId: streamClientMsgId,
|
|
@@ -4235,9 +4215,12 @@ async function processEvent(params) {
|
|
|
4235
4215
|
clientMsgId: info.kind === "block" ? streamClientMsgId : `reply_${messageSid}_${outboundCounter}`,
|
|
4236
4216
|
outboundCounter
|
|
4237
4217
|
});
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4218
|
+
const isStreamBlock = info.kind === "block" && !guardedText && !hasMedia && text.length > 0;
|
|
4219
|
+
if (!isStreamBlock) {
|
|
4220
|
+
runtime2.log(
|
|
4221
|
+
`[grix:${account.accountId}] deliver ${deliverContext} kind=${info.kind} textLen=${text.length} hasMedia=${hasMedia} streamedBefore=${streamedTextAlreadyVisible}`
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4241
4224
|
if (guardedText) {
|
|
4242
4225
|
runtime2.error(
|
|
4243
4226
|
`[grix:${account.accountId}] rewrite internal reply text ${deliverContext} code=${guardedText.code} raw=${JSON.stringify(guardedText.rawText)}`
|
|
@@ -4256,7 +4239,7 @@ async function processEvent(params) {
|
|
|
4256
4239
|
);
|
|
4257
4240
|
return;
|
|
4258
4241
|
}
|
|
4259
|
-
if (
|
|
4242
|
+
if (isStreamBlock) {
|
|
4260
4243
|
const didSendBlock = await deliverAibotStreamBlock({
|
|
4261
4244
|
text,
|
|
4262
4245
|
client,
|
|
@@ -4407,7 +4390,7 @@ async function processEvent(params) {
|
|
|
4407
4390
|
}
|
|
4408
4391
|
} finally {
|
|
4409
4392
|
runtime2.log(
|
|
4410
|
-
`[grix:${account.accountId}] active reply run clearing eventId=${activeRun?.eventId || "-"}
|
|
4393
|
+
`[grix:${account.accountId}] active reply run clearing eventId=${activeRun?.eventId || "-"} stopRequested=${activeRun?.stopRequested === true} abortReason=${activeRun ? resolveAbortReason(activeRun.controller.signal) : "-"} visibleOutputSent=${visibleOutputSent}`
|
|
4411
4394
|
);
|
|
4412
4395
|
clearActiveReplyRun(activeRun);
|
|
4413
4396
|
if (!inboundEventAccepted) {
|
|
@@ -4602,7 +4585,7 @@ var meta = {
|
|
|
4602
4585
|
label: "Grix",
|
|
4603
4586
|
selectionLabel: "Grix",
|
|
4604
4587
|
docsPath: "/channels/grix",
|
|
4605
|
-
blurb: "Connect OpenClaw to
|
|
4588
|
+
blurb: "Connect OpenClaw to a Grix deployment for website management with mobile PWA support.",
|
|
4606
4589
|
aliases: ["gr"],
|
|
4607
4590
|
order: 90
|
|
4608
4591
|
};
|
|
@@ -4715,6 +4698,7 @@ var aibotPlugin = {
|
|
|
4715
4698
|
clearBaseFields: [
|
|
4716
4699
|
"name",
|
|
4717
4700
|
"wsUrl",
|
|
4701
|
+
"apiBaseUrl",
|
|
4718
4702
|
"agentId",
|
|
4719
4703
|
"apiKey",
|
|
4720
4704
|
"reconnectMs",
|
|
@@ -5309,6 +5293,17 @@ function buildGroupMemberAddRequest(params) {
|
|
|
5309
5293
|
body
|
|
5310
5294
|
};
|
|
5311
5295
|
}
|
|
5296
|
+
function buildGroupLeaveSelfRequest(params) {
|
|
5297
|
+
const sessionID = readRequiredStringParam(params, "sessionId");
|
|
5298
|
+
return {
|
|
5299
|
+
actionName: "group_leave_self",
|
|
5300
|
+
method: "POST",
|
|
5301
|
+
path: "/sessions/leave",
|
|
5302
|
+
body: {
|
|
5303
|
+
session_id: sessionID
|
|
5304
|
+
}
|
|
5305
|
+
};
|
|
5306
|
+
}
|
|
5312
5307
|
function buildGroupMemberRemoveRequest(params) {
|
|
5313
5308
|
const sessionID = readRequiredStringParam(params, "sessionId");
|
|
5314
5309
|
const memberIDs = readNumericIDArray(params, "memberIds", true);
|
|
@@ -5516,6 +5511,8 @@ function buildAgentHTTPRequest(action, params) {
|
|
|
5516
5511
|
return buildMessageHistoryRequest(params);
|
|
5517
5512
|
case "group_create":
|
|
5518
5513
|
return buildGroupCreateRequest(params);
|
|
5514
|
+
case "group_leave_self":
|
|
5515
|
+
return buildGroupLeaveSelfRequest(params);
|
|
5519
5516
|
case "group_member_add":
|
|
5520
5517
|
return buildGroupMemberAddRequest(params);
|
|
5521
5518
|
case "group_member_remove":
|
|
@@ -5539,13 +5536,19 @@ function buildAgentHTTPRequest(action, params) {
|
|
|
5539
5536
|
|
|
5540
5537
|
// src/admin/agent-api-http.ts
|
|
5541
5538
|
var DEFAULT_HTTP_TIMEOUT_MS = 15e3;
|
|
5539
|
+
var MAX_LOG_KEYS = 8;
|
|
5540
|
+
var MAX_LOG_PAYLOAD_CHARS = 1200;
|
|
5542
5541
|
function trimTrailingSlash(value) {
|
|
5543
5542
|
return value.replace(/\/+$/, "");
|
|
5544
5543
|
}
|
|
5544
|
+
function logAgentAPIInfo(message) {
|
|
5545
|
+
console.info(`[grix:agent-api] ${message}`);
|
|
5546
|
+
}
|
|
5547
|
+
function logAgentAPIError(message) {
|
|
5548
|
+
console.error(`[grix:agent-api] ${message}`);
|
|
5549
|
+
}
|
|
5545
5550
|
function resolveExplicitAgentAPIBase() {
|
|
5546
|
-
const base = String(
|
|
5547
|
-
process.env.GRIX_AGENT_API_BASE ?? process.env.AIBOT_AGENT_API_BASE ?? ""
|
|
5548
|
-
).trim();
|
|
5551
|
+
const base = String(process.env.GRIX_AGENT_API_BASE ?? "").trim();
|
|
5549
5552
|
if (!base) {
|
|
5550
5553
|
return "";
|
|
5551
5554
|
}
|
|
@@ -5595,16 +5598,39 @@ function deriveLocalAgentAPIBaseFromWsUrl(wsUrl) {
|
|
|
5595
5598
|
const protocol = parsed.protocol === "wss:" ? "https:" : "http:";
|
|
5596
5599
|
return trimTrailingSlash(`${protocol}//${parsed.hostname}:${apiPort}`) + "/v1/agent-api";
|
|
5597
5600
|
}
|
|
5598
|
-
function
|
|
5599
|
-
const
|
|
5600
|
-
if (
|
|
5601
|
-
return
|
|
5601
|
+
function resolveAgentAPIBaseInfo(account) {
|
|
5602
|
+
const accountBase = trimTrailingSlash(String(account.apiBaseUrl ?? "").trim());
|
|
5603
|
+
if (accountBase) {
|
|
5604
|
+
return {
|
|
5605
|
+
base: accountBase,
|
|
5606
|
+
source: "account_api_base_url"
|
|
5607
|
+
};
|
|
5602
5608
|
}
|
|
5603
|
-
const
|
|
5609
|
+
const normalizedWsUrl = String(account.wsUrl ?? "").trim();
|
|
5610
|
+
const local = deriveLocalAgentAPIBaseFromWsUrl(normalizedWsUrl);
|
|
5604
5611
|
if (local) {
|
|
5605
|
-
return
|
|
5612
|
+
return {
|
|
5613
|
+
base: local,
|
|
5614
|
+
source: "local_ws_url"
|
|
5615
|
+
};
|
|
5616
|
+
}
|
|
5617
|
+
if (normalizedWsUrl) {
|
|
5618
|
+
return {
|
|
5619
|
+
base: deriveAgentAPIBaseFromWsUrl(normalizedWsUrl),
|
|
5620
|
+
source: "derived_from_ws_url"
|
|
5621
|
+
};
|
|
5622
|
+
}
|
|
5623
|
+
const explicit = resolveExplicitAgentAPIBase();
|
|
5624
|
+
if (explicit) {
|
|
5625
|
+
return {
|
|
5626
|
+
base: explicit,
|
|
5627
|
+
source: "env_grix_agent_api_base"
|
|
5628
|
+
};
|
|
5606
5629
|
}
|
|
5607
|
-
return
|
|
5630
|
+
return {
|
|
5631
|
+
base: deriveAgentAPIBaseFromWsUrl(normalizedWsUrl),
|
|
5632
|
+
source: "derived_from_ws_url"
|
|
5633
|
+
};
|
|
5608
5634
|
}
|
|
5609
5635
|
function buildRequestURL(base, path, query) {
|
|
5610
5636
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -5647,10 +5673,105 @@ function extractNetworkErrorMessage(error) {
|
|
|
5647
5673
|
}
|
|
5648
5674
|
return String(error);
|
|
5649
5675
|
}
|
|
5676
|
+
function buildAPIKeyState(apiKey) {
|
|
5677
|
+
const normalized = String(apiKey ?? "").trim();
|
|
5678
|
+
if (!normalized) {
|
|
5679
|
+
return "empty";
|
|
5680
|
+
}
|
|
5681
|
+
return "present";
|
|
5682
|
+
}
|
|
5683
|
+
function summarizePayloadKeys(payload) {
|
|
5684
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
5685
|
+
return "none";
|
|
5686
|
+
}
|
|
5687
|
+
const keys = Object.keys(payload).map((k) => String(k).trim()).filter(Boolean).sort();
|
|
5688
|
+
if (!keys.length) {
|
|
5689
|
+
return "none";
|
|
5690
|
+
}
|
|
5691
|
+
const limited = keys.slice(0, MAX_LOG_KEYS);
|
|
5692
|
+
if (keys.length <= MAX_LOG_KEYS) {
|
|
5693
|
+
return limited.join(",");
|
|
5694
|
+
}
|
|
5695
|
+
return `${limited.join(",")}...(total=${keys.length})`;
|
|
5696
|
+
}
|
|
5697
|
+
function summarizePayloadBytes(payload) {
|
|
5698
|
+
try {
|
|
5699
|
+
return String(Buffer.byteLength(JSON.stringify(payload ?? {}), "utf8"));
|
|
5700
|
+
} catch {
|
|
5701
|
+
return "unknown";
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
function isSensitiveLogKey(key) {
|
|
5705
|
+
const normalized = String(key ?? "").trim().toLowerCase();
|
|
5706
|
+
return normalized.includes("api_key") || normalized.includes("apikey") || normalized.includes("token") || normalized.includes("authorization") || normalized.includes("password") || normalized.includes("secret");
|
|
5707
|
+
}
|
|
5708
|
+
function sanitizePayloadForLog(payload, depth = 0) {
|
|
5709
|
+
if (depth >= 5) {
|
|
5710
|
+
return "[max-depth]";
|
|
5711
|
+
}
|
|
5712
|
+
if (payload == null) {
|
|
5713
|
+
return payload;
|
|
5714
|
+
}
|
|
5715
|
+
if (typeof payload === "string" || typeof payload === "number" || typeof payload === "boolean") {
|
|
5716
|
+
return payload;
|
|
5717
|
+
}
|
|
5718
|
+
if (Array.isArray(payload)) {
|
|
5719
|
+
return payload.map((item) => sanitizePayloadForLog(item, depth + 1));
|
|
5720
|
+
}
|
|
5721
|
+
if (typeof payload === "object") {
|
|
5722
|
+
const raw = payload;
|
|
5723
|
+
const sanitized = {};
|
|
5724
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
5725
|
+
sanitized[key] = isSensitiveLogKey(key) ? "<redacted>" : sanitizePayloadForLog(value, depth + 1);
|
|
5726
|
+
}
|
|
5727
|
+
return sanitized;
|
|
5728
|
+
}
|
|
5729
|
+
return String(payload);
|
|
5730
|
+
}
|
|
5731
|
+
function stringifyPayloadForLog(payload) {
|
|
5732
|
+
let json = "";
|
|
5733
|
+
try {
|
|
5734
|
+
json = JSON.stringify(sanitizePayloadForLog(payload));
|
|
5735
|
+
} catch {
|
|
5736
|
+
return '"[unserializable]"';
|
|
5737
|
+
}
|
|
5738
|
+
if (!json) {
|
|
5739
|
+
return "{}";
|
|
5740
|
+
}
|
|
5741
|
+
if (json.length <= MAX_LOG_PAYLOAD_CHARS) {
|
|
5742
|
+
return json;
|
|
5743
|
+
}
|
|
5744
|
+
return `${json.slice(0, MAX_LOG_PAYLOAD_CHARS)}...(truncated,len=${json.length})`;
|
|
5745
|
+
}
|
|
5746
|
+
function buildRequestLogContext(params, context) {
|
|
5747
|
+
const queryPayload = params.query ?? {};
|
|
5748
|
+
const bodyPayload = params.method === "POST" ? params.body ?? {} : {};
|
|
5749
|
+
return [
|
|
5750
|
+
`action=${params.actionName}`,
|
|
5751
|
+
`account=${params.account.accountId}`,
|
|
5752
|
+
`agent=${params.account.agentId}`,
|
|
5753
|
+
`method=${params.method}`,
|
|
5754
|
+
`source=${context.resolvedBase.source}`,
|
|
5755
|
+
`url=${context.url}`,
|
|
5756
|
+
`timeout_ms=${context.timeoutMs}`,
|
|
5757
|
+
`api_key=${buildAPIKeyState(params.account.apiKey)}`,
|
|
5758
|
+
`query_keys=${summarizePayloadKeys(queryPayload)}`,
|
|
5759
|
+
`query_payload=${JSON.stringify(stringifyPayloadForLog(queryPayload))}`,
|
|
5760
|
+
`body_keys=${summarizePayloadKeys(bodyPayload)}`,
|
|
5761
|
+
`body_payload=${JSON.stringify(stringifyPayloadForLog(bodyPayload))}`,
|
|
5762
|
+
`body_bytes=${summarizePayloadBytes(bodyPayload)}`
|
|
5763
|
+
].join(" ");
|
|
5764
|
+
}
|
|
5650
5765
|
async function callAgentAPI(params) {
|
|
5651
|
-
const
|
|
5652
|
-
const url = buildRequestURL(base, params.path, params.query);
|
|
5766
|
+
const resolvedBase = resolveAgentAPIBaseInfo(params.account);
|
|
5767
|
+
const url = buildRequestURL(resolvedBase.base, params.path, params.query);
|
|
5653
5768
|
const timeoutMs = Number.isFinite(params.timeoutMs) ? Math.max(1e3, Math.floor(params.timeoutMs)) : DEFAULT_HTTP_TIMEOUT_MS;
|
|
5769
|
+
const requestLogContext = buildRequestLogContext(params, {
|
|
5770
|
+
resolvedBase,
|
|
5771
|
+
url,
|
|
5772
|
+
timeoutMs
|
|
5773
|
+
});
|
|
5774
|
+
logAgentAPIInfo(`request ${requestLogContext}`);
|
|
5654
5775
|
const controller = new AbortController();
|
|
5655
5776
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5656
5777
|
let resp;
|
|
@@ -5666,6 +5787,9 @@ async function callAgentAPI(params) {
|
|
|
5666
5787
|
});
|
|
5667
5788
|
} catch (error) {
|
|
5668
5789
|
clearTimeout(timer);
|
|
5790
|
+
logAgentAPIError(
|
|
5791
|
+
`network_error ${requestLogContext} error=${JSON.stringify(extractNetworkErrorMessage(error))}`
|
|
5792
|
+
);
|
|
5669
5793
|
throw new Error(
|
|
5670
5794
|
`Grix ${params.actionName} network error: ${extractNetworkErrorMessage(error)}`
|
|
5671
5795
|
);
|
|
@@ -5677,6 +5801,9 @@ async function callAgentAPI(params) {
|
|
|
5677
5801
|
try {
|
|
5678
5802
|
envelope = JSON.parse(rawBody);
|
|
5679
5803
|
} catch {
|
|
5804
|
+
logAgentAPIError(
|
|
5805
|
+
`invalid_response ${requestLogContext} status=${status} raw_len=${rawBody.length}`
|
|
5806
|
+
);
|
|
5680
5807
|
throw new Error(
|
|
5681
5808
|
`Grix ${params.actionName} invalid response: status=${status} body=${rawBody.slice(0, 256)}`
|
|
5682
5809
|
);
|
|
@@ -5684,13 +5811,41 @@ async function callAgentAPI(params) {
|
|
|
5684
5811
|
const bizCode = normalizeBizCode(envelope.code);
|
|
5685
5812
|
if (!resp.ok || bizCode !== 0) {
|
|
5686
5813
|
const message = normalizeMessage(envelope.msg);
|
|
5814
|
+
logAgentAPIError(
|
|
5815
|
+
`failed ${requestLogContext} status=${status} code=${bizCode} msg=${message} has_data=${envelope.data == null ? "false" : "true"}`
|
|
5816
|
+
);
|
|
5687
5817
|
throw new Error(
|
|
5688
5818
|
`Grix ${params.actionName} failed: status=${status} code=${bizCode} msg=${message}`
|
|
5689
5819
|
);
|
|
5690
5820
|
}
|
|
5821
|
+
logAgentAPIInfo(`success ${requestLogContext} status=${status}`);
|
|
5691
5822
|
return envelope.data;
|
|
5692
5823
|
}
|
|
5693
5824
|
|
|
5825
|
+
// src/admin/account-binding.ts
|
|
5826
|
+
function normalizeNonEmpty2(value) {
|
|
5827
|
+
const normalized = String(value ?? "").trim();
|
|
5828
|
+
return normalized || void 0;
|
|
5829
|
+
}
|
|
5830
|
+
function resolveStrictToolAccountId(params) {
|
|
5831
|
+
const toolAccountId = normalizeNonEmpty2(params.toolAccountId);
|
|
5832
|
+
const contextAccountId = normalizeNonEmpty2(params.contextAccountId);
|
|
5833
|
+
console.info(
|
|
5834
|
+
`[grix:account-binding] tool=${params.toolName} request_account=${toolAccountId ?? "-"} context_account=${contextAccountId ?? "-"}`
|
|
5835
|
+
);
|
|
5836
|
+
if (!toolAccountId) {
|
|
5837
|
+
throw new Error(
|
|
5838
|
+
`[${params.toolName}] accountId is required. Pass the exact accountId of the current connection.`
|
|
5839
|
+
);
|
|
5840
|
+
}
|
|
5841
|
+
if (contextAccountId && toolAccountId !== contextAccountId) {
|
|
5842
|
+
throw new Error(
|
|
5843
|
+
`[${params.toolName}] accountId mismatch. request=${toolAccountId}, context=${contextAccountId}. Refusing cross-account execution.`
|
|
5844
|
+
);
|
|
5845
|
+
}
|
|
5846
|
+
return toolAccountId;
|
|
5847
|
+
}
|
|
5848
|
+
|
|
5694
5849
|
// src/admin/accounts.ts
|
|
5695
5850
|
var DEFAULT_ACCOUNT_ID2 = "default";
|
|
5696
5851
|
function normalizeAccountId2(value) {
|
|
@@ -5711,9 +5866,24 @@ function listConfiguredAccountIds2(cfg) {
|
|
|
5711
5866
|
}
|
|
5712
5867
|
return Object.keys(accounts).filter(Boolean);
|
|
5713
5868
|
}
|
|
5714
|
-
function
|
|
5869
|
+
function normalizeNonEmpty3(value) {
|
|
5715
5870
|
return String(value ?? "").trim();
|
|
5716
5871
|
}
|
|
5872
|
+
function trimTrailingSlash2(value) {
|
|
5873
|
+
return value.replace(/\/+$/, "");
|
|
5874
|
+
}
|
|
5875
|
+
function summarizeEndpoint(value) {
|
|
5876
|
+
const normalized = normalizeNonEmpty3(value);
|
|
5877
|
+
if (!normalized) {
|
|
5878
|
+
return "-";
|
|
5879
|
+
}
|
|
5880
|
+
try {
|
|
5881
|
+
const parsed = new URL(normalized);
|
|
5882
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
5883
|
+
} catch {
|
|
5884
|
+
return normalized.slice(0, 128);
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5717
5887
|
function appendAgentIdToWsUrl2(rawWsUrl, agentId) {
|
|
5718
5888
|
if (!rawWsUrl) {
|
|
5719
5889
|
return "";
|
|
@@ -5736,8 +5906,8 @@ function appendAgentIdToWsUrl2(rawWsUrl, agentId) {
|
|
|
5736
5906
|
}
|
|
5737
5907
|
}
|
|
5738
5908
|
function resolveWsUrl2(merged, agentId) {
|
|
5739
|
-
const envWs =
|
|
5740
|
-
const cfgWs =
|
|
5909
|
+
const envWs = normalizeNonEmpty3(process.env.GRIX_WS_URL);
|
|
5910
|
+
const cfgWs = normalizeNonEmpty3(merged.wsUrl);
|
|
5741
5911
|
const ws = cfgWs || envWs;
|
|
5742
5912
|
if (ws) {
|
|
5743
5913
|
return appendAgentIdToWsUrl2(ws, agentId);
|
|
@@ -5747,6 +5917,44 @@ function resolveWsUrl2(merged, agentId) {
|
|
|
5747
5917
|
}
|
|
5748
5918
|
return `ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=${encodeURIComponent(agentId)}`;
|
|
5749
5919
|
}
|
|
5920
|
+
function resolveAgentAPIBaseUrl2(merged) {
|
|
5921
|
+
const cfgBase = trimTrailingSlash2(normalizeNonEmpty3(merged.apiBaseUrl));
|
|
5922
|
+
if (cfgBase) {
|
|
5923
|
+
return cfgBase;
|
|
5924
|
+
}
|
|
5925
|
+
if (normalizeNonEmpty3(merged.wsUrl)) {
|
|
5926
|
+
return "";
|
|
5927
|
+
}
|
|
5928
|
+
const envBase = trimTrailingSlash2(normalizeNonEmpty3(process.env.GRIX_AGENT_API_BASE));
|
|
5929
|
+
const webBase = trimTrailingSlash2(normalizeNonEmpty3(process.env.GRIX_WEB_BASE_URL));
|
|
5930
|
+
return envBase || webBase;
|
|
5931
|
+
}
|
|
5932
|
+
function resolveStrictAccountConfig(cfg, accountId) {
|
|
5933
|
+
const grixCfg = rawGrixConfig(cfg);
|
|
5934
|
+
const accounts = grixCfg.accounts;
|
|
5935
|
+
if (!accounts || typeof accounts !== "object") {
|
|
5936
|
+
console.error(
|
|
5937
|
+
`[grix:account] strict lookup failed account=${accountId} reason=accounts_map_missing`
|
|
5938
|
+
);
|
|
5939
|
+
throw new Error(
|
|
5940
|
+
`Grix account "${accountId}" is not configured under channels.grix.accounts.`
|
|
5941
|
+
);
|
|
5942
|
+
}
|
|
5943
|
+
const configuredIds = Object.keys(accounts).filter(Boolean).sort();
|
|
5944
|
+
const account = accounts[accountId];
|
|
5945
|
+
if (!account || typeof account !== "object") {
|
|
5946
|
+
console.error(
|
|
5947
|
+
`[grix:account] strict lookup failed account=${accountId} reason=account_missing configured_accounts=${configuredIds.join(",") || "none"}`
|
|
5948
|
+
);
|
|
5949
|
+
throw new Error(
|
|
5950
|
+
`Grix account "${accountId}" is missing under channels.grix.accounts.${accountId}.`
|
|
5951
|
+
);
|
|
5952
|
+
}
|
|
5953
|
+
console.info(
|
|
5954
|
+
`[grix:account] strict lookup account=${accountId} configured_accounts=${configuredIds.join(",") || "none"} has_ws=${normalizeNonEmpty3(account.wsUrl) ? "yes" : "no"} has_api_base=${normalizeNonEmpty3(account.apiBaseUrl) ? "yes" : "no"} has_agent_id=${normalizeNonEmpty3(account.agentId) ? "yes" : "no"} has_api_key=${normalizeNonEmpty3(account.apiKey) ? "yes" : "no"}`
|
|
5955
|
+
);
|
|
5956
|
+
return account;
|
|
5957
|
+
}
|
|
5750
5958
|
function resolveMergedAccountConfig(cfg, accountId) {
|
|
5751
5959
|
const grixCfg = rawGrixConfig(cfg);
|
|
5752
5960
|
const { accounts: _ignoredAccounts, defaultAccount: _ignoredDefault, ...base } = grixCfg;
|
|
@@ -5776,21 +5984,29 @@ function resolveDefaultGrixAccountId(cfg) {
|
|
|
5776
5984
|
return ids[0] ?? DEFAULT_ACCOUNT_ID2;
|
|
5777
5985
|
}
|
|
5778
5986
|
function resolveGrixAccount(params) {
|
|
5987
|
+
const strictScope = Boolean(params.strictAccountScope);
|
|
5779
5988
|
const accountId = params.accountId == null || String(params.accountId).trim() === "" ? resolveDefaultGrixAccountId(params.cfg) : normalizeAccountId2(params.accountId);
|
|
5780
|
-
const merged = resolveMergedAccountConfig(params.cfg, accountId);
|
|
5989
|
+
const merged = strictScope ? resolveStrictAccountConfig(params.cfg, accountId) : resolveMergedAccountConfig(params.cfg, accountId);
|
|
5781
5990
|
const baseEnabled = rawGrixConfig(params.cfg).enabled !== false;
|
|
5782
5991
|
const accountEnabled = merged.enabled !== false;
|
|
5783
5992
|
const enabled = baseEnabled && accountEnabled;
|
|
5784
|
-
const agentId =
|
|
5785
|
-
const apiKey =
|
|
5786
|
-
const wsUrl = resolveWsUrl2(merged, agentId);
|
|
5993
|
+
const agentId = strictScope ? normalizeNonEmpty3(merged.agentId) : normalizeNonEmpty3(merged.agentId || process.env.GRIX_AGENT_ID);
|
|
5994
|
+
const apiKey = strictScope ? normalizeNonEmpty3(merged.apiKey) : normalizeNonEmpty3(merged.apiKey || process.env.GRIX_API_KEY);
|
|
5995
|
+
const wsUrl = strictScope ? appendAgentIdToWsUrl2(normalizeNonEmpty3(merged.wsUrl), agentId) : resolveWsUrl2(merged, agentId);
|
|
5996
|
+
const apiBaseUrl = strictScope ? trimTrailingSlash2(normalizeNonEmpty3(merged.apiBaseUrl)) : resolveAgentAPIBaseUrl2(merged);
|
|
5787
5997
|
const configured = Boolean(wsUrl && agentId && apiKey);
|
|
5998
|
+
if (strictScope) {
|
|
5999
|
+
console.info(
|
|
6000
|
+
`[grix:account] strict resolved account=${accountId} enabled=${enabled} configured=${configured} ws_endpoint=${summarizeEndpoint(wsUrl)} api_base_endpoint=${summarizeEndpoint(apiBaseUrl)} agent_id=${agentId || "-"} api_key=${apiKey ? "present" : "empty"}`
|
|
6001
|
+
);
|
|
6002
|
+
}
|
|
5788
6003
|
return {
|
|
5789
6004
|
accountId,
|
|
5790
|
-
name:
|
|
6005
|
+
name: normalizeNonEmpty3(merged.name) || void 0,
|
|
5791
6006
|
enabled,
|
|
5792
6007
|
configured,
|
|
5793
6008
|
wsUrl,
|
|
6009
|
+
apiBaseUrl,
|
|
5794
6010
|
agentId,
|
|
5795
6011
|
apiKey,
|
|
5796
6012
|
config: merged
|
|
@@ -5805,6 +6021,7 @@ function summarizeGrixAccounts(cfg) {
|
|
|
5805
6021
|
enabled: account.enabled,
|
|
5806
6022
|
configured: account.configured,
|
|
5807
6023
|
wsUrl: account.wsUrl || null,
|
|
6024
|
+
apiBaseUrl: account.apiBaseUrl || null,
|
|
5808
6025
|
agentId: account.agentId || null
|
|
5809
6026
|
};
|
|
5810
6027
|
});
|
|
@@ -5842,9 +6059,15 @@ function sanitizeCreatedAgentData(data) {
|
|
|
5842
6059
|
return payload;
|
|
5843
6060
|
}
|
|
5844
6061
|
async function createGrixApiAgent(params) {
|
|
6062
|
+
const accountId = resolveStrictToolAccountId({
|
|
6063
|
+
toolName: "grix_agent_admin",
|
|
6064
|
+
toolAccountId: params.toolParams.accountId,
|
|
6065
|
+
contextAccountId: params.contextAccountId
|
|
6066
|
+
});
|
|
5845
6067
|
const account = resolveGrixAccount({
|
|
5846
6068
|
cfg: params.cfg,
|
|
5847
|
-
accountId
|
|
6069
|
+
accountId,
|
|
6070
|
+
strictAccountScope: true
|
|
5848
6071
|
});
|
|
5849
6072
|
if (!account.enabled) {
|
|
5850
6073
|
throw new Error(`Grix account "${account.accountId}" is disabled.`);
|
|
@@ -5968,9 +6191,10 @@ var GrixAgentAdminToolSchema = {
|
|
|
5968
6191
|
required: ["actions"]
|
|
5969
6192
|
}
|
|
5970
6193
|
},
|
|
5971
|
-
required: ["agentName", "describeMessageTool"]
|
|
6194
|
+
required: ["accountId", "agentName", "describeMessageTool"]
|
|
5972
6195
|
};
|
|
5973
|
-
function createGrixAgentAdminTool(api) {
|
|
6196
|
+
function createGrixAgentAdminTool(api, ctx) {
|
|
6197
|
+
const contextAccountId = ctx?.agentAccountId;
|
|
5974
6198
|
return {
|
|
5975
6199
|
name: "grix_agent_admin",
|
|
5976
6200
|
label: "Grix Agent Admin",
|
|
@@ -5981,7 +6205,8 @@ function createGrixAgentAdminTool(api) {
|
|
|
5981
6205
|
return jsonToolResult(
|
|
5982
6206
|
await createGrixApiAgent({
|
|
5983
6207
|
cfg: api.config,
|
|
5984
|
-
toolParams: params
|
|
6208
|
+
toolParams: params,
|
|
6209
|
+
contextAccountId
|
|
5985
6210
|
})
|
|
5986
6211
|
);
|
|
5987
6212
|
} catch (err) {
|
|
@@ -6000,6 +6225,8 @@ function mapGroupActionToRequestAction(action) {
|
|
|
6000
6225
|
return "group_create";
|
|
6001
6226
|
case "detail":
|
|
6002
6227
|
return "group_detail_read";
|
|
6228
|
+
case "leave":
|
|
6229
|
+
return "group_leave_self";
|
|
6003
6230
|
case "add_members":
|
|
6004
6231
|
return "group_member_add";
|
|
6005
6232
|
case "remove_members":
|
|
@@ -6018,9 +6245,15 @@ function mapGroupActionToRequestAction(action) {
|
|
|
6018
6245
|
}
|
|
6019
6246
|
}
|
|
6020
6247
|
async function runGrixGroupAction(params) {
|
|
6248
|
+
const accountId = resolveStrictToolAccountId({
|
|
6249
|
+
toolName: "grix_group",
|
|
6250
|
+
toolAccountId: params.toolParams.accountId,
|
|
6251
|
+
contextAccountId: params.contextAccountId
|
|
6252
|
+
});
|
|
6021
6253
|
const account = resolveGrixAccount({
|
|
6022
6254
|
cfg: params.cfg,
|
|
6023
|
-
accountId
|
|
6255
|
+
accountId,
|
|
6256
|
+
strictAccountScope: true
|
|
6024
6257
|
});
|
|
6025
6258
|
if (!account.enabled) {
|
|
6026
6259
|
throw new Error(`Grix account "${account.accountId}" is disabled.`);
|
|
@@ -6038,6 +6271,13 @@ async function runGrixGroupAction(params) {
|
|
|
6038
6271
|
query: request.query,
|
|
6039
6272
|
body: request.body
|
|
6040
6273
|
});
|
|
6274
|
+
if (params.toolParams.action === "leave") {
|
|
6275
|
+
const d = data;
|
|
6276
|
+
const left = d != null && typeof d === "object" ? d["left"] : void 0;
|
|
6277
|
+
console.info(
|
|
6278
|
+
`[grix:group] leave result account=${account.accountId} agent=${account.agentId} session=${String(params.toolParams.sessionId ?? "")} left=${left}`
|
|
6279
|
+
);
|
|
6280
|
+
}
|
|
6041
6281
|
return {
|
|
6042
6282
|
ok: true,
|
|
6043
6283
|
accountId: account.accountId,
|
|
@@ -6063,7 +6303,7 @@ var GrixGroupToolSchema = {
|
|
|
6063
6303
|
memberIds: { type: "array", items: numericIdSchema },
|
|
6064
6304
|
memberTypes: { type: "array", items: { type: "integer", enum: [1, 2] } }
|
|
6065
6305
|
},
|
|
6066
|
-
required: ["action", "name"]
|
|
6306
|
+
required: ["action", "accountId", "name"]
|
|
6067
6307
|
},
|
|
6068
6308
|
{
|
|
6069
6309
|
type: "object",
|
|
@@ -6073,7 +6313,17 @@ var GrixGroupToolSchema = {
|
|
|
6073
6313
|
accountId: { type: "string", minLength: 1 },
|
|
6074
6314
|
sessionId: { type: "string", minLength: 1 }
|
|
6075
6315
|
},
|
|
6076
|
-
required: ["action", "sessionId"]
|
|
6316
|
+
required: ["action", "accountId", "sessionId"]
|
|
6317
|
+
},
|
|
6318
|
+
{
|
|
6319
|
+
type: "object",
|
|
6320
|
+
additionalProperties: false,
|
|
6321
|
+
properties: {
|
|
6322
|
+
action: { const: "leave" },
|
|
6323
|
+
accountId: { type: "string", minLength: 1 },
|
|
6324
|
+
sessionId: { type: "string", minLength: 1 }
|
|
6325
|
+
},
|
|
6326
|
+
required: ["action", "accountId", "sessionId"]
|
|
6077
6327
|
},
|
|
6078
6328
|
{
|
|
6079
6329
|
type: "object",
|
|
@@ -6085,7 +6335,7 @@ var GrixGroupToolSchema = {
|
|
|
6085
6335
|
memberIds: { type: "array", items: numericIdSchema, minItems: 1 },
|
|
6086
6336
|
memberTypes: { type: "array", items: { type: "integer", enum: [1, 2] } }
|
|
6087
6337
|
},
|
|
6088
|
-
required: ["action", "sessionId", "memberIds"]
|
|
6338
|
+
required: ["action", "accountId", "sessionId", "memberIds"]
|
|
6089
6339
|
},
|
|
6090
6340
|
{
|
|
6091
6341
|
type: "object",
|
|
@@ -6097,7 +6347,7 @@ var GrixGroupToolSchema = {
|
|
|
6097
6347
|
memberIds: { type: "array", items: numericIdSchema, minItems: 1 },
|
|
6098
6348
|
memberTypes: { type: "array", items: { type: "integer", enum: [1, 2] } }
|
|
6099
6349
|
},
|
|
6100
|
-
required: ["action", "sessionId", "memberIds"]
|
|
6350
|
+
required: ["action", "accountId", "sessionId", "memberIds"]
|
|
6101
6351
|
},
|
|
6102
6352
|
{
|
|
6103
6353
|
type: "object",
|
|
@@ -6110,7 +6360,7 @@ var GrixGroupToolSchema = {
|
|
|
6110
6360
|
memberType: { type: "integer", enum: [1] },
|
|
6111
6361
|
role: { type: "integer", enum: [1, 2] }
|
|
6112
6362
|
},
|
|
6113
|
-
required: ["action", "sessionId", "memberId", "role"]
|
|
6363
|
+
required: ["action", "accountId", "sessionId", "memberId", "role"]
|
|
6114
6364
|
},
|
|
6115
6365
|
{
|
|
6116
6366
|
type: "object",
|
|
@@ -6121,7 +6371,7 @@ var GrixGroupToolSchema = {
|
|
|
6121
6371
|
sessionId: { type: "string", minLength: 1 },
|
|
6122
6372
|
allMembersMuted: { type: "boolean" }
|
|
6123
6373
|
},
|
|
6124
|
-
required: ["action", "sessionId", "allMembersMuted"]
|
|
6374
|
+
required: ["action", "accountId", "sessionId", "allMembersMuted"]
|
|
6125
6375
|
},
|
|
6126
6376
|
{
|
|
6127
6377
|
type: "object",
|
|
@@ -6135,7 +6385,7 @@ var GrixGroupToolSchema = {
|
|
|
6135
6385
|
isSpeakMuted: { type: "boolean" },
|
|
6136
6386
|
canSpeakWhenAllMuted: { type: "boolean" }
|
|
6137
6387
|
},
|
|
6138
|
-
required: ["action", "sessionId", "memberId"],
|
|
6388
|
+
required: ["action", "accountId", "sessionId", "memberId"],
|
|
6139
6389
|
anyOf: [
|
|
6140
6390
|
{ required: ["isSpeakMuted"] },
|
|
6141
6391
|
{ required: ["canSpeakWhenAllMuted"] }
|
|
@@ -6149,11 +6399,12 @@ var GrixGroupToolSchema = {
|
|
|
6149
6399
|
accountId: { type: "string", minLength: 1 },
|
|
6150
6400
|
sessionId: { type: "string", minLength: 1 }
|
|
6151
6401
|
},
|
|
6152
|
-
required: ["action", "sessionId"]
|
|
6402
|
+
required: ["action", "accountId", "sessionId"]
|
|
6153
6403
|
}
|
|
6154
6404
|
]
|
|
6155
6405
|
};
|
|
6156
|
-
function createGrixGroupTool(api) {
|
|
6406
|
+
function createGrixGroupTool(api, ctx) {
|
|
6407
|
+
const contextAccountId = ctx?.agentAccountId;
|
|
6157
6408
|
return {
|
|
6158
6409
|
name: "grix_group",
|
|
6159
6410
|
label: "Grix Group",
|
|
@@ -6164,7 +6415,8 @@ function createGrixGroupTool(api) {
|
|
|
6164
6415
|
return jsonToolResult(
|
|
6165
6416
|
await runGrixGroupAction({
|
|
6166
6417
|
cfg: api.config,
|
|
6167
|
-
toolParams: params
|
|
6418
|
+
toolParams: params,
|
|
6419
|
+
contextAccountId
|
|
6168
6420
|
})
|
|
6169
6421
|
);
|
|
6170
6422
|
} catch (err) {
|
|
@@ -6191,9 +6443,15 @@ function mapQueryActionToRequestAction(action) {
|
|
|
6191
6443
|
}
|
|
6192
6444
|
}
|
|
6193
6445
|
async function runGrixQueryAction(params) {
|
|
6446
|
+
const accountId = resolveStrictToolAccountId({
|
|
6447
|
+
toolName: "grix_query",
|
|
6448
|
+
toolAccountId: params.toolParams.accountId,
|
|
6449
|
+
contextAccountId: params.contextAccountId
|
|
6450
|
+
});
|
|
6194
6451
|
const account = resolveGrixAccount({
|
|
6195
6452
|
cfg: params.cfg,
|
|
6196
|
-
accountId
|
|
6453
|
+
accountId,
|
|
6454
|
+
strictAccountScope: true
|
|
6197
6455
|
});
|
|
6198
6456
|
if (!account.enabled) {
|
|
6199
6457
|
throw new Error(`Grix account "${account.accountId}" is disabled.`);
|
|
@@ -6232,7 +6490,7 @@ var GrixQueryToolSchema = {
|
|
|
6232
6490
|
limit: { type: "integer", minimum: 1 },
|
|
6233
6491
|
offset: { type: "integer", minimum: 0 }
|
|
6234
6492
|
},
|
|
6235
|
-
required: ["action", "id"]
|
|
6493
|
+
required: ["action", "accountId", "id"]
|
|
6236
6494
|
},
|
|
6237
6495
|
{
|
|
6238
6496
|
type: "object",
|
|
@@ -6244,7 +6502,7 @@ var GrixQueryToolSchema = {
|
|
|
6244
6502
|
limit: { type: "integer", minimum: 1 },
|
|
6245
6503
|
offset: { type: "integer", minimum: 0 }
|
|
6246
6504
|
},
|
|
6247
|
-
required: ["action", "id"]
|
|
6505
|
+
required: ["action", "accountId", "id"]
|
|
6248
6506
|
},
|
|
6249
6507
|
{
|
|
6250
6508
|
type: "object",
|
|
@@ -6256,11 +6514,12 @@ var GrixQueryToolSchema = {
|
|
|
6256
6514
|
beforeId: { type: "string", pattern: "^[0-9]+$" },
|
|
6257
6515
|
limit: { type: "integer", minimum: 1 }
|
|
6258
6516
|
},
|
|
6259
|
-
required: ["action", "sessionId"]
|
|
6517
|
+
required: ["action", "accountId", "sessionId"]
|
|
6260
6518
|
}
|
|
6261
6519
|
]
|
|
6262
6520
|
};
|
|
6263
|
-
function createGrixQueryTool(api) {
|
|
6521
|
+
function createGrixQueryTool(api, ctx) {
|
|
6522
|
+
const contextAccountId = ctx?.agentAccountId;
|
|
6264
6523
|
return {
|
|
6265
6524
|
name: "grix_query",
|
|
6266
6525
|
label: "Grix Query",
|
|
@@ -6271,7 +6530,8 @@ function createGrixQueryTool(api) {
|
|
|
6271
6530
|
return jsonToolResult(
|
|
6272
6531
|
await runGrixQueryAction({
|
|
6273
6532
|
cfg: api.config,
|
|
6274
|
-
toolParams: params
|
|
6533
|
+
toolParams: params,
|
|
6534
|
+
contextAccountId
|
|
6275
6535
|
})
|
|
6276
6536
|
);
|
|
6277
6537
|
} catch (err) {
|
|
@@ -6364,9 +6624,9 @@ var plugin = {
|
|
|
6364
6624
|
register(api) {
|
|
6365
6625
|
setAibotRuntime(api.runtime);
|
|
6366
6626
|
api.registerChannel({ plugin: aibotPlugin });
|
|
6367
|
-
api.registerTool(createGrixQueryTool(api), { optional: true });
|
|
6368
|
-
api.registerTool(createGrixGroupTool(api), { optional: true });
|
|
6369
|
-
api.registerTool(createGrixAgentAdminTool(api), { optional: true });
|
|
6627
|
+
api.registerTool((ctx) => createGrixQueryTool(api, ctx), { optional: true });
|
|
6628
|
+
api.registerTool((ctx) => createGrixGroupTool(api, ctx), { optional: true });
|
|
6629
|
+
api.registerTool((ctx) => createGrixAgentAdminTool(api, ctx), { optional: true });
|
|
6370
6630
|
api.registerCli(({ program }) => registerGrixAdminCli({ api, program }), {
|
|
6371
6631
|
commands: ["grix"]
|
|
6372
6632
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhf-openclaw/grix",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
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",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"label": "Grix",
|
|
61
61
|
"selectionLabel": "Grix",
|
|
62
62
|
"docsPath": "/channels/grix",
|
|
63
|
-
"blurb": "Connect OpenClaw to
|
|
63
|
+
"blurb": "Connect OpenClaw to a Grix deployment for website management with mobile PWA support.",
|
|
64
64
|
"aliases": [
|
|
65
65
|
"gr"
|
|
66
66
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grix-group
|
|
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.
|
|
3
|
+
description: Use the typed `grix_group` tool for Grix group lifecycle and membership operations. Trigger when users ask to create, inspect, leave, update, or dissolve groups, or when these operations fail with scope or permission errors.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Grix Group Governance
|
|
@@ -11,7 +11,7 @@ This skill is about tool selection and guardrails, not protocol bridging.
|
|
|
11
11
|
## Workflow
|
|
12
12
|
|
|
13
13
|
1. Parse the user request into one action:
|
|
14
|
-
`create`, `detail`, `add_members`, `remove_members`, `update_member_role`, `update_all_members_muted`, `update_member_speaking`, or `dissolve`.
|
|
14
|
+
`create`, `detail`, `leave`, `add_members`, `remove_members`, `update_member_role`, `update_all_members_muted`, `update_member_speaking`, or `dissolve`.
|
|
15
15
|
2. Validate required fields before any call.
|
|
16
16
|
3. Call `grix_group` exactly once per business action.
|
|
17
17
|
4. Classify failures by HTTP/BizCode and return exact remediation.
|
|
@@ -23,8 +23,8 @@ This skill is about tool selection and guardrails, not protocol bridging.
|
|
|
23
23
|
For Grix group governance, always call:
|
|
24
24
|
|
|
25
25
|
1. Tool: `grix_group`
|
|
26
|
-
2. `action`: one of `create`, `detail`, `add_members`, `remove_members`, `update_member_role`, `update_all_members_muted`, `update_member_speaking`, `dissolve`
|
|
27
|
-
3. `accountId`:
|
|
26
|
+
2. `action`: one of `create`, `detail`, `leave`, `add_members`, `remove_members`, `update_member_role`, `update_all_members_muted`, `update_member_speaking`, `dissolve`
|
|
27
|
+
3. `accountId`: always pass `{{AccountId}}` from the current conversation context for every action, including `leave`.
|
|
28
28
|
|
|
29
29
|
Rules:
|
|
30
30
|
|
|
@@ -67,6 +67,24 @@ Guardrails:
|
|
|
67
67
|
2. Reject non-numeric `memberIds` before calling the tool.
|
|
68
68
|
3. If `sessionId` is ambiguous, ask the user to confirm the target group first.
|
|
69
69
|
|
|
70
|
+
### leave
|
|
71
|
+
|
|
72
|
+
Purpose: let the current Agent leave a group by itself.
|
|
73
|
+
|
|
74
|
+
Required input:
|
|
75
|
+
|
|
76
|
+
1. `sessionId`
|
|
77
|
+
|
|
78
|
+
Guardrails:
|
|
79
|
+
|
|
80
|
+
1. Only the current Agent owner can request this action.
|
|
81
|
+
2. If the requester is not the current Agent owner, reject the request directly and do not call `grix_group`.
|
|
82
|
+
3. This action is only for the current Agent leaving its own group membership.
|
|
83
|
+
4. Never translate a request to remove other members into `leave`; use `remove_members` for that.
|
|
84
|
+
5. Do not send `memberId`, `memberIds`, or `memberTypes` with this action.
|
|
85
|
+
6. This action does not require scope and should not be described as a scope-grant workflow.
|
|
86
|
+
7. Execute `leave` silently: do not send any pre-leave or farewell message to the group before leaving.
|
|
87
|
+
|
|
70
88
|
### remove_members
|
|
71
89
|
|
|
72
90
|
Required input:
|
|
@@ -123,6 +141,7 @@ Required input:
|
|
|
123
141
|
|
|
124
142
|
1. `403/20011`:
|
|
125
143
|
report missing scope and ask owner to grant the scope in Aibot Agent permission page.
|
|
144
|
+
Do not use this remediation for `leave`, because `leave` is scope-free.
|
|
126
145
|
2. `401/10001`:
|
|
127
146
|
report invalid key/auth and suggest checking agent config or rotating API key.
|
|
128
147
|
3. `403/10002`:
|
|
@@ -138,6 +157,7 @@ Required input:
|
|
|
138
157
|
2. Include key identifiers (`session_id`, member count, mute state) when successful.
|
|
139
158
|
3. Include exact remediation when failed.
|
|
140
159
|
4. Never hide scope or auth errors behind generic wording.
|
|
160
|
+
5. For `leave`, report result to the requester only; do not post extra messages into the group session.
|
|
141
161
|
|
|
142
162
|
## References
|
|
143
163
|
|
|
@@ -16,6 +16,7 @@ Map high-level governance actions to Aibot Agent API HTTP routes.
|
|
|
16
16
|
| Action | Method | Route | Required Scope |
|
|
17
17
|
|---|---|---|---|
|
|
18
18
|
| `group_create` | `POST` | `/sessions/create_group` | `group.create` |
|
|
19
|
+
| `group_leave_self` | `POST` | `/sessions/leave` | - |
|
|
19
20
|
| `group_member_add` | `POST` | `/sessions/members/add` | `group.member.add` |
|
|
20
21
|
|
|
21
22
|
## OpenClaw Tool Mapping
|
|
@@ -26,6 +27,7 @@ Use the native `grix_group` tool with typed fields:
|
|
|
26
27
|
|---|---|---|
|
|
27
28
|
| `create` | `group_create` | `name` |
|
|
28
29
|
| `detail` | `group_detail_read` | `sessionId` |
|
|
30
|
+
| `leave` | `group_leave_self` | `sessionId` |
|
|
29
31
|
| `add_members` | `group_member_add` | `sessionId`, `memberIds` |
|
|
30
32
|
| `remove_members` | `group_member_remove` | `sessionId`, `memberIds` |
|
|
31
33
|
| `update_member_role` | `group_member_role_update` | `sessionId`, `memberId`, `role` |
|
|
@@ -57,6 +59,15 @@ Use the native `grix_group` tool with typed fields:
|
|
|
57
59
|
}
|
|
58
60
|
```
|
|
59
61
|
|
|
62
|
+
### leave
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"action": "leave",
|
|
67
|
+
"sessionId": "task_room_9083"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
60
71
|
## Error Matrix
|
|
61
72
|
|
|
62
73
|
| HTTP/BizCode | Meaning | Skill Response |
|
|
@@ -66,6 +77,10 @@ Use the native `grix_group` tool with typed fields:
|
|
|
66
77
|
| `401/10001` | invalid or missing auth | Check api_key and account config |
|
|
67
78
|
| `403/10002` | agent not active / invalid provider | Ask owner to activate the agent |
|
|
68
79
|
|
|
80
|
+
Notes:
|
|
81
|
+
|
|
82
|
+
1. `leave` does not require scope and should not route `403/20011` into scope remediation.
|
|
83
|
+
|
|
69
84
|
## Retry Policy
|
|
70
85
|
|
|
71
86
|
1. Never auto-retry `group_create` unless user confirms.
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
## Base
|
|
10
10
|
|
|
11
|
-
1.
|
|
12
|
-
2.
|
|
11
|
+
1. Default website: `https://grix.dhf.pub/`
|
|
12
|
+
2. Default public Grix API base: `https://grix.dhf.pub/v1`
|
|
13
|
+
3. Local development or private deployment can override the base URL.
|
|
13
14
|
|
|
14
15
|
## Route Mapping
|
|
15
16
|
|
|
@@ -13,7 +13,24 @@ import uuid
|
|
|
13
13
|
|
|
14
14
|
DEFAULT_BASE_URL = "https://grix.dhf.pub"
|
|
15
15
|
DEFAULT_TIMEOUT_SECONDS = 15
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_default_base_url() -> str:
|
|
19
|
+
return (os.environ.get("GRIX_WEB_BASE_URL", "") or "").strip() or DEFAULT_BASE_URL
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def derive_portal_url(raw_base_url: str) -> str:
|
|
23
|
+
base = (raw_base_url or "").strip() or resolve_default_base_url()
|
|
24
|
+
parsed = urllib.parse.urlparse(base)
|
|
25
|
+
if not parsed.scheme or not parsed.netloc:
|
|
26
|
+
raise ValueError(f"Invalid base URL: {base}")
|
|
27
|
+
|
|
28
|
+
path = parsed.path.rstrip("/")
|
|
29
|
+
if path.endswith("/v1"):
|
|
30
|
+
path = path[: -len("/v1")]
|
|
31
|
+
|
|
32
|
+
normalized = parsed._replace(path=path or "/", params="", query="", fragment="")
|
|
33
|
+
return urllib.parse.urlunparse(normalized).rstrip("/") + "/"
|
|
17
34
|
|
|
18
35
|
|
|
19
36
|
class GrixAuthError(RuntimeError):
|
|
@@ -25,7 +42,7 @@ class GrixAuthError(RuntimeError):
|
|
|
25
42
|
|
|
26
43
|
|
|
27
44
|
def normalize_base_url(raw_base_url: str) -> str:
|
|
28
|
-
base = (raw_base_url or "").strip() or
|
|
45
|
+
base = (raw_base_url or "").strip() or resolve_default_base_url()
|
|
29
46
|
parsed = urllib.parse.urlparse(base)
|
|
30
47
|
if not parsed.scheme or not parsed.netloc:
|
|
31
48
|
raise ValueError(f"Invalid base URL: {base}")
|
|
@@ -112,7 +129,7 @@ def print_json(payload):
|
|
|
112
129
|
sys.stdout.write("\n")
|
|
113
130
|
|
|
114
131
|
|
|
115
|
-
def build_auth_result(action: str, result: dict):
|
|
132
|
+
def build_auth_result(action: str, result: dict, base_url: str):
|
|
116
133
|
data = result.get("data") or {}
|
|
117
134
|
user = data.get("user") or {}
|
|
118
135
|
return {
|
|
@@ -123,7 +140,7 @@ def build_auth_result(action: str, result: dict):
|
|
|
123
140
|
"refresh_token": data.get("refresh_token", ""),
|
|
124
141
|
"expires_in": data.get("expires_in", 0),
|
|
125
142
|
"user_id": user.get("id", ""),
|
|
126
|
-
"portal_url":
|
|
143
|
+
"portal_url": derive_portal_url(base_url),
|
|
127
144
|
"data": data,
|
|
128
145
|
}
|
|
129
146
|
|
|
@@ -170,7 +187,7 @@ def login_with_credentials(base_url: str, account: str, password: str, device_id
|
|
|
170
187
|
"platform": platform,
|
|
171
188
|
},
|
|
172
189
|
)
|
|
173
|
-
return build_auth_result("login", result)
|
|
190
|
+
return build_auth_result("login", result, base_url)
|
|
174
191
|
|
|
175
192
|
|
|
176
193
|
def create_api_agent(base_url: str, access_token: str, agent_name: str, avatar_url: str):
|
|
@@ -338,7 +355,7 @@ def handle_register(args):
|
|
|
338
355
|
"platform": platform,
|
|
339
356
|
},
|
|
340
357
|
)
|
|
341
|
-
print_json(build_auth_result("register", result))
|
|
358
|
+
print_json(build_auth_result("register", result, args.base_url))
|
|
342
359
|
|
|
343
360
|
|
|
344
361
|
def handle_login(args):
|
|
@@ -374,7 +391,11 @@ def handle_create_api_agent(args):
|
|
|
374
391
|
|
|
375
392
|
def build_parser():
|
|
376
393
|
parser = argparse.ArgumentParser(description="Grix public auth API helper")
|
|
377
|
-
parser.add_argument(
|
|
394
|
+
parser.add_argument(
|
|
395
|
+
"--base-url",
|
|
396
|
+
default=resolve_default_base_url(),
|
|
397
|
+
help="Grix web base URL (defaults to GRIX_WEB_BASE_URL or https://grix.dhf.pub)",
|
|
398
|
+
)
|
|
378
399
|
|
|
379
400
|
subparsers = parser.add_subparsers(dest="action", required=True)
|
|
380
401
|
|