grix-connector 3.10.2 → 3.12.0

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.
Files changed (30) hide show
  1. package/dist/adapter/claude/claude-bridge-server.js +1 -1
  2. package/dist/adapter/claude/claude-tools.js +1 -1
  3. package/dist/adapter/claude/claude-worker-client.js +1 -1
  4. package/dist/adapter/claude/mcp-http-launcher.js +2 -2
  5. package/dist/adapter/claude/result-timeout.js +1 -1
  6. package/dist/adapter/claude/skill-scanner.js +2 -2
  7. package/dist/adapter/opencode/opencode-adapter.js +2 -2
  8. package/dist/adapter/pi/pi-adapter.js +4 -4
  9. package/dist/bridge/bridge.js +9 -9
  10. package/dist/bridge/event-queue.js +1 -1
  11. package/dist/core/access/allowlist-store.js +1 -1
  12. package/dist/core/file-ops/list-files.js +1 -1
  13. package/dist/core/provider-quota/index.js +1 -1
  14. package/dist/core/provider-quota/presentation.js +1 -0
  15. package/dist/core/provider-quota/providers.js +2 -2
  16. package/dist/core/provider-quota/service.js +1 -0
  17. package/dist/default-skills/grix-admin/SKILL.md +278 -25
  18. package/dist/default-skills/grix-admin/references/api-contract.md +293 -0
  19. package/dist/default-skills/index.js +1 -1
  20. package/dist/log.js +2 -2
  21. package/dist/manager.js +2 -2
  22. package/dist/mcp/stream-http/config.js +1 -1
  23. package/dist/mcp/stream-http/connection-binding.js +1 -1
  24. package/dist/mcp/stream-http/security.js +1 -1
  25. package/dist/mcp/stream-http/tool-executor.js +1 -1
  26. package/dist/mcp/stream-http/tool-registry.js +1 -1
  27. package/dist/mcp/stream-http/tool-schemas.js +1 -1
  28. package/openclaw-plugin/skills/grix-admin/SKILL.md +110 -24
  29. package/openclaw-plugin/skills/grix-admin/references/api-contract.md +85 -2
  30. package/package.json +1 -1
