grix-connector 3.29.2 → 3.30.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.
- package/README.md +10 -0
- package/dist/adapter/deepseek-harness/bridge-installer.js +3 -3
- package/dist/adapter/deepseek-harness/grix-tool-bridge.js +1 -1
- package/dist/adapter/deepseek-harness/jsonrpc-adapter.js +2 -2
- package/dist/adapter/deepseek-harness/process-runtime.js +1 -1
- package/dist/adapter/deepseek-harness/profile-resolver.js +1 -1
- package/dist/adapter/deepseek-harness/profile-runtime.js +2 -2
- package/dist/adapter/deepseek-harness/profile-supervisor.js +2 -2
- package/dist/adapter/deepseek-harness/toolbar-state.js +1 -1
- package/dist/adapter/deepseek-harness/types.js +1 -0
- package/dist/assets/dsh-bridge/grix-dsh-bridge-3.30.0.tgz +0 -0
- package/dist/assets/dsh-bridge/manifest.json +6 -6
- package/dist/bridge/bridge.js +4 -4
- package/dist/core/persistence/session-binding-store.js +2 -2
- package/dist/default-skills/grix-admin/SKILL.md +71 -211
- package/dist/default-skills/grix-admin/references/api-contract.md +43 -214
- package/dist/manager.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/openclaw-plugin/skills/grix-admin/SKILL.md +65 -202
- package/openclaw-plugin/skills/grix-admin/references/api-contract.md +35 -211
- package/package.json +1 -1
- package/dist/assets/dsh-bridge/grix-dsh-bridge-3.29.2.tgz +0 -0
|
@@ -1,28 +1,21 @@
|
|
|
1
|
-
# API Contract
|
|
1
|
+
# API Contract for grix-connector Agents
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
5
|
+
This contract covers remote Agent and category administration through `grix_admin`, followed by local binding to grix-connector when required.
|
|
12
6
|
|
|
13
7
|
## Base Rules
|
|
14
8
|
|
|
15
|
-
1. Do not ask users
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
20
|
-
6.
|
|
21
|
-
7. Always send the finalized behavioral description in the actual `introduction` field of `action=create_agent`; never substitute a chat summary, `soulContent`, or local persona file for this API field.
|
|
9
|
+
1. Do not ask users for a website account or password.
|
|
10
|
+
2. Perform remote creation and category actions through `grix_admin` on the current account's authenticated WS channel.
|
|
11
|
+
3. Require a concrete `agentName` and professional `introduction` before remote creation.
|
|
12
|
+
4. If agent parameters are incomplete and the current account cannot create remotely, require backend admin creation first.
|
|
13
|
+
5. Report the exact missing scope when the service returns `code=4003`.
|
|
14
|
+
6. Local config writing alone is not full convergence. Verify reload, `alive=true`, and the platform connection when the current invocation can do so.
|
|
22
15
|
|
|
23
|
-
## Direct
|
|
16
|
+
## Direct Actions
|
|
24
17
|
|
|
25
|
-
###
|
|
18
|
+
### Create Agent
|
|
26
19
|
|
|
27
20
|
```json
|
|
28
21
|
{
|
|
@@ -36,33 +29,14 @@
|
|
|
36
29
|
}
|
|
37
30
|
```
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
1. `createdAgent.id`
|
|
42
|
-
2. `createdAgent.agent_name`
|
|
43
|
-
3. `createdAgent.api_endpoint`
|
|
44
|
-
4. `createdAgent.api_key`
|
|
45
|
-
|
|
46
|
-
Required scope:
|
|
47
|
-
|
|
48
|
-
1. `agent.api.create`
|
|
49
|
-
2. If `categoryName` is included, may additionally need `agent.category.list`, `agent.category.create`, `agent.category.assign`
|
|
50
|
-
3. If `categoryId` is included, additionally needs `agent.category.assign`
|
|
32
|
+
Read `createdAgent.id`, `createdAgent.agent_name`, `createdAgent.api_endpoint`, and `createdAgent.api_key` from the result. Required scope is `agent.api.create`, plus category scopes when category fields are supplied.
|
|
51
33
|
|
|
52
|
-
###
|
|
34
|
+
### Category Actions
|
|
53
35
|
|
|
54
36
|
```json
|
|
55
|
-
{
|
|
56
|
-
"action": "list_categories"
|
|
57
|
-
}
|
|
37
|
+
{ "action": "list_categories" }
|
|
58
38
|
```
|
|
59
39
|
|
|
60
|
-
Required scope:
|
|
61
|
-
|
|
62
|
-
1. `agent.category.list`
|
|
63
|
-
|
|
64
|
-
### 3. Create Category
|
|
65
|
-
|
|
66
40
|
```json
|
|
67
41
|
{
|
|
68
42
|
"action": "create_category",
|
|
@@ -72,12 +46,6 @@ Required scope:
|
|
|
72
46
|
}
|
|
73
47
|
```
|
|
74
48
|
|
|
75
|
-
Required scope:
|
|
76
|
-
|
|
77
|
-
1. `agent.category.create`
|
|
78
|
-
|
|
79
|
-
### 4. Update Category
|
|
80
|
-
|
|
81
49
|
```json
|
|
82
50
|
{
|
|
83
51
|
"action": "update_category",
|
|
@@ -88,12 +56,6 @@ Required scope:
|
|
|
88
56
|
}
|
|
89
57
|
```
|
|
90
58
|
|
|
91
|
-
Required scope:
|
|
92
|
-
|
|
93
|
-
1. `agent.category.update`
|
|
94
|
-
|
|
95
|
-
### 5. Assign or Clear Category
|
|
96
|
-
|
|
97
59
|
```json
|
|
98
60
|
{
|
|
99
61
|
"action": "assign_category",
|
|
@@ -102,99 +64,31 @@ Required scope:
|
|
|
102
64
|
}
|
|
103
65
|
```
|
|
104
66
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
```json
|
|
108
|
-
{
|
|
109
|
-
"action": "assign_category",
|
|
110
|
-
"agentId": "10001",
|
|
111
|
-
"categoryId": "0"
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Required scope:
|
|
116
|
-
|
|
117
|
-
1. `agent.category.assign`
|
|
118
|
-
|
|
119
|
-
## Local Bind Steps
|
|
67
|
+
Use `categoryId: "0"` to clear an assignment. Required scopes are `agent.category.list`, `agent.category.create`, `agent.category.update`, and `agent.category.assign` respectively.
|
|
120
68
|
|
|
121
|
-
|
|
69
|
+
## Local Binding
|
|
122
70
|
|
|
123
|
-
|
|
71
|
+
Manage `~/.grix/config/agents.json` directly:
|
|
124
72
|
|
|
125
|
-
|
|
73
|
+
1. Initialize a missing file as `{ "agents": [] }`.
|
|
74
|
+
2. Match the existing entry by `name === agent_name`; update it or append a new entry.
|
|
75
|
+
3. Map fields as follows:
|
|
126
76
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- If still unavailable, clearly report error and stop
|
|
135
|
-
3. Read current config and merge:
|
|
136
|
-
- `channels.grix.accounts`
|
|
137
|
-
- `agents.list`
|
|
138
|
-
- `tools.profile`
|
|
139
|
-
- `tools.alsoAllow`
|
|
140
|
-
- `tools.sessions.visibility`
|
|
141
|
-
4. Write back using official CLI:
|
|
142
|
-
- `channels.grix.accounts.<agent_name>`
|
|
143
|
-
- `agents.list`
|
|
144
|
-
- `openclaw agents bind --agent <agent_name> --bind grix:<agent_name>`
|
|
145
|
-
- `tools.profile`
|
|
146
|
-
- `tools.alsoAllow`
|
|
147
|
-
- `tools.sessions.visibility`
|
|
148
|
-
- If needed, restore `channels.grix.enabled=true`
|
|
149
|
-
5. After writing, perform static validation first:
|
|
150
|
-
- `openclaw config validate`
|
|
151
|
-
- `openclaw config get --json channels.grix.accounts.<agent_name>`
|
|
152
|
-
- `openclaw config get --json agents.list`
|
|
153
|
-
- `openclaw agents bindings --agent <agent_name> --json`
|
|
154
|
-
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.
|
|
155
|
-
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.
|
|
156
|
-
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".
|
|
77
|
+
| Input | Config field |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `agent_name` | `name` |
|
|
80
|
+
| `api_endpoint` | `ws_url` |
|
|
81
|
+
| `agent_id` | `agent_id` |
|
|
82
|
+
| `api_key` | `api_key` |
|
|
83
|
+
| `client_type` | `client_type` |
|
|
157
84
|
|
|
158
|
-
|
|
85
|
+
4. Before writing, create `~/.grix/config/agents.json.bak.<YYYYMMDDHHMMSS>`.
|
|
86
|
+
5. Preserve valid JSON and set both files to `0o600`.
|
|
87
|
+
6. If the daemon is running, call synchronous `POST http://127.0.0.1:<admin-port>/api/reload`. Resolve a customized port from `~/.grix/data/admin-port`.
|
|
88
|
+
7. Verify through `GET http://127.0.0.1:<admin-port>/api/agents` that the entry exists and has `alive=true`.
|
|
89
|
+
8. Verify the platform connection through the latest daemon log or a test message. `alive=true` alone does not prove authentication succeeded.
|
|
159
90
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
1. Target file: `~/.grix/config/agents.json`. Initialize as `{ "agents": [] }` if missing.
|
|
163
|
-
2. Read the existing `agents` array. Match by `name === agent_name`:
|
|
164
|
-
- If found, update the existing entry in place.
|
|
165
|
-
- If not found, append a new entry.
|
|
166
|
-
3. Write the entry with these fields:
|
|
167
|
-
- `name`: `agent_name`
|
|
168
|
-
- `ws_url`: `api_endpoint`
|
|
169
|
-
- `agent_id`: `agent_id`
|
|
170
|
-
- `api_key`: `api_key`
|
|
171
|
-
- `client_type`: `client_type` (default `pi`)
|
|
172
|
-
4. Preserve valid JSON, set file permissions to `0o600`, and create a timestamped backup (also `0o600`) before writing.
|
|
173
|
-
5. Trigger reload through the synchronous Admin API:
|
|
174
|
-
- Default endpoint: `POST http://127.0.0.1:19580/api/reload`
|
|
175
|
-
- 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.
|
|
176
|
-
- If the admin port was customized via `GRIX_ADMIN_PORT` or `--admin-port`, read the actual port from `~/.grix/data/admin-port`.
|
|
177
|
-
6. Verify via Admin API `GET http://127.0.0.1:<admin-port>/api/agents`:
|
|
178
|
-
- Find entry where `name === agent_name`.
|
|
179
|
-
- Confirm the entry exists and reports `alive === true`.
|
|
180
|
-
- **Caveat**: `alive=true` only means the daemon started the instance. It does **not** prove the Agent successfully authenticated with the Grix platform.
|
|
181
|
-
7. Perform secondary platform-connection verification:
|
|
182
|
-
- Inspect daemon logs for WebSocket connection success / authentication failure messages for this Agent.
|
|
183
|
-
- Or ask the owner to send a test message to the Agent and confirm it responds.
|
|
184
|
-
8. If verification fails, report the observed status and stop; do not claim success.
|
|
185
|
-
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".
|
|
186
|
-
|
|
187
|
-
## `bind-local` Input Contract
|
|
188
|
-
|
|
189
|
-
```json
|
|
190
|
-
{
|
|
191
|
-
"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"
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
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.
|
|
196
|
-
|
|
197
|
-
## `connector-bind-local` Input Contract
|
|
91
|
+
## `connector-bind-local`
|
|
198
92
|
|
|
199
93
|
```json
|
|
200
94
|
{
|
|
@@ -202,55 +96,9 @@ This mode prioritizes local binding; if this invocation has real verification co
|
|
|
202
96
|
}
|
|
203
97
|
```
|
|
204
98
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
| Input field | JSON field | Notes |
|
|
208
|
-
| -------------- | ------------ | ------------------------------------------ |
|
|
209
|
-
| `agent_name` | `name` | Also used as the matching key |
|
|
210
|
-
| `api_endpoint` | `ws_url` | The WebSocket URL for the agent API |
|
|
211
|
-
| `agent_id` | `agent_id` | Platform agent ID |
|
|
212
|
-
| `api_key` | `api_key` | Agent API key |
|
|
213
|
-
| `client_type` | `client_type`| Optional, default `pi` |
|
|
99
|
+
This mode performs local binding only. Stop on config, reload, or verification failure and report the exact failed phase.
|
|
214
100
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
1. Backup `~/.grix/config/agents.json` before writing; set backup permissions to `0o600`.
|
|
218
|
-
2. Preserve valid JSON and set file permissions to `0o600`.
|
|
219
|
-
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).
|
|
220
|
-
4. Verify via `GET http://127.0.0.1:<admin-port>/api/agents` that the entry exists and has `alive=true`.
|
|
221
|
-
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.
|
|
222
|
-
|
|
223
|
-
## `create-and-bind` Input Contract
|
|
224
|
-
|
|
225
|
-
When the main agent already has an available account and `agent.api.create` scope, can enter the creation flow through `grix_admin.task`:
|
|
226
|
-
|
|
227
|
-
```json
|
|
228
|
-
{
|
|
229
|
-
"task": "create-and-bind\nagentName=ops helper\nintroduction=Professional deployment operations Agent responsible for release readiness checks, change coordination, on-call triage, and clear status reporting; requests confirmation before high-risk operations.\nisMain=false\ncategoryName=Project Assistant\nparentCategoryId=0\ncategorySortOrder=10"
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
This mode requires steps in order:
|
|
234
|
-
|
|
235
|
-
1. Confirm `agentName` and a professionally organized `introduction` are both present; otherwise ask the user about the Agent's purpose, core responsibilities, intended users, and boundaries before continuing
|
|
236
|
-
2. First make one direct call with `action=create_agent`, passing `agentName`, `introduction`, and optional `categoryId` / `categoryName` / `parentCategoryId` / `categorySortOrder` together
|
|
237
|
-
3. If the return already includes the category assignment result, continue directly
|
|
238
|
-
4. If the caller used a legacy path, or the return does not include the category assignment result, supplement with:
|
|
239
|
-
- `categoryId` -> `action=assign_category`
|
|
240
|
-
- `categoryName` -> `action=list_categories`
|
|
241
|
-
- Not found -> `action=create_category`
|
|
242
|
-
- After obtaining category ID -> `action=assign_category`
|
|
243
|
-
5. Finally follow the same local binding and runtime convergence flow as `bind-local`
|
|
244
|
-
|
|
245
|
-
Notes:
|
|
246
|
-
|
|
247
|
-
1. `categoryId` and `categoryName` cannot be provided simultaneously
|
|
248
|
-
2. When matching `categoryName`, must also consider `parentCategoryId`
|
|
249
|
-
3. If the remote return indicates missing `agent.api.create` or any `agent.category.*` scope, clearly state which specific scope is missing
|
|
250
|
-
|
|
251
|
-
## `create-and-connector-bind` Input Contract
|
|
252
|
-
|
|
253
|
-
When the main agent already has an available account and `agent.api.create` scope, and the target runtime is grix-connector:
|
|
101
|
+
## `create-and-connector-bind`
|
|
254
102
|
|
|
255
103
|
```json
|
|
256
104
|
{
|
|
@@ -258,24 +106,16 @@ When the main agent already has an available account and `agent.api.create` scop
|
|
|
258
106
|
}
|
|
259
107
|
```
|
|
260
108
|
|
|
261
|
-
|
|
109
|
+
Execution order:
|
|
262
110
|
|
|
263
|
-
1.
|
|
264
|
-
2.
|
|
265
|
-
3.
|
|
266
|
-
4. Read
|
|
267
|
-
5.
|
|
268
|
-
6.
|
|
111
|
+
1. Validate `agentName`, `introduction`, and category-field exclusivity.
|
|
112
|
+
2. Call `action=create_agent` once with all supplied creation and category fields.
|
|
113
|
+
3. Supplement category resolution only if the response did not complete it.
|
|
114
|
+
4. Read the returned agent parameters.
|
|
115
|
+
5. Continue with `connector-bind-local`, using `clientType` or the default `pi`.
|
|
116
|
+
6. Complete reload, `alive=true`, and platform-connection verification before claiming full convergence.
|
|
269
117
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
1. `categoryId` and `categoryName` cannot be provided simultaneously.
|
|
273
|
-
2. `clientType` defaults to `pi` when omitted.
|
|
274
|
-
3. If the remote return indicates missing `agent.api.create` or any `agent.category.*` scope, clearly state which specific scope is missing.
|
|
275
|
-
|
|
276
|
-
## `category-manage` Input Contract
|
|
277
|
-
|
|
278
|
-
When only doing subsequent category management, enter through `grix_admin.task`:
|
|
118
|
+
## `category-manage`
|
|
279
119
|
|
|
280
120
|
```json
|
|
281
121
|
{
|
|
@@ -283,15 +123,4 @@ When only doing subsequent category management, enter through `grix_admin.task`:
|
|
|
283
123
|
}
|
|
284
124
|
```
|
|
285
125
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
1. `operation=list` -> `action=list_categories`
|
|
289
|
-
2. `operation=create` -> `action=create_category`
|
|
290
|
-
3. `operation=update` -> `action=update_category`
|
|
291
|
-
4. `operation=assign` -> `action=assign_category`
|
|
292
|
-
|
|
293
|
-
Notes:
|
|
294
|
-
|
|
295
|
-
1. For `operation=assign`, `categoryId=0` means clear the category
|
|
296
|
-
2. No step can be executed cross-account
|
|
297
|
-
3. Do not hand-write HTTP or fall back to legacy scripts
|
|
126
|
+
Map `list`, `create`, `update`, and `assign` to their corresponding direct actions. Never execute across accounts or fall back to handwritten HTTP.
|
package/dist/manager.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFileSync as se,readdirSync as ie,writeFileSync as oe}from"node:fs";import{join as b}from"node:path";import{homedir as ce}from"node:os";import{AgentInstance as q}from"./bridge/bridge.js";import{buildSharedInstanceConfig as le,diffSharedOwners as de,sharedInstanceKey as pe,SharedOwnersCache as ue}from"./manager-share-config.js";import{isAgentDeletedError as B}from"./core/aibot/errors.js";import{GRIX_PATHS as w,log as l}from"./core/log/index.js";import{resolveClientVersion as me}from"./core/util/client-version.js";import{UpgradeChecker as ge}from"./core/upgrade/upgrade-checker.js";import{SkillSyncer as fe}from"./core/skill-sync/skill-syncer.js";import{AgentGlobalConfigStore as he}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as _e}from"./adapter/claude/skill-scanner.js";import{buildWireSkills as ye}from"./core/skill-sync/sync-state.js";import{buildLibrarySkills as we}from"./core/skill-sync/library-skills.js";import{AUDIT_LOCAL_ACTION_TYPES as Se}from"./core/aibot/types.js";import{buildReportedSkills as ke,logDefaultSkillsCheck as be,cleanupProjectedSkills as U}from"./default-skills/index.js";import{supportsKiroCwRelay as F,supportsNonMitmRelayConfig as ve}from"./core/config/provider-env.js";import{ensureKiroRelay as K,stopKiroRelay as Q}from"./core/proxy/kiro-relay.js";import{resolveCopilotCommand as W}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as G}from"./core/runtime/cursor-resolve.js";import{getCliVersion as Ae,resolveCliPath as Ce}from"./core/util/cli-probe.js";import{AgentInstaller as xe}from"./core/installer/installer.js";import{reportInstallFailure as $e}from"./core/observability/sentry.js";import{createDefaultAuditProducer as Ie}from"./audit/runtime/default-audit-recorder.js";import{normalizeCodexConfigOverrides as Te}from"./adapter/codex/config-overrides.js";import{resolveCodexDirectHome as Ee}from"./adapter/codex/direct-home.js";function z(n){if(n)return{...n.provider_id?.trim()?{providerId:n.provider_id.trim()}:{},...n.base_url?.trim()?{baseUrl:n.base_url.trim()}:{},...n.quota_base_url?.trim()?{quotaBaseUrl:n.quota_base_url.trim()}:{},...n.api_key?.trim()?{apiKey:n.api_key.trim()}:{},...n.model?.trim()?{model:n.model.trim()}:{},...n.fast_model?.trim()?{fastModel:n.fast_model.trim()}:{},...n.effort?.trim()?{effort:n.effort.trim()}:{}}}const Pe=8e3;function je(){const n=W();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:G()},{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"},{clientType:"deepseek",command:"dsh"}]}async function Me(){return Promise.all(je().map(async n=>{const e=await Ce(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await Ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function Re(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:G()};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=W();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"deepseek":return{adapterType:"deepseek-harness",command:"dsh",adapterHint:"deepseek/grix-bridge-v1",options:{integrationMode:"profile_bridge",dshProfile:"web",autoInstallBridge:!0,autoStartProfile:!0},enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function v(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 De(n){return b(w.data,`session-bindings-${v(n,"unknown")}.json`)}function Oe(n,e){return b(w.data,`session-bindings-${v(n,e)}.json`)}function He(n,e){return b(w.data,`active-events-${v(n,e)}.json`)}function Le(n,e){const t=v(n,"agent"),r=v(e,"unknown");return b(w.data,`terminal-outbox-${t}-${r}.json`)}function Ne(...n){const e=[],t=new Set;for(const r of n)for(const a of r??[]){const c=String(a??"").trim(),s=c.toLowerCase();!c||t.has(s)||(t.add(s),e.push(c))}return e.length>0?e:void 0}function qe(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},"deepseek-harness":{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 Be(n,e={}){const t=me(),r=String(n.client_type??"").trim().toLowerCase(),a=Re(r),c=r==="deepseek"?n.dsh?.integration_mode??"profile_bridge":void 0,s=c==="embedded_jsonrpc",p=String(n.ws_url??"").trim(),o="get_session_usage",i="get_rate_limits",h="get_agent_global_config",g=Se;if(!n.name?.trim())throw new Error("agent name is required");if(!p)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 d=a.adapterType,u=d==="acp",y=r==="qwen",m={...a.options??{}};if(d==="deepseek-harness"&&(m.integrationMode=c,m.dshProfile=n.dsh?.profile?.trim()||"web",m.autoInstallBridge=n.dsh?.auto_install_bridge??!s,m.autoStartProfile=n.dsh?.auto_start_profile??!s),d==="deepseek-harness"&&n.provider?.model?.trim()&&(m.defaultModel=n.provider.model.trim()),n.codex_config_overrides!==void 0){if(d!=="codex")throw new Error(`agent ${n.name}: codex_config_overrides is only supported for client_type=codex`);Te(n.codex_config_overrides,`agent ${n.name}: codex_config_overrides`),m.codexConfigOverrides=n.codex_config_overrides}if(d==="codex"&&n.provider?.wire_api!==void 0){const _=n.provider;if(_.wire_api!=="responses"||!_.base_url?.trim()||!_.api_key?.trim()||!_.model?.trim()||!_.catalog_version?.trim()||!_.catalog_sha256?.trim()||!_.catalog_json?.trim())throw Object.assign(new Error(`agent ${n.name}: incomplete Codex direct relay config`),{code:"INVALID_CODEX_DIRECT_CONFIG"});const N=Ee(w.data,n.name);m.codexHome=N,m.codexDirect={codexHome:N,config:{baseUrl:_.base_url.trim(),model:_.model.trim(),..._.reasoning_effort?.trim()?{reasoningEffort:_.reasoning_effort.trim()}:{},token:_.api_key.trim()},catalog:{version:_.catalog_version.trim(),sha256:_.catalog_sha256.trim(),json:_.catalog_json}}}const f=d==="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,i]}:null,k=d==="claude"?{localActions:["session_control","set_mode","set_model","set_reasoning_effort","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,i]}:null,S=y?{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,C=d==="pi"?{adapterHint:"pi/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o,i]}:null,x=d==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder"]}:null,$=d==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder","thread_compact","claude_interaction_reply",i]}:null,j=d==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,I=d==="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","claude_interaction_reply","file_list","create_folder"]}:null,T=d==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder"]}:null,E=d==="deepseek-harness"?{adapterHint:s?"deepseek/jsonrpc-v1":"deepseek/grix-bridge-v1",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_provider","set_model","set_mode","set_preset","set_profile","create_profile",o,i,"exec_approve","exec_reject","permission_approve","permission_reject","file_list","create_folder","dsh_bridge_status","dsh_install_bridge","dsh_list_plugins","dsh_enable_plugin","dsh_disable_plugin","dsh_refresh_plugins","dsh_list_skills","dsh_list_mcp","dsh_list_tools","dsh_list_jobs","dsh_list_goals"]}:null,M=new Set(["hermes","reasonix","kimi","copilot"]),J=u&&!M.has(r),X=u&&!y?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","claude_interaction_reply","file_list","create_folder",...J?[o]:[]]}:null,Y=r==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,i]}:null,Z=d==="codex"||d==="claude"||d==="opencode"||d==="deepseek-harness"||r==="gemini"?["session_control","set_model","set_mode",...d==="deepseek-harness"?["set_provider","set_preset","set_profile","create_profile","dsh_list_plugins","dsh_enable_plugin","dsh_disable_plugin","dsh_refresh_plugins"]:[],...d==="claude"?["set_reasoning_effort"]:[]]:void 0,ee=d==="cursor"||d==="opencode"||d==="openhuman"||d==="agy"||u&&M.has(r)?[i]:[o,i];u&&m.raw_transport===void 0&&(m.raw_transport=r==="gemini");const te=`${r}/base`,P=_e(d,r),ne=P==="kiro"?void 0:process.cwd(),R={...process.env,...n.env},D=ke({mode:P,projectDir:ne,env:R}),O=D.filter(_=>_.source==="connector").map(_=>_.name);O.length>0&&!e.quiet&&l.info("manager",`[${n.name}] injecting connector skills: [${O.join(", ")}]`);const H=ye(D,w.skills);let re=H.length>0?H:void 0;const ae=we({skillsDir:w.skills,mode:P,home:ce(),env:R}),L=z(n.provider);return{name:n.name,adapterType:d,aibot:{url:p,agentId:n.agent_id,apiKey:n.api_key,clientType:r,clientVersion:t,adapterHint:E?.adapterHint??a.adapterHint??S?.adapterHint??C?.adapterHint??x?.adapterHint??$?.adapterHint??I?.adapterHint??T?.adapterHint??te,capabilities:E?.capabilities??f?.capabilities??j?.capabilities??C?.capabilities??x?.capabilities??$?.capabilities??I?.capabilities??T?.capabilities??S?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:Ne(E?.localActions??f?.localActions??j?.localActions??k?.localActions??C?.localActions??x?.localActions??$?.localActions??I?.localActions??T?.localActions??S?.localActions??Y?.localActions??X?.localActions??["exec_approve","exec_reject"],Z,ee,["connector_rollback","connector_upgrade_push",h,"configure_gateway_provider","skill_upload","skill_enable","skill_disable","skill_refresh",...g]),skills:re,librarySkills:ae,terminalOutboxPath:Le(n.name,n.agent_id)},agent:{command:s?"dsh-jsonrpc-agent":a.command,args:[...a.args??[],...n.args??[]],env:n.env,clientType:r,...L?{provider:L}:{}},adapterOptions:m,acpAuthMethod:m.auth_method,acpInitialMode:m.initial_mode,acpMcpTools:m.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:De(n.agent_id),legacyBindingsPath:Oe(n.name,n.agent_id),activeEventStorePath:He(n.name,n.agent_id),...a.enableSessionBinding||u?{enableSessionBinding:!0}:{},...a.autoInjectArgs||r==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...a.autoInjectArgs??{},...r==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize??(d==="deepseek-harness"?4:void 0),poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:qe(d,n.event_queue),logDir:w.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function Ue(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):Pe}function V(n){const e=[],t=[],r=[],a=ie(n).filter(c=>c.endsWith(".json")).sort();for(const c of a)try{const s=JSON.parse(se(b(n,c),"utf-8"));if(s===null||typeof s!="object"||Array.isArray(s)||!("agents"in s)){r.push(c);continue}if(!Array.isArray(s.agents)){t.push({file:c,error:"agents is not an array"});continue}if(s.agents.length===0){t.push({file:c,error:"agents array is empty"});continue}for(const p of s.agents)e.push({entry:p,file:c})}catch(s){t.push({file:c,error:s instanceof Error?s.message:String(s)})}return{entries:e,fileErrors:t,skipped:r}}function A(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(A).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${A(e[r])}`).join(",")}}`}function Fe(n,e){return A(n)===A(e)}class yt{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 ue(w.data);installer=new xe;relaySecretResolver;auditProducer=Ie();async start(e){const t=e??w.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),be(),U(),this.globalConfigStore=new he(b(w.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:a,skipped:c}=V(t);for(const i of a)l.error("manager",`Failed to load ${i.file}: ${i.error}`);for(const i of c)l.info("manager",`Skipped ${i}: not an agent config (no "agents" key)`);if(r.length===0&&a.length===0&&c.length===0){l.warn("manager",`No config files found in ${t}; daemon idle (waiting for config). Add agents under this directory then run: grix-connector reload`);return}const s=[];let p=a.length;for(const{entry:i,file:h}of r)try{const g=this.toRuntimeInstanceConfig(i);s.push({config:g,entry:i,file:h}),l.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${h}`)}catch(g){const d=typeof i?.name=="string"?i.name:"<unknown>";l.error("manager",`Invalid agent config in ${h} (name=${d}): ${g}`),p++}let o=0;if(s.length>0){const i=Ue();l.info("manager",`Starting ${s.length} agent(s), startup wait=${i}ms`);const h=u=>{this.instances=this.instances.filter(y=>y!==u)},g=s.map(({config:u,entry:y,file:m})=>{const f=this.createConfiguredInstance(u);return this.instances.push(f),this.configMap.set(u.name,u),this.rawConfigMap.set(u.name,{entry:y,file:m}),{config:u,instance:f,startPromise:this.prepareProviderRuntime(u,y).then(()=>f.start())}}),d=await Promise.all(g.map(async u=>{const y=await new Promise(m=>{let f=!1;const k=setTimeout(()=>{f||(f=!0,m({kind:"timeout"}))},i);u.startPromise.then(()=>{f||(f=!0,clearTimeout(k),m({kind:"started"}))}).catch(S=>{f||(f=!0,clearTimeout(k),m({kind:"failed",error:S}))})});return{task:u,outcome:y}}));for(const{task:u,outcome:y}of d){if(y.kind==="started"){this.restoreCachedSharedInstances(u.config);continue}if(y.kind==="failed"){if(B(y.error)){this.onAgentDeletedByPlatform(u.config.name);continue}h(u.instance),l.error("manager",`Failed to start ${u.config.name}: ${y.error}`);continue}o++,l.warn("manager",`Startup pending for ${u.config.name}, continue retrying in background`),u.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${u.config.name}`),this.restoreCachedSharedInstances(u.config)}).catch(m=>{if(B(m)){this.onAgentDeletedByPlatform(u.config.name);return}h(u.instance),l.error("manager",`Delayed start failed: ${u.config.name}: ${m}`)})}if(this.instances.length>0){const u=Math.max(0,this.instances.length-o);l.info("manager",`${u}/${s.length} agent(s) running now`)}o>0&&l.warn("manager",`${o} agent(s) still connecting in background`)}if(this.instances.length===0&&s.length>0)throw new Error("All agent configurations failed to start");await this.ensureAuxServices()}async ensureAuxServices(){if(this.configMap.size===0)return;const e=[...this.configMap.values()].map(t=>({apiKey:t.aibot.apiKey,wsUrl:t.aibot.url}));this.upgradeChecker||(this.upgradeChecker=new ge(e,()=>this.instances.some(t=>t.hasPendingOrBackgroundWork())||[...this.sharedInstances.values()].some(t=>t.hasPendingOrBackgroundWork())),await this.upgradeChecker.start()),this.skillSyncer||(this.skillSyncer=new fe(e,w.skills),this.skillSyncer.setOnSyncSuccess(()=>this.refreshOwnerLibrarySkills()),await this.skillSyncer.start())}refreshOwnerLibrarySkills(){const e=new Map,t=r=>{let a;try{a=r.skillsSyncGroupKey()}catch(s){l.warn("manager",`skillsSyncGroupKey failed for ${r.name}: ${s}`);return}const c=e.get(a)??[];c.push(r),e.set(a,c)};for(const r of this.instances)t(r);for(const r of this.sharedInstances.values())t(r);for(const[r,a]of e){let c=!1;const s=[];for(const p of a){if(c){s.push(p);continue}let o=!1;try{o=p.forceRefreshSkills(void 0,{force:!0,ownerLibrarySync:!0})===!0}catch(i){l.warn("manager",`owner library skills reporter threw group=${r} agent=${p.name}: ${i}`)}if(o){c=!0;continue}l.warn("manager",`owner library skills reporter failed group=${r} agent=${p.name}; trying next`)}if(!c){l.warn("manager",`owner library skills: no reporter succeeded group=${r}`);continue}for(const p of s)try{p.adoptSkillsWireHashFromDisk()}catch(o){l.warn("manager",`adoptSkillsWireHashFromDisk failed for ${p.name}: ${o}`)}}}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(),await this.auditProducer.stop({timeoutMs:5e3}).catch(()=>!1),this.instances=[],U(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:a}=de(e.name,this.sharedInstances.keys(),t);for(const c of r){if(this.stopping)break;const s=pe(e.name,c);try{const p=le(e,c),o=new q(p,this.globalConfigStore,this.auditProducer);this.sharedInstances.set(s,o),await o.start(),o.forceRefreshSkills(void 0,{force:!0}),l.info("manager",`shared instance started: ${s}`)}catch(p){this.sharedInstances.delete(s);const o=p instanceof Error?p.message:String(p);/Auth failed/i.test(o)?(this.sharedOwnersCache.remove(e.name,c),l.warn("manager",`shared instance auth rejected, removed from cache: ${s}`)):l.error("manager",`start shared instance failed ${s}: ${p}`)}}for(const{key:c}of a){const s=this.sharedInstances.get(c);s&&(this.sharedInstances.delete(c),s.stop().catch(p=>l.error("manager",`stop shared instance failed ${c}: ${p}`)),l.info("manager",`shared instance stopped: ${c}`))}}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(),await this.ensureAuxServices(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=this.toRuntimeInstanceConfig(e);if(this.instances.some(a=>a.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}createConfiguredInstance(e){const t=new q(e,this.globalConfigStore,this.auditProducer);return t.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),t.setShareSetHandler(r=>this.onShareSet(e,r)),t.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),t.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),t.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),t.setRelayStateApplyPorts({getLocalState:()=>this.relayStateApplier?.getLocalState(e.name)??{enabled:!1},applyEnable:(r,a)=>{const c=this.relayStateApplier;return c?c.applyEnable(e.name,r,a):Promise.reject(new Error("relay state applier is not configured"))},applyDisable:()=>{const r=this.relayStateApplier;return r?r.applyDisable(e.name):Promise.reject(new Error("relay state applier is not configured"))}}),t.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),t.setProviderResolver(()=>z(this.getAgentProvider(e.name))),t}async prepareProviderRuntime(e,t){const r=this.resolveAgentProvider(t.provider);F(t.client_type)&&r?.base_url?.trim()&&r?.api_key?.trim()&&r?.model?.trim()&&await K(e.name,{baseUrl:r.base_url.trim(),apiKey:r.api_key.trim(),model:r.model.trim()})}async startInstanceFromConfig(e,t,r){await this.prepareProviderRuntime(e,t);const a=this.createConfiguredInstance(e);await a.start(),this.instances.push(a),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),a=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const c=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),c&&await c.catch(()=>{});const s=`${e}#shared:`;for(const[p,o]of[...this.sharedInstances.entries()])p.startsWith(s)&&(this.sharedInstances.delete(p),o.stop().catch(i=>l.error("manager",`stop shared instance failed ${p}: ${i}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop(),t?.keepShareCache||await Q(e).catch(()=>{})}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=b(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const c=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...c?{provider:c}:{}})}oe(e,JSON.stringify({agents:t},null,4)+`
|
|
1
|
+
import{readFileSync as se,readdirSync as ie,writeFileSync as oe}from"node:fs";import{join as b}from"node:path";import{homedir as ce}from"node:os";import{AgentInstance as q}from"./bridge/bridge.js";import{buildSharedInstanceConfig as le,diffSharedOwners as de,sharedInstanceKey as pe,SharedOwnersCache as ue}from"./manager-share-config.js";import{isAgentDeletedError as B}from"./core/aibot/errors.js";import{GRIX_PATHS as w,log as l}from"./core/log/index.js";import{resolveClientVersion as me}from"./core/util/client-version.js";import{UpgradeChecker as ge}from"./core/upgrade/upgrade-checker.js";import{SkillSyncer as fe}from"./core/skill-sync/skill-syncer.js";import{AgentGlobalConfigStore as he}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as _e}from"./adapter/claude/skill-scanner.js";import{buildWireSkills as ye}from"./core/skill-sync/sync-state.js";import{buildLibrarySkills as we}from"./core/skill-sync/library-skills.js";import{AUDIT_LOCAL_ACTION_TYPES as Se}from"./core/aibot/types.js";import{buildReportedSkills as ke,logDefaultSkillsCheck as be,cleanupProjectedSkills as U}from"./default-skills/index.js";import{supportsKiroCwRelay as F,supportsNonMitmRelayConfig as ve}from"./core/config/provider-env.js";import{ensureKiroRelay as K,stopKiroRelay as Q}from"./core/proxy/kiro-relay.js";import{resolveCopilotCommand as W}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as G}from"./core/runtime/cursor-resolve.js";import{getCliVersion as Ae,resolveCliPath as Ce}from"./core/util/cli-probe.js";import{AgentInstaller as xe}from"./core/installer/installer.js";import{reportInstallFailure as $e}from"./core/observability/sentry.js";import{createDefaultAuditProducer as Ie}from"./audit/runtime/default-audit-recorder.js";import{normalizeCodexConfigOverrides as Te}from"./adapter/codex/config-overrides.js";import{resolveCodexDirectHome as Ee}from"./adapter/codex/direct-home.js";function z(n){if(n)return{...n.provider_id?.trim()?{providerId:n.provider_id.trim()}:{},...n.base_url?.trim()?{baseUrl:n.base_url.trim()}:{},...n.quota_base_url?.trim()?{quotaBaseUrl:n.quota_base_url.trim()}:{},...n.api_key?.trim()?{apiKey:n.api_key.trim()}:{},...n.model?.trim()?{model:n.model.trim()}:{},...n.fast_model?.trim()?{fastModel:n.fast_model.trim()}:{},...n.effort?.trim()?{effort:n.effort.trim()}:{}}}const Pe=8e3;function je(){const n=W();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:G()},{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"},{clientType:"deepseek",command:"dsh"}]}async function Me(){return Promise.all(je().map(async n=>{const e=await Ce(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await Ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function Re(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:G()};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=W();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"deepseek":return{adapterType:"deepseek-harness",command:"dsh",adapterHint:"deepseek/grix-bridge-v1",options:{integrationMode:"profile_bridge",dshProfile:"web",autoInstallBridge:!0,autoStartProfile:!0},enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function v(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 De(n){return b(w.data,`session-bindings-${v(n,"unknown")}.json`)}function Oe(n,e){return b(w.data,`session-bindings-${v(n,e)}.json`)}function He(n,e){return b(w.data,`active-events-${v(n,e)}.json`)}function Le(n,e){const t=v(n,"agent"),r=v(e,"unknown");return b(w.data,`terminal-outbox-${t}-${r}.json`)}function Ne(...n){const e=[],t=new Set;for(const r of n)for(const a of r??[]){const c=String(a??"").trim(),s=c.toLowerCase();!c||t.has(s)||(t.add(s),e.push(c))}return e.length>0?e:void 0}function qe(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},"deepseek-harness":{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 Be(n,e={}){const t=me(),r=String(n.client_type??"").trim().toLowerCase(),a=Re(r),c=r==="deepseek"?n.dsh?.integration_mode??"profile_bridge":void 0,s=c==="embedded_jsonrpc",p=String(n.ws_url??"").trim(),o="get_session_usage",i="get_rate_limits",h="get_agent_global_config",g=Se;if(!n.name?.trim())throw new Error("agent name is required");if(!p)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 d=a.adapterType,u=d==="acp",y=r==="qwen",m={...a.options??{}};if(d==="deepseek-harness"&&(m.integrationMode=c,m.dshProfile=n.dsh?.profile?.trim()||"web",m.autoInstallBridge=n.dsh?.auto_install_bridge??!s,m.autoStartProfile=n.dsh?.auto_start_profile??!s),d==="deepseek-harness"&&n.provider?.model?.trim()&&(m.defaultModel=n.provider.model.trim()),n.codex_config_overrides!==void 0){if(d!=="codex")throw new Error(`agent ${n.name}: codex_config_overrides is only supported for client_type=codex`);Te(n.codex_config_overrides,`agent ${n.name}: codex_config_overrides`),m.codexConfigOverrides=n.codex_config_overrides}if(d==="codex"&&n.provider?.wire_api!==void 0){const _=n.provider;if(_.wire_api!=="responses"||!_.base_url?.trim()||!_.api_key?.trim()||!_.model?.trim()||!_.catalog_version?.trim()||!_.catalog_sha256?.trim()||!_.catalog_json?.trim())throw Object.assign(new Error(`agent ${n.name}: incomplete Codex direct relay config`),{code:"INVALID_CODEX_DIRECT_CONFIG"});const N=Ee(w.data,n.name);m.codexHome=N,m.codexDirect={codexHome:N,config:{baseUrl:_.base_url.trim(),model:_.model.trim(),..._.reasoning_effort?.trim()?{reasoningEffort:_.reasoning_effort.trim()}:{},token:_.api_key.trim()},catalog:{version:_.catalog_version.trim(),sha256:_.catalog_sha256.trim(),json:_.catalog_json}}}const f=d==="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,i]}:null,k=d==="claude"?{localActions:["session_control","set_mode","set_model","set_reasoning_effort","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,i]}:null,S=y?{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,C=d==="pi"?{adapterHint:"pi/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o,i]}:null,x=d==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder"]}:null,$=d==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder","thread_compact","claude_interaction_reply",i]}:null,j=d==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,I=d==="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","claude_interaction_reply","file_list","create_folder"]}:null,T=d==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder"]}:null,E=d==="deepseek-harness"?{adapterHint:s?"deepseek/jsonrpc-v1":"deepseek/grix-bridge-v1",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_provider","set_model","set_mode","set_thinking","set_reasoning_effort","set_preset","set_profile","create_profile",o,i,"exec_approve","exec_reject","permission_approve","permission_reject","file_list","create_folder","dsh_bridge_status","dsh_install_bridge","dsh_list_plugins","dsh_enable_plugin","dsh_disable_plugin","dsh_refresh_plugins","dsh_list_skills","dsh_list_mcp","dsh_list_tools","dsh_list_jobs","dsh_list_goals"]}:null,M=new Set(["hermes","reasonix","kimi","copilot"]),J=u&&!M.has(r),X=u&&!y?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","claude_interaction_reply","file_list","create_folder",...J?[o]:[]]}:null,Y=r==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,i]}:null,Z=d==="codex"||d==="claude"||d==="opencode"||d==="deepseek-harness"||r==="gemini"?["session_control","set_model","set_mode",...d==="deepseek-harness"?["set_provider","set_thinking","set_reasoning_effort","set_preset","set_profile","create_profile","dsh_list_plugins","dsh_enable_plugin","dsh_disable_plugin","dsh_refresh_plugins"]:[],...d==="claude"?["set_reasoning_effort"]:[]]:void 0,ee=d==="cursor"||d==="opencode"||d==="openhuman"||d==="agy"||u&&M.has(r)?[i]:[o,i];u&&m.raw_transport===void 0&&(m.raw_transport=r==="gemini");const te=`${r}/base`,P=_e(d,r),ne=P==="kiro"?void 0:process.cwd(),R={...process.env,...n.env},D=ke({mode:P,projectDir:ne,env:R}),O=D.filter(_=>_.source==="connector").map(_=>_.name);O.length>0&&!e.quiet&&l.info("manager",`[${n.name}] injecting connector skills: [${O.join(", ")}]`);const H=ye(D,w.skills);let re=H.length>0?H:void 0;const ae=we({skillsDir:w.skills,mode:P,home:ce(),env:R}),L=z(n.provider);return{name:n.name,adapterType:d,aibot:{url:p,agentId:n.agent_id,apiKey:n.api_key,clientType:r,clientVersion:t,adapterHint:E?.adapterHint??a.adapterHint??S?.adapterHint??C?.adapterHint??x?.adapterHint??$?.adapterHint??I?.adapterHint??T?.adapterHint??te,capabilities:E?.capabilities??f?.capabilities??j?.capabilities??C?.capabilities??x?.capabilities??$?.capabilities??I?.capabilities??T?.capabilities??S?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:Ne(E?.localActions??f?.localActions??j?.localActions??k?.localActions??C?.localActions??x?.localActions??$?.localActions??I?.localActions??T?.localActions??S?.localActions??Y?.localActions??X?.localActions??["exec_approve","exec_reject"],Z,ee,["connector_rollback","connector_upgrade_push",h,"configure_gateway_provider","skill_upload","skill_enable","skill_disable","skill_refresh",...g]),skills:re,librarySkills:ae,terminalOutboxPath:Le(n.name,n.agent_id)},agent:{command:s?"dsh-jsonrpc-agent":a.command,args:[...a.args??[],...n.args??[]],env:n.env,clientType:r,...L?{provider:L}:{}},adapterOptions:m,acpAuthMethod:m.auth_method,acpInitialMode:m.initial_mode,acpMcpTools:m.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:De(n.agent_id),legacyBindingsPath:Oe(n.name,n.agent_id),activeEventStorePath:He(n.name,n.agent_id),...a.enableSessionBinding||u?{enableSessionBinding:!0}:{},...a.autoInjectArgs||r==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...a.autoInjectArgs??{},...r==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize??(d==="deepseek-harness"?4:void 0),poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:qe(d,n.event_queue),logDir:w.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function Ue(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):Pe}function V(n){const e=[],t=[],r=[],a=ie(n).filter(c=>c.endsWith(".json")).sort();for(const c of a)try{const s=JSON.parse(se(b(n,c),"utf-8"));if(s===null||typeof s!="object"||Array.isArray(s)||!("agents"in s)){r.push(c);continue}if(!Array.isArray(s.agents)){t.push({file:c,error:"agents is not an array"});continue}if(s.agents.length===0){t.push({file:c,error:"agents array is empty"});continue}for(const p of s.agents)e.push({entry:p,file:c})}catch(s){t.push({file:c,error:s instanceof Error?s.message:String(s)})}return{entries:e,fileErrors:t,skipped:r}}function A(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(A).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${A(e[r])}`).join(",")}}`}function Fe(n,e){return A(n)===A(e)}class yt{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 ue(w.data);installer=new xe;relaySecretResolver;auditProducer=Ie();async start(e){const t=e??w.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),be(),U(),this.globalConfigStore=new he(b(w.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:a,skipped:c}=V(t);for(const i of a)l.error("manager",`Failed to load ${i.file}: ${i.error}`);for(const i of c)l.info("manager",`Skipped ${i}: not an agent config (no "agents" key)`);if(r.length===0&&a.length===0&&c.length===0){l.warn("manager",`No config files found in ${t}; daemon idle (waiting for config). Add agents under this directory then run: grix-connector reload`);return}const s=[];let p=a.length;for(const{entry:i,file:h}of r)try{const g=this.toRuntimeInstanceConfig(i);s.push({config:g,entry:i,file:h}),l.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${h}`)}catch(g){const d=typeof i?.name=="string"?i.name:"<unknown>";l.error("manager",`Invalid agent config in ${h} (name=${d}): ${g}`),p++}let o=0;if(s.length>0){const i=Ue();l.info("manager",`Starting ${s.length} agent(s), startup wait=${i}ms`);const h=u=>{this.instances=this.instances.filter(y=>y!==u)},g=s.map(({config:u,entry:y,file:m})=>{const f=this.createConfiguredInstance(u);return this.instances.push(f),this.configMap.set(u.name,u),this.rawConfigMap.set(u.name,{entry:y,file:m}),{config:u,instance:f,startPromise:this.prepareProviderRuntime(u,y).then(()=>f.start())}}),d=await Promise.all(g.map(async u=>{const y=await new Promise(m=>{let f=!1;const k=setTimeout(()=>{f||(f=!0,m({kind:"timeout"}))},i);u.startPromise.then(()=>{f||(f=!0,clearTimeout(k),m({kind:"started"}))}).catch(S=>{f||(f=!0,clearTimeout(k),m({kind:"failed",error:S}))})});return{task:u,outcome:y}}));for(const{task:u,outcome:y}of d){if(y.kind==="started"){this.restoreCachedSharedInstances(u.config);continue}if(y.kind==="failed"){if(B(y.error)){this.onAgentDeletedByPlatform(u.config.name);continue}h(u.instance),l.error("manager",`Failed to start ${u.config.name}: ${y.error}`);continue}o++,l.warn("manager",`Startup pending for ${u.config.name}, continue retrying in background`),u.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${u.config.name}`),this.restoreCachedSharedInstances(u.config)}).catch(m=>{if(B(m)){this.onAgentDeletedByPlatform(u.config.name);return}h(u.instance),l.error("manager",`Delayed start failed: ${u.config.name}: ${m}`)})}if(this.instances.length>0){const u=Math.max(0,this.instances.length-o);l.info("manager",`${u}/${s.length} agent(s) running now`)}o>0&&l.warn("manager",`${o} agent(s) still connecting in background`)}if(this.instances.length===0&&s.length>0)throw new Error("All agent configurations failed to start");await this.ensureAuxServices()}async ensureAuxServices(){if(this.configMap.size===0)return;const e=[...this.configMap.values()].map(t=>({apiKey:t.aibot.apiKey,wsUrl:t.aibot.url}));this.upgradeChecker||(this.upgradeChecker=new ge(e,()=>this.instances.some(t=>t.hasPendingOrBackgroundWork())||[...this.sharedInstances.values()].some(t=>t.hasPendingOrBackgroundWork())),await this.upgradeChecker.start()),this.skillSyncer||(this.skillSyncer=new fe(e,w.skills),this.skillSyncer.setOnSyncSuccess(()=>this.refreshOwnerLibrarySkills()),await this.skillSyncer.start())}refreshOwnerLibrarySkills(){const e=new Map,t=r=>{let a;try{a=r.skillsSyncGroupKey()}catch(s){l.warn("manager",`skillsSyncGroupKey failed for ${r.name}: ${s}`);return}const c=e.get(a)??[];c.push(r),e.set(a,c)};for(const r of this.instances)t(r);for(const r of this.sharedInstances.values())t(r);for(const[r,a]of e){let c=!1;const s=[];for(const p of a){if(c){s.push(p);continue}let o=!1;try{o=p.forceRefreshSkills(void 0,{force:!0,ownerLibrarySync:!0})===!0}catch(i){l.warn("manager",`owner library skills reporter threw group=${r} agent=${p.name}: ${i}`)}if(o){c=!0;continue}l.warn("manager",`owner library skills reporter failed group=${r} agent=${p.name}; trying next`)}if(!c){l.warn("manager",`owner library skills: no reporter succeeded group=${r}`);continue}for(const p of s)try{p.adoptSkillsWireHashFromDisk()}catch(o){l.warn("manager",`adoptSkillsWireHashFromDisk failed for ${p.name}: ${o}`)}}}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(),await this.auditProducer.stop({timeoutMs:5e3}).catch(()=>!1),this.instances=[],U(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:a}=de(e.name,this.sharedInstances.keys(),t);for(const c of r){if(this.stopping)break;const s=pe(e.name,c);try{const p=le(e,c),o=new q(p,this.globalConfigStore,this.auditProducer);this.sharedInstances.set(s,o),await o.start(),o.forceRefreshSkills(void 0,{force:!0}),l.info("manager",`shared instance started: ${s}`)}catch(p){this.sharedInstances.delete(s);const o=p instanceof Error?p.message:String(p);/Auth failed/i.test(o)?(this.sharedOwnersCache.remove(e.name,c),l.warn("manager",`shared instance auth rejected, removed from cache: ${s}`)):l.error("manager",`start shared instance failed ${s}: ${p}`)}}for(const{key:c}of a){const s=this.sharedInstances.get(c);s&&(this.sharedInstances.delete(c),s.stop().catch(p=>l.error("manager",`stop shared instance failed ${c}: ${p}`)),l.info("manager",`shared instance stopped: ${c}`))}}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(),await this.ensureAuxServices(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=this.toRuntimeInstanceConfig(e);if(this.instances.some(a=>a.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}createConfiguredInstance(e){const t=new q(e,this.globalConfigStore,this.auditProducer);return t.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),t.setShareSetHandler(r=>this.onShareSet(e,r)),t.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),t.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),t.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),t.setRelayStateApplyPorts({getLocalState:()=>this.relayStateApplier?.getLocalState(e.name)??{enabled:!1},applyEnable:(r,a)=>{const c=this.relayStateApplier;return c?c.applyEnable(e.name,r,a):Promise.reject(new Error("relay state applier is not configured"))},applyDisable:()=>{const r=this.relayStateApplier;return r?r.applyDisable(e.name):Promise.reject(new Error("relay state applier is not configured"))}}),t.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),t.setProviderResolver(()=>z(this.getAgentProvider(e.name))),t}async prepareProviderRuntime(e,t){const r=this.resolveAgentProvider(t.provider);F(t.client_type)&&r?.base_url?.trim()&&r?.api_key?.trim()&&r?.model?.trim()&&await K(e.name,{baseUrl:r.base_url.trim(),apiKey:r.api_key.trim(),model:r.model.trim()})}async startInstanceFromConfig(e,t,r){await this.prepareProviderRuntime(e,t);const a=this.createConfiguredInstance(e);await a.start(),this.instances.push(a),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),a=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const c=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),c&&await c.catch(()=>{});const s=`${e}#shared:`;for(const[p,o]of[...this.sharedInstances.entries()])p.startsWith(s)&&(this.sharedInstances.delete(p),o.stop().catch(i=>l.error("manager",`stop shared instance failed ${p}: ${i}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop(),t?.keepShareCache||await Q(e).catch(()=>{})}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=b(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const c=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...c?{provider:c}:{}})}oe(e,JSON.stringify({agents:t},null,4)+`
|
|
2
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}relayStateApplier;setRelayStateApplier(e){this.relayStateApplier=e}reportRelayStateLocalChange(e){this.instances.find(r=>r.name===e)?.reportRelayStateLocalChange()}setRelaySecretResolver(e){this.relaySecretResolver=e}resolveAgentProvider(e){if(!e?.api_key_ref?.trim())return e;const t=e.api_key_ref.trim(),r=this.relaySecretResolver?.(t);if(!r)throw Object.assign(new Error(`relay credential "${t}" is unavailable`),{code:"RELAY_SECRET_UNAVAILABLE"});return{...e,api_key:r}}toRuntimeInstanceConfig(e,t={}){const r=this.resolveAgentProvider(e.provider);return Be(r===e.provider?e:{...e,provider:r},t)}getAgentProvider(e){return this.resolveAgentProvider(this.rawConfigMap.get(e)?.entry.provider)}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,r){const a=this.rawConfigMap.get(e);if(!a)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const c=String(a.entry.client_type??"").trim().toLowerCase();F(c)&&(t?.base_url?.trim()&&t?.api_key?.trim()&&t?.model?.trim()?await K(e,{baseUrl:t.base_url.trim(),apiKey:t.api_key.trim(),model:t.model.trim()}):await Q(e));const s=t?.api_key_ref?.trim()?Object.fromEntries(Object.entries(t).filter(([o])=>o!=="api_key")):t,p={...a.entry,provider:s};if(r?.deferRestart){this.rawConfigMap.set(e,{...a,entry:p}),this.instances.find(o=>o.name===e)?.markRelayEnvStale(),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent (deferred restart): ${e}`);return}await this.replaceInstance(e,p,a.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}hasDirectRelayConfig(e){const t=this.rawConfigMap.get(e)?.entry.provider;return!!(t?.api_key_ref?.trim()||t?.api_key?.trim())}getDirectRelayConfigAgentNames(){return[...this.rawConfigMap.entries()].filter(([,e])=>!!e.entry.provider&&(ve(e.entry.client_type)||["claude","codex"].includes(String(e.entry.client_type??"").trim().toLowerCase()))).map(([e])=>e)}async fetchRelayCredential(e,t){const r=this.instances.find(a=>a.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.fetchRelayCredential(t)}async clearDirectProviderConfigs(){const e=this.getDirectRelayConfigAgentNames(),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0,{deferRestart:this.isAgentBusy(r)}),t.push(r)}catch(a){l.warn("manager",`Failed to clear provider config for "${r}": ${a instanceof Error?a.message:String(a)}`)}return t}async replaceInstance(e,t,r){const a=this.toRuntimeInstanceConfig(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,r),this.restoreCachedSharedInstances(a)}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}=V(this.configDir);for(const o of r)l.info("manager",`reload: skipped ${o}: not an agent config (no "agents" key)`);if(t.length>0){const o=t.map(i=>`${i.file}: ${i.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const a=[];for(const{entry:o}of e)try{this.toRuntimeInstanceConfig(o,{quiet:!0})}catch(i){const h=o?.name,g=typeof h=="string"&&h.trim()?h:"<unknown>";a.push({name:g,error:i instanceof Error?i.message:String(i)})}if(a.length>0){const o=a.map(i=>`${i.name}: ${i.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${a.length} agent config(s) invalid [${o}]`),{code:"RELOAD_UNSAFE"})}const c=new Map;for(const o of e){const i=String(o.entry?.name??"").trim();i&&(c.has(i)&&l.warn("manager",`reload: duplicate agent name "${i}", last one wins`),c.set(i,o))}const s={added:[],removed:[],restarted:[],unchanged:[],failed:[]},p=new Set(this.rawConfigMap.keys());for(const o of p)if(!c.has(o))try{await this.removeAgentInternal(o),s.removed.push(o)}catch(i){s.failed.push({name:o,error:i instanceof Error?i.message:String(i)})}for(const[o,i]of c){const h=this.rawConfigMap.get(o);if(h)if(Fe(h.entry,i.entry))this.rawConfigMap.set(o,i),s.unchanged.push(o);else try{await this.replaceInstance(o,i.entry,i.file),s.restarted.push(o)}catch(g){s.failed.push({name:o,error:g instanceof Error?g.message:String(g)})}else try{await this.addAgentInternal(i.entry,i.file),s.added.push(o)}catch(g){s.failed.push({name:o,error:g instanceof Error?g.message:String(g)})}}return l.info("manager",`reload done: +${s.added.length} -${s.removed.length} ~${s.restarted.length} =${s.unchanged.length} !${s.failed.length}`),await this.ensureAuxServices(),s}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return Ke(this.instances,e)}async probeOne(e,t={}){return Qe(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return $e(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function Ke(n,e){const t=e.concurrency??4,r=Date.now(),a=new Array(n.length);await new Promise(i=>{let h=0,g=0;const d=n.length;if(d===0){i();return}function u(f){const k=n[f];k.probe(e).then(S=>{a[f]=S,y()},S=>{a[f]={agent_name:k.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:S?.message??String(S)}},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}},y()})}function y(){g++,h<d?u(h++):g===d&&i()}const m=Math.min(t,d);for(let f=0;f<m;f++)u(h++)});const c=a.filter(i=>i.status==="healthy").length,s=a.filter(i=>i.status==="degraded").length,p=a.filter(i=>i.status==="unavailable").length,o=await Me();return{ok:c===a.length&&a.length>0,total:a.length,healthy:c,degraded:s,unavailable:p,installed_clients:o,agents:a,probed_at:r,duration_ms:Date.now()-r}}async function Qe(n,e,t){const r=n.find(a=>a.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{yt as Manager,z as normalizeAgentProviderForRuntime,Me as probeInstalledClientCommands,Ke as probeInstances,Qe as probeOneInstance};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(
|
|
1
|
+
function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
|