@@ -0,0 +1,293 @@
1
+ # API Contract
2
+
3
+ ## Purpose
4
+
5
+ `grix-admin` is responsible for local binding and runtime convergence, and when the current agent has the corresponding scope, supports completing the following through `grix_admin` via WS:
6
+
7
+ 1. Create new remote API agents
8
+ 2. Query agent categories under the current account
9
+ 3. Create categories
10
+ 4. Modify categories
11
+ 5. Assign or clear categories for specified agents
12
+
13
+ ## Base Rules
14
+
15
+ 1. Do not ask users to provide website account/password for this flow.
16
+ 2. All remote creation and category actions must go through `grix_admin` via the current account's authenticated WS channel.
17
+ 3. If `agent_name` / `agent_id` / `api_endpoint` / `api_key` are incomplete, and the current account cannot create remotely, stop first and require backend admin to complete them.
18
+ 4. The current agent must first have the corresponding scope enabled on the frontend permissions page; without scope, WS will fail directly.
19
+ 5. For `bind-local` / `create-and-bind` / `connector-bind-local` / `create-and-connector-bind`, "config written successfully" does not equal completion; if this invocation already has real routing verification conditions, real verification passing must also be counted as part of the success criteria; otherwise explicitly hand the subsequent verification responsibility back to the upper-level flow.
20
+
21
+ ## Direct `grix_admin` Contract
22
+
23
+ ### 1. Create Remote Agent
24
+
25
+ ```json
26
+ {
27
+ "action": "create_agent",
28
+ "agentName": "ops helper",
29
+ "introduction": "Handles deployment and on-call collaboration",
30
+ "isMain": false,
31
+ "categoryName": "Project Assistant",
32
+ "parentCategoryId": "0",
33
+ "categorySortOrder": 10
34
+ }
35
+ ```
36
+
37
+ Key fields to read from the return:
38
+
39
+ 1. `createdAgent.id`
40
+ 2. `createdAgent.agent_name`
41
+ 3. `createdAgent.api_endpoint`
42
+ 4. `createdAgent.api_key`
43
+
44
+ Required scope:
45
+
46
+ 1. `agent.api.create`
47
+ 2. If `categoryName` is included, may additionally need `agent.category.list`, `agent.category.create`, `agent.category.assign`
48
+ 3. If `categoryId` is included, additionally needs `agent.category.assign`
49
+
50
+ ### 2. List Categories
51
+
52
+ ```json
53
+ {
54
+ "action": "list_categories"
55
+ }
56
+ ```
57
+
58
+ Required scope:
59
+
60
+ 1. `agent.category.list`
61
+
62
+ ### 3. Create Category
63
+
64
+ ```json
65
+ {
66
+ "action": "create_category",
67
+ "name": "Project Assistant",
68
+ "parentId": "0",
69
+ "sortOrder": 10
70
+ }
71
+ ```
72
+
73
+ Required scope:
74
+
75
+ 1. `agent.category.create`
76
+
77
+ ### 4. Update Category
78
+
79
+ ```json
80
+ {
81
+ "action": "update_category",
82
+ "categoryId": "20001",
83
+ "name": "On-call Assistant",
84
+ "parentId": "0",
85
+ "sortOrder": 20
86
+ }
87
+ ```
88
+
89
+ Required scope:
90
+
91
+ 1. `agent.category.update`
92
+
93
+ ### 5. Assign or Clear Category
94
+
95
+ ```json
96
+ {
97
+ "action": "assign_category",
98
+ "agentId": "10001",
99
+ "categoryId": "20001"
100
+ }
101
+ ```
102
+
103
+ Clear category:
104
+
105
+ ```json
106
+ {
107
+ "action": "assign_category",
108
+ "agentId": "10001",
109
+ "categoryId": "0"
110
+ }
111
+ ```
112
+
113
+ Required scope:
114
+
115
+ 1. `agent.category.assign`
116
+
117
+ ## Local Bind Steps
118
+
119
+ After remote agent parameters are complete, continue with local binding. The target environment determines which path to follow.
120
+
121
+ ### Path A: OpenClaw
122
+
123
+ Use official OpenClaw CLI commands:
124
+
125
+ 1. Prepare local directories:
126
+ - `workspace=~/.openclaw/workspace-<agent_name>`
127
+ - `agentDir=~/.openclaw/agents/<agent_name>/agent`
128
+ - Add minimal `IDENTITY.md`, `SOUL.md`, `AGENTS.md` when required persona files are missing
129
+ 2. Resolve `model` in this order:
130
+ - The existing `model` from that local agent's entry
131
+ - `agents.defaults.model.primary`
132
+ - If still unavailable, clearly report error and stop
133
+ 3. Read current config and merge:
134
+ - `channels.grix.accounts`
135
+ - `agents.list`
136
+ - `tools.profile`
137
+ - `tools.alsoAllow`
138
+ - `tools.sessions.visibility`
139
+ 4. Write back using official CLI:
140
+ - `channels.grix.accounts.<agent_name>`
141
+ - `agents.list`
142
+ - `openclaw agents bind --agent <agent_name> --bind grix:<agent_name>`
143
+ - `tools.profile`
144
+ - `tools.alsoAllow`
145
+ - `tools.sessions.visibility`
146
+ - If needed, restore `channels.grix.enabled=true`
147
+ 5. After writing, perform static validation first:
148
+ - `openclaw config validate`
149
+ - `openclaw config get --json channels.grix.accounts.<agent_name>`
150
+ - `openclaw config get --json agents.list`
151
+ - `openclaw agents bindings --agent <agent_name> --json`
152
+ 6. If this invocation already has real verification conditions, must immediately perform a real routing verification; reuse the current install/acceptance context, do not invent additional probes. Reply falling to the main agent, default assistant, old persona, or old config are all considered failures.
153
+ 7. Only when step 5 static validation passes and this invocation itself handles step 6 real verification but verification fails, is one `openclaw gateway restart` allowed; after restart, must redo the same round of real routing verification.
154
+ 8. If this invocation cannot perform real verification, can only state "config has been written, runtime not yet tested, needs subsequent flow to continue verification"; do not write it as "already fully taken effect".
155
+
156
+ ### Path B: grix-connector
157
+
158
+ Directly manage `~/.grix/config/agents.json` and hot-reload the daemon:
159
+
160
+ 1. Target file: `~/.grix/config/agents.json`. Initialize as `{ "agents": [] }` if missing.
161
+ 2. Read the existing `agents` array. Match by `name === agent_name`:
162
+ - If found, update the existing entry in place.
163
+ - If not found, append a new entry.
164
+ 3. Write the entry with these fields:
165
+ - `name`: `agent_name`
166
+ - `ws_url`: `api_endpoint`
167
+ - `agent_id`: `agent_id`
168
+ - `api_key`: `api_key`
169
+ - `client_type`: `client_type` (default `pi`)
170
+ 4. Preserve valid JSON, set file permissions to `0o600`, and create a timestamped backup (also `0o600`) before writing.
171
+ 5. Trigger reload through the synchronous Admin API:
172
+ - Default endpoint: `POST http://127.0.0.1:19580/api/reload`
173
+ - The endpoint waits for `manager.reload()` to complete and returns `{ ok: true, result }` or an error. Use this instead of the CLI `grix-connector reload` to avoid races and to surface reload failures.
174
+ - If the admin port was customized via `GRIX_ADMIN_PORT` or `--admin-port`, read the actual port from `~/.grix/data/admin-port`.
175
+ 6. Verify via Admin API `GET http://127.0.0.1:<admin-port>/api/agents`:
176
+ - Find entry where `name === agent_name`.
177
+ - Confirm the entry exists and reports `alive === true`.
178
+ - **Caveat**: `alive=true` only means the daemon started the instance. It does **not** prove the Agent successfully authenticated with the Grix platform.
179
+ 7. Perform secondary platform-connection verification:
180
+ - Inspect daemon logs for WebSocket connection success / authentication failure messages for this Agent.
181
+ - Or ask the owner to send a test message to the Agent and confirm it responds.
182
+ 8. If verification fails, report the observed status and stop; do not claim success.
183
+ 9. If this invocation cannot perform real verification, can only state "config has been written, reload completed, runtime not yet tested"; do not write it as "already fully taken effect".
184
+
185
+ ## `bind-local` Input Contract
186
+
187
+ ```json
188
+ {
189
+ "task": "bind-local\nagent_name=grix-main\nagent_id=2029786829095440384\napi_endpoint=wss://grix.dhf.pub/v1/agent-api/ws?agent_id=2029786829095440384\napi_key=ak_xxx\ndo_not_create_remote_agent=true"
190
+ }
191
+ ```
192
+
193
+ This mode prioritizes local binding; if this invocation has real verification conditions, must also complete real routing verification before it counts as full convergence; otherwise only complete static binding and explicitly hand subsequent verification responsibility to the upper-level flow.
194
+
195
+ ## `connector-bind-local` Input Contract
196
+
197
+ ```json
198
+ {
199
+ "task": "connector-bind-local\nagent_name=programmer-pi\nagent_id=2079349263263338496\napi_endpoint=wss://grix.dhf.pub/v1/agent-api/ws?agent_id=2079349263263338496\napi_key=ak_xxx\nclient_type=pi"
200
+ }
201
+ ```
202
+
203
+ Field mapping to `~/.grix/config/agents.json`:
204
+
205
+ | Input field | JSON field | Notes |
206
+ | -------------- | ------------ | ------------------------------------------ |
207
+ | `agent_name` | `name` | Also used as the matching key |
208
+ | `api_endpoint` | `ws_url` | The WebSocket URL for the agent API |
209
+ | `agent_id` | `agent_id` | Platform agent ID |
210
+ | `api_key` | `api_key` | Agent API key |
211
+ | `client_type` | `client_type`| Optional, default `pi` |
212
+
213
+ Convergence rules:
214
+
215
+ 1. Backup `~/.grix/config/agents.json` before writing; set backup permissions to `0o600`.
216
+ 2. Preserve valid JSON and set file permissions to `0o600`.
217
+ 3. Trigger reload via synchronous Admin API `POST http://127.0.0.1:19580/api/reload` (or the port stored in `~/.grix/data/admin-port` if customized).
218
+ 4. Verify via `GET http://127.0.0.1:<admin-port>/api/agents` that the entry exists and has `alive=true`.
219
+ 5. Perform secondary platform-connection verification by checking daemon logs or sending a test message; do not treat `alive=true` as proof of successful platform authentication.
220
+
221
+ ## `create-and-bind` Input Contract
222
+
223
+ When the main agent already has an available account and `agent.api.create` scope, can enter the creation flow through `grix_admin.task`:
224
+
225
+ ```json
226
+ {
227
+ "task": "create-and-bind\nagentName=ops helper\nintroduction=Handles deployment and on-call collaboration\nisMain=false\ncategoryName=Project Assistant\nparentCategoryId=0\ncategorySortOrder=10"
228
+ }
229
+ ```
230
+
231
+ This mode requires steps in order:
232
+
233
+ 1. First make one direct call with `action=create_agent`, passing optional `categoryId` / `categoryName` / `parentCategoryId` / `categorySortOrder` together
234
+ 2. If the return already includes the category assignment result, continue directly
235
+ 3. If the caller used a legacy path, or the return does not include the category assignment result, supplement with:
236
+ - `categoryId` -> `action=assign_category`
237
+ - `categoryName` -> `action=list_categories`
238
+ - Not found -> `action=create_category`
239
+ - After obtaining category ID -> `action=assign_category`
240
+ 4. Finally follow the same local binding and runtime convergence flow as `bind-local`
241
+
242
+ Notes:
243
+
244
+ 1. `categoryId` and `categoryName` cannot be provided simultaneously
245
+ 2. When matching `categoryName`, must also consider `parentCategoryId`
246
+ 3. If the remote return indicates missing `agent.api.create` or any `agent.category.*` scope, clearly state which specific scope is missing
247
+
248
+ ## `create-and-connector-bind` Input Contract
249
+
250
+ When the main agent already has an available account and `agent.api.create` scope, and the target runtime is grix-connector:
251
+
252
+ ```json
253
+ {
254
+ "task": "create-and-connector-bind\nagentName=程序员 pi\nintroduction=A coding assistant\nisMain=false\nclientType=pi\ncategoryName=Developers\nparentCategoryId=0\ncategorySortOrder=10"
255
+ }
256
+ ```
257
+
258
+ This mode requires steps in order:
259
+
260
+ 1. Make one direct call with `action=create_agent`, passing optional `categoryId` / `categoryName` / `parentCategoryId` / `categorySortOrder` together.
261
+ 2. If the return already includes the category assignment result, continue directly; otherwise supplement with the same category resolution logic as `create-and-bind`.
262
+ 3. Read `createdAgent.id`, `createdAgent.agent_name`, `createdAgent.api_endpoint`, `createdAgent.api_key` from the result.
263
+ 4. Follow the `connector-bind-local` local binding and runtime convergence flow, using `clientType` (default `pi`) for the `client_type` field.
264
+ 5. Trigger reload via the synchronous Admin API `POST /api/reload`, verify the Agent entry exists and `alive=true` via `GET /api/agents`, and perform secondary platform-connection verification (log inspection or test message).
265
+
266
+ Notes:
267
+
268
+ 1. `categoryId` and `categoryName` cannot be provided simultaneously.
269
+ 2. `clientType` defaults to `pi` when omitted.
270
+ 3. If the remote return indicates missing `agent.api.create` or any `agent.category.*` scope, clearly state which specific scope is missing.
271
+
272
+ ## `category-manage` Input Contract
273
+
274
+ When only doing subsequent category management, enter through `grix_admin.task`:
275
+
276
+ ```json
277
+ {
278
+ "task": "category-manage\noperation=assign\nagentId=10001\ncategoryId=0"
279
+ }
280
+ ```
281
+
282
+ Mapping:
283
+
284
+ 1. `operation=list` -> `action=list_categories`
285
+ 2. `operation=create` -> `action=create_category`
286
+ 3. `operation=update` -> `action=update_category`
287
+ 4. `operation=assign` -> `action=assign_category`
288
+
289
+ Notes:
290
+
291
+ 1. For `operation=assign`, `categoryId=0` means clear the category
292
+ 2. No step can be executed cross-account
293
+ 3. Do not hand-write HTTP or fall back to legacy scripts
@@ -1 +1 @@
1
- import{readFileSync as p,readdirSync as c,existsSync as a,mkdirSync as k,cpSync as y,writeFileSync as h,rmSync as g}from"node:fs";import{join as t,dirname as S}from"node:path";import{homedir as m}from"node:os";import{fileURLToPath as D}from"node:url";import{parseSkillFrontmatter as v,scanSkills as x,dedupeSkills as F}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as w}from"../core/config/index.js";import{log as u}from"../core/log/index.js";const l=S(D(import.meta.url)),f=".grix-managed";function d(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=p(n,"utf-8"),o=v(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector",managed:!0})}catch{}}return e}function T(e){let i=[];try{i=x(e)}catch{i=[]}return F([...i,...d()])}function j(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(a(t(s,"SKILL.md")))try{k(e,{recursive:!0});const o=t(e,n.name);y(s,o,{recursive:!0}),h(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function C(){return(process.env.KIMI_CODE_HOME||"").trim()||t(m(),".kimi-code")}function L(){const e=m(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(w().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills"),t(C(),"skills")]}function O(){const e=[];for(const i of L()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(a(t(s,f)))try{g(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&u.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function b(){const e=d();if(e.length===0){u.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}u.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{T as buildReportedSkills,O as cleanupProjectedSkills,b as logDefaultSkillsCheck,C as resolveKimiCodeHome,d as scanDefaultSkills,j as syncDefaultSkillsToDir};
1
+ import{readFileSync as p,readdirSync as c,existsSync as a,mkdirSync as k,cpSync as y,writeFileSync as h,rmSync as g}from"node:fs";import{join as t,dirname as S}from"node:path";import{homedir as m}from"node:os";import{fileURLToPath as D}from"node:url";import{parseSkillFrontmatter as v,scanSkills as x,dedupeSkills as F,MANAGED_MARKER as f}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as w}from"../core/config/index.js";import{log as u}from"../core/log/index.js";const l=S(D(import.meta.url));function d(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=p(n,"utf-8"),o=v(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector",managed:!0})}catch{}}return e}function T(e){let i=[];try{i=x(e)}catch{i=[]}return F([...i,...d()])}function j(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(a(t(s,"SKILL.md")))try{k(e,{recursive:!0});const o=t(e,n.name);y(s,o,{recursive:!0}),h(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function C(){return(process.env.KIMI_CODE_HOME||"").trim()||t(m(),".kimi-code")}function L(){const e=m(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(w().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills"),t(C(),"skills")]}function O(){const e=[];for(const i of L()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(a(t(s,f)))try{g(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&u.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function b(){const e=d();if(e.length===0){u.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}u.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{T as buildReportedSkills,O as cleanupProjectedSkills,b as logDefaultSkillsCheck,C as resolveKimiCodeHome,d as scanDefaultSkills,j as syncDefaultSkillsToDir};
package/dist/log.js CHANGED
@@ -1,3 +1,3 @@
1
- import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
- `)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
1
+ import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
+ `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
3
  `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as K,readdirSync as G,writeFileSync as z}from"node:fs";import{join as A}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as J,diffSharedOwners as V,sharedInstanceKey as X,SharedOwnersCache as Y}from"./manager-share-config.js";import{isAgentDeletedError as D}from"./core/aibot/errors.js";import{GRIX_PATHS as w,log as l}from"./core/log/index.js";import{resolveClientVersion as Z}from"./core/util/client-version.js";import{UpgradeChecker as ee}from"./core/upgrade/upgrade-checker.js";import{SkillSyncer as te}from"./core/skill-sync/skill-syncer.js";import{AgentGlobalConfigStore as ne}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as ae}from"./adapter/claude/skill-scanner.js";import{buildWireSkills as re}from"./core/skill-sync/sync-state.js";import{buildReportedSkills as se,logDefaultSkillsCheck as oe,cleanupProjectedSkills as P}from"./default-skills/index.js";import{supportsDirectProviderConfig as ie}from"./core/config/provider-env.js";import{resolveCopilotCommand as O}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as H}from"./core/runtime/cursor-resolve.js";import{getCliVersion as ce,resolveCliPath as le}from"./core/util/cli-probe.js";import{AgentInstaller as de}from"./core/installer/installer.js";import{reportInstallFailure as pe}from"./core/observability/sentry.js";const me=8e3;function ue(){const n=O();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:H()},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ge(){return Promise.all(ue().map(async n=>{const e=await le(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ce(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function he(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:H()};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],options:{initial_mode:"yolo"},enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=O();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function q(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function fe(n,e){return A(w.data,`session-bindings-${q(n,e)}.json`)}function _e(n,e){return A(w.data,`active-events-${q(n,e)}.json`)}function ye(...n){const e=[],t=new Set;for(const r of n)for(const s of r??[]){const i=String(s??"").trim(),o=i.toLowerCase();!i||t.has(o)||(t.add(o),e.push(i))}return e.length>0?e:void 0}function we(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function b(n,e={}){const t=Z(),r=String(n.client_type??"").trim().toLowerCase(),s=he(r),i=String(n.ws_url??"").trim(),o="get_session_usage",p="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!i)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const a=s.adapterType,g=a==="acp",m=r==="qwen",_={...s.options??{}},S=a==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",o,p]}:null,d=a==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,p]}:null,f=m?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o],adapterHint:"qwen/base"}:null,h=a==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o]}:null,u=a==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,y=a==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",o,p]}:null,v=a==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,C=a==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,$=a==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,R=g&&!m?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,B=r==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,p]}:null,F=a==="codex"||a==="claude"||r==="gemini"?["session_control","set_model","set_mode"]:void 0,U=[o,p];g&&_.raw_transport===void 0&&(_.raw_transport=r==="gemini");const L=`${r}/base`,I=ae(a,r),Q=I==="kiro"?void 0:process.cwd(),E=se({mode:I,projectDir:Q}),M=E.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&!e.quiet&&l.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);const j=re(E,w.skills);let W=j.length>0?j:void 0;return{name:n.name,adapterType:a,aibot:{url:i,agentId:n.agent_id,apiKey:n.api_key,clientType:r,clientVersion:t,adapterHint:s.adapterHint??f?.adapterHint??h?.adapterHint??u?.adapterHint??y?.adapterHint??C?.adapterHint??$?.adapterHint??L,capabilities:S?.capabilities??v?.capabilities??h?.capabilities??u?.capabilities??y?.capabilities??C?.capabilities??$?.capabilities??f?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ye(S?.localActions??v?.localActions??d?.localActions??h?.localActions??u?.localActions??y?.localActions??C?.localActions??$?.localActions??f?.localActions??B?.localActions??R?.localActions??["exec_approve","exec_reject"],F,U,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider","skill_upload"]),skills:W},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:r,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:_,acpAuthMethod:_.auth_method,acpInitialMode:_.initial_mode,acpMcpTools:_.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:fe(n.name,n.agent_id),activeEventStorePath:_e(n.name,n.agent_id),...s.enableSessionBinding||g?{enableSessionBinding:!0}:{},...s.autoInjectArgs||r==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...s.autoInjectArgs??{},...r==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:we(a,n.event_queue),logDir:w.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function Se(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):me}function N(n){const e=[],t=[],r=[],s=G(n).filter(i=>i.endsWith(".json")).sort();for(const i of s)try{const o=JSON.parse(K(A(n,i),"utf-8"));if(o===null||typeof o!="object"||Array.isArray(o)||!("agents"in o)){r.push(i);continue}if(!Array.isArray(o.agents)){t.push({file:i,error:"agents is not an array"});continue}if(o.agents.length===0){t.push({file:i,error:"agents array is empty"});continue}for(const p of o.agents)e.push({entry:p,file:i})}catch(o){t.push({file:i,error:o instanceof Error?o.message:String(o)})}return{entries:e,fileErrors:t,skipped:r}}function k(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(k).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${k(e[r])}`).join(",")}}`}function Ae(n,e){return k(n)===k(e)}class Le{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;skillSyncer=null;globalConfigStore;configDir=w.config;sharedOwnersCache=new Y(w.data);installer=new de;async start(e){const t=e??w.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),oe(),P(),this.globalConfigStore=new ne(A(w.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:s,skipped:i}=N(t);for(const a of s)l.error("manager",`Failed to load ${a.file}: ${a.error}`);for(const a of i)l.info("manager",`Skipped ${a}: not an agent config (no "agents" key)`);if(r.length===0&&s.length===0&&i.length===0)throw new Error(`No config files found in ${t}`);const o=[];let p=s.length;for(const{entry:a,file:g}of r)try{const m=b(a);o.push({config:m,entry:a,file:g}),l.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${g}`)}catch(m){const _=typeof a?.name=="string"?a.name:"<unknown>";l.error("manager",`Invalid agent config in ${g} (name=${_}): ${m}`),p++}let c=0;if(o.length>0){const a=Se();l.info("manager",`Starting ${o.length} agent(s), startup wait=${a}ms`);const g=()=>this.upgradeChecker?.triggerCheck(),m=d=>{this.instances=this.instances.filter(f=>f!==d)},_=o.map(({config:d,entry:f,file:h})=>{const u=new T(d,this.globalConfigStore);return u.setUpgradeTrigger(g),u.setShareSetHandler(y=>this.onShareSet(d,y)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),u.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:f,file:h}),{config:d,instance:u,startPromise:u.start()}}),S=await Promise.all(_.map(async d=>{const f=await new Promise(h=>{let u=!1;const y=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},a);d.startPromise.then(()=>{u||(u=!0,clearTimeout(y),h({kind:"started"}))}).catch(v=>{u||(u=!0,clearTimeout(y),h({kind:"failed",error:v}))})});return{task:d,outcome:f}}));for(const{task:d,outcome:f}of S){if(f.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(f.kind==="failed"){if(D(f.error)){this.onAgentDeletedByPlatform(d.config.name);continue}m(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${f.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(D(h)){this.onAgentDeletedByPlatform(d.config.name);return}m(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${o.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&o.length>0)throw new Error("All agent configurations failed to start");o.length>0&&(this.upgradeChecker=new ee(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),()=>this.instances.some(a=>a.hasPendingOrBackgroundWork())||[...this.sharedInstances.values()].some(a=>a.hasPendingOrBackgroundWork())),await this.upgradeChecker.start()),o.length>0&&(this.skillSyncer=new te(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),w.skills),await this.skillSyncer.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),this.skillSyncer?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],P(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const s=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,s)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:s}=V(e.name,this.sharedInstances.keys(),t);for(const i of r){if(this.stopping)break;const o=X(e.name,i);try{const p=J(e,i),c=new T(p,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),l.info("manager",`shared instance started: ${o}`)}catch(p){this.sharedInstances.delete(o);const c=p instanceof Error?p.message:String(p);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,i),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${p}`)}}for(const{key:i}of s){const o=this.sharedInstances.get(i);o&&(this.sharedInstances.delete(i),o.stop().catch(p=>l.error("manager",`stop shared instance failed ${i}: ${p}`)),l.info("manager",`shared instance stopped: ${i}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=b(e);if(this.instances.some(s=>s.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const s=new T(e,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(i=>this.onShareSet(e,i)),s.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),s.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),s.setProviderConfigHandler(i=>this.setAgentProvider(e.name,i)),s.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await s.start(),this.instances.push(s),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(p=>p.name===e),s=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const i=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),i&&await i.catch(()=>{});const o=`${e}#shared:`;for(const[p,c]of[...this.sharedInstances.entries()])p.startsWith(o)&&(this.sharedInstances.delete(p),c.stop().catch(a=>l.error("manager",`stop shared instance failed ${p}: ${a}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),s&&await s.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=A(this.configDir,"agents.json");try{const t=[];for(const[,s]of this.configMap){const i=this.rawConfigMap.get(s.name)?.entry.provider;t.push({name:s.name,ws_url:s.aibot.url,agent_id:s.aibot.agentId,api_key:s.aibot.apiKey,client_type:s.aibot.clientType,...i?{provider:i}:{}})}z(e,JSON.stringify({agents:t},null,4)+`
2
- `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const s={...r.entry,provider:t};await this.replaceInstance(e,s,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&ie(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(s){l.warn("manager",`Failed to clear provider config for "${r}": ${s instanceof Error?s.message:String(s)}`)}return t}async replaceInstance(e,t,r){const s=b(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(s,t,r),this.restoreCachedSharedInstances(s)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t,skipped:r}=N(this.configDir);for(const c of r)l.info("manager",`reload: skipped ${c}: not an agent config (no "agents" key)`);if(t.length>0){const c=t.map(a=>`${a.file}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${c}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=[];for(const{entry:c}of e)try{b(c,{quiet:!0})}catch(a){const g=c?.name,m=typeof g=="string"&&g.trim()?g:"<unknown>";s.push({name:m,error:a instanceof Error?a.message:String(a)})}if(s.length>0){const c=s.map(a=>`${a.name}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${s.length} agent config(s) invalid [${c}]`),{code:"RELOAD_UNSAFE"})}const i=new Map;for(const c of e){const a=String(c.entry?.name??"").trim();a&&(i.has(a)&&l.warn("manager",`reload: duplicate agent name "${a}", last one wins`),i.set(a,c))}const o={added:[],removed:[],restarted:[],unchanged:[],failed:[]},p=new Set(this.rawConfigMap.keys());for(const c of p)if(!i.has(c))try{await this.removeAgentInternal(c),o.removed.push(c)}catch(a){o.failed.push({name:c,error:a instanceof Error?a.message:String(a)})}for(const[c,a]of i){const g=this.rawConfigMap.get(c);if(g)if(Ae(g.entry,a.entry))this.rawConfigMap.set(c,a),o.unchanged.push(c);else try{await this.replaceInstance(c,a.entry,a.file),o.restarted.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}else try{await this.addAgentInternal(a.entry,a.file),o.added.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}}return l.info("manager",`reload done: +${o.added.length} -${o.removed.length} ~${o.restarted.length} =${o.unchanged.length} !${o.failed.length}`),o}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return ve(this.instances,e)}async probeOne(e,t={}){return be(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return pe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function ve(n,e){const t=e.concurrency??4,r=Date.now(),s=new Array(n.length);await new Promise(a=>{let g=0,m=0;const _=n.length;if(_===0){a();return}function S(h){const u=n[h];u.probe(e).then(y=>{s[h]=y,d()},y=>{s[h]={agent_name:u.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},d()})}function d(){m++,g<_?S(g++):m===_&&a()}const f=Math.min(t,_);for(let h=0;h<f;h++)S(g++)});const i=s.filter(a=>a.status==="healthy").length,o=s.filter(a=>a.status==="degraded").length,p=s.filter(a=>a.status==="unavailable").length,c=await ge();return{ok:i===s.length&&s.length>0,total:s.length,healthy:i,degraded:o,unavailable:p,installed_clients:c,agents:s,probed_at:r,duration_ms:Date.now()-r}}async function be(n,e,t){const r=n.find(s=>s.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Le as Manager,ge as probeInstalledClientCommands,ve as probeInstances,be as probeOneInstance};
1
+ import{readFileSync as K,readdirSync as G,writeFileSync as z}from"node:fs";import{join as v}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as J,diffSharedOwners as V,sharedInstanceKey as X,SharedOwnersCache as Y}from"./manager-share-config.js";import{isAgentDeletedError as D}from"./core/aibot/errors.js";import{GRIX_PATHS as w,log as l}from"./core/log/index.js";import{resolveClientVersion as Z}from"./core/util/client-version.js";import{UpgradeChecker as ee}from"./core/upgrade/upgrade-checker.js";import{SkillSyncer as te}from"./core/skill-sync/skill-syncer.js";import{AgentGlobalConfigStore as ne}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as ae}from"./adapter/claude/skill-scanner.js";import{buildWireSkills as re}from"./core/skill-sync/sync-state.js";import{buildReportedSkills as se,logDefaultSkillsCheck as oe,cleanupProjectedSkills as P}from"./default-skills/index.js";import{supportsDirectProviderConfig as ie}from"./core/config/provider-env.js";import{resolveCopilotCommand as O}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as q}from"./core/runtime/cursor-resolve.js";import{getCliVersion as ce,resolveCliPath as le}from"./core/util/cli-probe.js";import{AgentInstaller as de}from"./core/installer/installer.js";import{reportInstallFailure as pe}from"./core/observability/sentry.js";const me=8e3;function ue(){const t=O();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:q()},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:t.command},{clientType:"agy",command:"agy"}]}async function ge(){return Promise.all(ue().map(async t=>{const e=await le(t.command);if(!e)return{client_type:t.clientType,command:t.command,installed:!1,path:null,version:null};const n=await ce(e,t.versionArgs??["--version"]);return{client_type:t.clientType,command:t.command,installed:!0,path:e,version:n.version,error:n.error}}))}function he(t){switch(t){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:q()};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],options:{initial_mode:"yolo"},enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=O();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${t}`)}}function H(t,e){return String(t??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function fe(t,e){return v(w.data,`session-bindings-${H(t,e)}.json`)}function _e(t,e){return v(w.data,`active-events-${H(t,e)}.json`)}function ye(...t){const e=[],n=new Set;for(const r of t)for(const s of r??[]){const i=String(s??"").trim(),o=i.toLowerCase();!i||n.has(o)||(n.add(o),e.push(i))}return e.length>0?e:void 0}function we(t,e){const n={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=n[t]??n.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function b(t,e={}){const n=Z(),r=String(t.client_type??"").trim().toLowerCase(),s=he(r),i=String(t.ws_url??"").trim(),o="get_session_usage",p="get_rate_limits",c="get_agent_global_config";if(!t.name?.trim())throw new Error("agent name is required");if(!i)throw new Error(`agent ${t.name}: ws_url is required`);if(!t.agent_id?.trim())throw new Error(`agent ${t.name}: agent_id is required`);if(!t.api_key?.trim())throw new Error(`agent ${t.name}: api_key is required`);const a=s.adapterType,g=a==="acp",m=r==="qwen",_={...s.options??{}},S=a==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",o,p]}:null,d=a==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,p]}:null,f=m?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o],adapterHint:"qwen/base"}:null,h=a==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o]}:null,u=a==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,y=a==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",o,p]}:null,A=a==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,C=a==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,$=a==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,R=g&&!m?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,B=r==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,p]}:null,U=a==="codex"||a==="claude"||r==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[o,p];g&&_.raw_transport===void 0&&(_.raw_transport=r==="gemini");const L=`${r}/base`,I=ae(a,r),Q=I==="kiro"?void 0:process.cwd(),E=se({mode:I,projectDir:Q}),M=E.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&!e.quiet&&l.info("manager",`[${t.name}] injecting connector skills: [${M.join(", ")}]`);const j=re(E,w.skills);let W=j.length>0?j:void 0;return{name:t.name,adapterType:a,aibot:{url:i,agentId:t.agent_id,apiKey:t.api_key,clientType:r,clientVersion:n,adapterHint:s.adapterHint??f?.adapterHint??h?.adapterHint??u?.adapterHint??y?.adapterHint??C?.adapterHint??$?.adapterHint??L,capabilities:S?.capabilities??A?.capabilities??h?.capabilities??u?.capabilities??y?.capabilities??C?.capabilities??$?.capabilities??f?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ye(S?.localActions??A?.localActions??d?.localActions??h?.localActions??u?.localActions??y?.localActions??C?.localActions??$?.localActions??f?.localActions??B?.localActions??R?.localActions??["exec_approve","exec_reject"],U,F,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider","skill_upload"]),skills:W},agent:{command:s.command,args:[...s.args??[],...t.args??[]],env:t.env,clientType:r,...t.provider?{provider:{...t.provider.provider_id?.trim()?{providerId:t.provider.provider_id.trim()}:{},...t.provider.base_url?.trim()?{baseUrl:t.provider.base_url.trim()}:{},...t.provider.quota_base_url?.trim()?{quotaBaseUrl:t.provider.quota_base_url.trim()}:{},...t.provider.api_key?.trim()?{apiKey:t.provider.api_key.trim()}:{},...t.provider.model?.trim()?{model:t.provider.model.trim()}:{}}}:{}},adapterOptions:_,acpAuthMethod:_.auth_method,acpInitialMode:_.initial_mode,acpMcpTools:_.acp_mcp_tools,promptTimeoutMs:t.prompt_timeout_ms,bindingsPath:fe(t.name,t.agent_id),activeEventStorePath:_e(t.name,t.agent_id),...s.enableSessionBinding||g?{enableSessionBinding:!0}:{},...s.autoInjectArgs||r==="reasonix"&&t.provider?.model?.trim()?{autoInjectArgs:{...s.autoInjectArgs??{},...r==="reasonix"&&t.provider?.model?.trim()?{model:t.provider.model.trim()}:{}}}:{},poolMaxSize:t.pool?.maxSize,poolIdleTimeoutMs:t.pool?.idleTimeoutMs,eventQueue:we(a,t.event_queue),logDir:w.log,providerBaseUrl:t.provider_base_url?.trim()||void 0,providerApiKey:t.provider_api_key?.trim()||void 0}}function Se(){const t=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(t);return Number.isFinite(e)&&e>=500?Math.floor(e):me}function N(t){const e=[],n=[],r=[],s=G(t).filter(i=>i.endsWith(".json")).sort();for(const i of s)try{const o=JSON.parse(K(v(t,i),"utf-8"));if(o===null||typeof o!="object"||Array.isArray(o)||!("agents"in o)){r.push(i);continue}if(!Array.isArray(o.agents)){n.push({file:i,error:"agents is not an array"});continue}if(o.agents.length===0){n.push({file:i,error:"agents array is empty"});continue}for(const p of o.agents)e.push({entry:p,file:i})}catch(o){n.push({file:i,error:o instanceof Error?o.message:String(o)})}return{entries:e,fileErrors:n,skipped:r}}function k(t){if(t===null||typeof t!="object")return JSON.stringify(t)??"null";if(Array.isArray(t))return`[${t.map(k).join(",")}]`;const e=t;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${k(e[r])}`).join(",")}}`}function ve(t,e){return k(t)===k(e)}class Le{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;skillSyncer=null;globalConfigStore;configDir=w.config;sharedOwnersCache=new Y(w.data);installer=new de;async start(e){const n=e??w.config;this.configDir=n,l.info("manager",`Loading configs from ${n}`),oe(),P(),this.globalConfigStore=new ne(v(w.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:s,skipped:i}=N(n);for(const a of s)l.error("manager",`Failed to load ${a.file}: ${a.error}`);for(const a of i)l.info("manager",`Skipped ${a}: not an agent config (no "agents" key)`);if(r.length===0&&s.length===0&&i.length===0)throw new Error(`No config files found in ${n}`);const o=[];let p=s.length;for(const{entry:a,file:g}of r)try{const m=b(a);o.push({config:m,entry:a,file:g}),l.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${g}`)}catch(m){const _=typeof a?.name=="string"?a.name:"<unknown>";l.error("manager",`Invalid agent config in ${g} (name=${_}): ${m}`),p++}let c=0;if(o.length>0){const a=Se();l.info("manager",`Starting ${o.length} agent(s), startup wait=${a}ms`);const g=()=>this.upgradeChecker?.triggerCheck(),m=d=>{this.instances=this.instances.filter(f=>f!==d)},_=o.map(({config:d,entry:f,file:h})=>{const u=new T(d,this.globalConfigStore);return u.setUpgradeTrigger(g),u.setShareSetHandler(y=>this.onShareSet(d,y)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),u.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:f,file:h}),{config:d,instance:u,startPromise:u.start()}}),S=await Promise.all(_.map(async d=>{const f=await new Promise(h=>{let u=!1;const y=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},a);d.startPromise.then(()=>{u||(u=!0,clearTimeout(y),h({kind:"started"}))}).catch(A=>{u||(u=!0,clearTimeout(y),h({kind:"failed",error:A}))})});return{task:d,outcome:f}}));for(const{task:d,outcome:f}of S){if(f.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(f.kind==="failed"){if(D(f.error)){this.onAgentDeletedByPlatform(d.config.name);continue}m(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${f.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(D(h)){this.onAgentDeletedByPlatform(d.config.name);return}m(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${o.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&o.length>0)throw new Error("All agent configurations failed to start");o.length>0&&(this.upgradeChecker=new ee(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),()=>this.instances.some(a=>a.hasPendingOrBackgroundWork())||[...this.sharedInstances.values()].some(a=>a.hasPendingOrBackgroundWork())),await this.upgradeChecker.start()),o.length>0&&(this.skillSyncer=new te(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),w.skills),await this.skillSyncer.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),this.skillSyncer?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(n=>n.stop()),...e.map(n=>n.stop())]),await this.globalConfigStore?.flush(),this.instances=[],P(),l.info("manager","All stopped")}onShareSet(e,n){this.sharedOwnersCache.save(e.name,n);const s=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,n));this.shareSyncChains.set(e.name,s)}async syncSharedInstances(e,n){if(this.stopping)return;const{toAdd:r,toRemove:s}=V(e.name,this.sharedInstances.keys(),n);for(const i of r){if(this.stopping)break;const o=X(e.name,i);try{const p=J(e,i),c=new T(p,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),l.info("manager",`shared instance started: ${o}`)}catch(p){this.sharedInstances.delete(o);const c=p instanceof Error?p.message:String(p);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,i),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${p}`)}}for(const{key:i}of s){const o=this.sharedInstances.get(i);o&&(this.sharedInstances.delete(i),o.stop().catch(p=>l.error("manager",`stop shared instance failed ${i}: ${p}`)),l.info("manager",`shared instance stopped: ${i}`))}}restoreCachedSharedInstances(e){const n=this.sharedOwnersCache.load(e.name);n.length!==0&&(l.info("manager",`restoring ${n.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,n))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,n){const r=b(e);if(this.instances.some(s=>s.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,n)}async startInstanceFromConfig(e,n,r){const s=new T(e,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(i=>this.onShareSet(e,i)),s.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),s.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),s.setProviderConfigHandler(i=>this.setAgentProvider(e.name,i)),s.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await s.start(),this.instances.push(s),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:n,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(n=>n.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,n){const r=this.instances.findIndex(p=>p.name===e),s=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const i=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),i&&await i.catch(()=>{});const o=`${e}#shared:`;for(const[p,c]of[...this.sharedInstances.entries()])p.startsWith(o)&&(this.sharedInstances.delete(p),c.stop().catch(a=>l.error("manager",`stop shared instance failed ${p}: ${a}`)));n?.keepShareCache||this.sharedOwnersCache.delete(e),s&&await s.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(n){l.error("manager",`cleanup for deleted agent "${e}" failed: ${n}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=v(this.configDir,"agents.json");try{const n=[];for(const[,s]of this.configMap){const i=this.rawConfigMap.get(s.name)?.entry.provider;n.push({name:s.name,ws_url:s.aibot.url,agent_id:s.aibot.agentId,api_key:s.aibot.apiKey,client_type:s.aibot.clientType,...i?{provider:i}:{}})}z(e,JSON.stringify({agents:n},null,4)+`
2
+ `,"utf-8")}catch(n){l.error("manager",`Failed to persist agents config: ${n}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const n=this.instances.find(r=>r.name===e);return n?(n.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const n=this.rawConfigMap.get(e);if(!n)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,n.entry,n.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,n){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const s={...r.entry,provider:n};await this.replaceInstance(e,s,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&ie(r.entry.client_type)).map(([r])=>r),n=[];for(const r of e)try{await this.setAgentProvider(r,void 0),n.push(r)}catch(s){l.warn("manager",`Failed to clear provider config for "${r}": ${s instanceof Error?s.message:String(s)}`)}return n}async replaceInstance(e,n,r){const s=b(n);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(s,n,r),this.restoreCachedSharedInstances(s)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:n,skipped:r}=N(this.configDir);for(const c of r)l.info("manager",`reload: skipped ${c}: not an agent config (no "agents" key)`);if(n.length>0){const c=n.map(a=>`${a.file}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${n.length} config file(s) failed to parse [${c}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=[];for(const{entry:c}of e)try{b(c,{quiet:!0})}catch(a){const g=c?.name,m=typeof g=="string"&&g.trim()?g:"<unknown>";s.push({name:m,error:a instanceof Error?a.message:String(a)})}if(s.length>0){const c=s.map(a=>`${a.name}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${s.length} agent config(s) invalid [${c}]`),{code:"RELOAD_UNSAFE"})}const i=new Map;for(const c of e){const a=String(c.entry?.name??"").trim();a&&(i.has(a)&&l.warn("manager",`reload: duplicate agent name "${a}", last one wins`),i.set(a,c))}const o={added:[],removed:[],restarted:[],unchanged:[],failed:[]},p=new Set(this.rawConfigMap.keys());for(const c of p)if(!i.has(c))try{await this.removeAgentInternal(c),o.removed.push(c)}catch(a){o.failed.push({name:c,error:a instanceof Error?a.message:String(a)})}for(const[c,a]of i){const g=this.rawConfigMap.get(c);if(g)if(ve(g.entry,a.entry))this.rawConfigMap.set(c,a),o.unchanged.push(c);else try{await this.replaceInstance(c,a.entry,a.file),o.restarted.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}else try{await this.addAgentInternal(a.entry,a.file),o.added.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}}return l.info("manager",`reload done: +${o.added.length} -${o.removed.length} ~${o.restarted.length} =${o.unchanged.length} !${o.failed.length}`),o}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return Ae(this.instances,e)}async probeOne(e,n={}){return be(this.instances,e,n)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const n=await this.installer.install(e);return pe(n),n}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function Ae(t,e){const n=e.concurrency??4,r=Date.now(),s=new Array(t.length);await new Promise(a=>{let g=0,m=0;const _=t.length;if(_===0){a();return}function S(h){const u=t[h];u.probe(e).then(y=>{s[h]=y,d()},y=>{s[h]={agent_name:u.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},d()})}function d(){m++,g<_?S(g++):m===_&&a()}const f=Math.min(n,_);for(let h=0;h<f;h++)S(g++)});const i=s.filter(a=>a.status==="healthy").length,o=s.filter(a=>a.status==="degraded").length,p=s.filter(a=>a.status==="unavailable").length,c=await ge();return{ok:i===s.length&&s.length>0,total:s.length,healthy:i,degraded:o,unavailable:p,installed_clients:c,agents:s,probed_at:r,duration_ms:Date.now()-r}}async function be(t,e,n){const r=t.find(s=>s.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(n)}export{Le as Manager,ge as probeInstalledClientCommands,Ae as probeInstances,be as probeOneInstance};
@@ -1 +1 @@
1
- import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
1
+ import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
@@ -1 +1 @@
1
- const r=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
1
+ const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
@@ -1 +1 @@
1
- function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1 +1 @@
1
- import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(t,e,r,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,r);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(t.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${t.status}`);if(p(e))return this.executeEventTool(t,e,r);const o=i(e,r);try{const u=await t.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(t){if(t==null||typeof t!="object")return this.successResult(t??null);const e=t,r=typeof e.code=="number"?e.code:0;if(r===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${r}]: ${n}`)}successResult(t){return{content:[{type:"text",text:JSON.stringify(t)}],isError:!1}}errorResult(t){return{content:[{type:"text",text:t}],isError:!0}}async executeEventTool(t,e,r){return e==="grix_access_control"?this.executeAccessControl(t,r):d(t,e,r)}async executeAccessControl(t,e){const r=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[r];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${r}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await t.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
1
+ import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await r.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
@@ -1 +1 @@
1
- import{TOOLS as s,EVENT_TOOLS as t}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...s.filter(o=>e.has(o.name)),...t.filter(o=>r.has(o.name))],this.toolMap=new Map(this.tools.map(o=>[o.name,o]))}getTools(){return this.tools}getTool(o){return this.toolMap.get(o)}hasTool(o){return this.toolMap.has(o)}}export{a as ToolRegistryImpl};
1
+ import{TOOLS as o,EVENT_TOOLS as s}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}export{a as ToolRegistryImpl};
@@ -1 +1 @@
1
- const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(u,t){const e=C[u];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${u}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,r]of Object.entries(t)){if(r==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,r,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(u,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${u} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${u} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const r=t[i];if(e.items.type==="string"&&typeof r!="string")return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.type==="integer"){if(typeof r!="number"||!Number.isInteger(r))return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.enum&&!e.items.enum.includes(r))return`\u53C2\u6570 ${u}[${i}] \u503C ${r} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
1
+ const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};