@0xmaxma/claude-gateway 1.8.7 → 1.8.9
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 +96 -0
- package/dist/agent/runner.d.ts +47 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +116 -17
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/connectors-router.d.ts +50 -0
- package/dist/api/connectors-router.d.ts.map +1 -0
- package/dist/api/connectors-router.js +650 -0
- package/dist/api/connectors-router.js.map +1 -0
- package/dist/api/gateway-router.d.ts +4 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +26 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/oauth-connectors-router.d.ts +43 -0
- package/dist/api/oauth-connectors-router.d.ts.map +1 -0
- package/dist/api/oauth-connectors-router.js +384 -0
- package/dist/api/oauth-connectors-router.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +101 -13
- package/dist/api/router.js.map +1 -1
- package/dist/apps/agent-manager.d.ts +6 -2
- package/dist/apps/agent-manager.d.ts.map +1 -1
- package/dist/apps/agent-manager.js +10 -18
- package/dist/apps/agent-manager.js.map +1 -1
- package/dist/config/config-write-lock.d.ts +45 -0
- package/dist/config/config-write-lock.d.ts.map +1 -0
- package/dist/config/config-write-lock.js +164 -0
- package/dist/config/config-write-lock.js.map +1 -0
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +29 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/connectors/custom-connectors-store.d.ts +54 -0
- package/dist/connectors/custom-connectors-store.d.ts.map +1 -0
- package/dist/connectors/custom-connectors-store.js +204 -0
- package/dist/connectors/custom-connectors-store.js.map +1 -0
- package/dist/connectors/custom.d.ts +69 -0
- package/dist/connectors/custom.d.ts.map +1 -0
- package/dist/connectors/custom.js +158 -0
- package/dist/connectors/custom.js.map +1 -0
- package/dist/connectors/mcp-oauth.d.ts +152 -0
- package/dist/connectors/mcp-oauth.d.ts.map +1 -0
- package/dist/connectors/mcp-oauth.js +522 -0
- package/dist/connectors/mcp-oauth.js.map +1 -0
- package/dist/connectors/oauth-refresh-sweep.d.ts +71 -0
- package/dist/connectors/oauth-refresh-sweep.d.ts.map +1 -0
- package/dist/connectors/oauth-refresh-sweep.js +337 -0
- package/dist/connectors/oauth-refresh-sweep.js.map +1 -0
- package/dist/connectors/pending-oauth-store.d.ts +39 -0
- package/dist/connectors/pending-oauth-store.d.ts.map +1 -0
- package/dist/connectors/pending-oauth-store.js +50 -0
- package/dist/connectors/pending-oauth-store.js.map +1 -0
- package/dist/connectors/resolve.d.ts +52 -0
- package/dist/connectors/resolve.d.ts.map +1 -0
- package/dist/connectors/resolve.js +176 -0
- package/dist/connectors/resolve.js.map +1 -0
- package/dist/connectors/token-env.d.ts +93 -0
- package/dist/connectors/token-env.d.ts.map +1 -0
- package/dist/connectors/token-env.js +323 -0
- package/dist/connectors/token-env.js.map +1 -0
- package/dist/connectors/types.d.ts +126 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +13 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts +23 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +78 -2
- package/dist/session/process.js.map +1 -1
- package/dist/types.d.ts +46 -1
- package/dist/types.d.ts.map +1 -1
- package/mcp/instructions.ts +14 -1
- package/mcp/server.ts +11 -5
- package/mcp/tools/image/module.ts +2 -70
- package/mcp/tools/shared/media.ts +76 -0
- package/mcp/tools/video/module.ts +676 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -352,6 +352,91 @@ public base — e.g. LINE, which derives its host from the inbound webhook — b
|
|
|
352
352
|
`<base>/shared/<token>` themselves. HTTP is accepted only for local development
|
|
353
353
|
hosts such as `http://host.docker.internal:10850/gateway`.
|
|
354
354
|
|
|
355
|
+
### `gateway.oauthReturnUrl` (optional)
|
|
356
|
+
|
|
357
|
+
Where to send the browser after a connector OAuth sign-in finishes. The gateway is
|
|
358
|
+
product-agnostic and never hardcodes a downstream app's domain, so this is opt-in.
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"gateway": {
|
|
363
|
+
"oauthReturnUrl": "https://app.example.com/settings/connectors"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Set, the callback issues a real `302` to it on **every** terminal outcome — success, and
|
|
369
|
+
also a denied, expired or failed sign-in, which carries `?connector_oauth_error=<code>`.
|
|
370
|
+
Unset, the callback renders a plain "Connected — you can close this tab" page instead.
|
|
371
|
+
The value is validated once at startup: anything that isn't a well-formed `http(s)` URL
|
|
372
|
+
is logged and ignored rather than injecting a broken redirect into every future callback.
|
|
373
|
+
The scheme is part of that check — this value becomes the `Location` of a redirect sent
|
|
374
|
+
to the end user's own browser from a public route, so a `javascript:` or `data:` URL
|
|
375
|
+
here would be script running on every sign-in, and is refused like any other malformed
|
|
376
|
+
value.
|
|
377
|
+
|
|
378
|
+
### `gateway.customConnectors` (optional)
|
|
379
|
+
|
|
380
|
+
User-pasted MCP connectors, keyed by a slugified id. Normally written through the API
|
|
381
|
+
(`POST /api/v1/connectors/custom`) rather than by hand.
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"gateway": {
|
|
386
|
+
"customConnectors": {
|
|
387
|
+
"firecrawl": {
|
|
388
|
+
"label": "Firecrawl",
|
|
389
|
+
"config": {
|
|
390
|
+
"type": "streamable-http",
|
|
391
|
+
"url": "https://mcp.firecrawl.dev/v2/mcp-oauth",
|
|
392
|
+
"headers": { "Authorization": "Bearer {access_token}" }
|
|
393
|
+
},
|
|
394
|
+
"secretNames": ["access_token"],
|
|
395
|
+
"credentialOwner": "gateway"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Each entry is raw `mcpServers`-entry JSON with `{placeholder}` tokens standing in for
|
|
403
|
+
secrets. `credentialOwner` records who holds the credential and keeps it valid — `none`,
|
|
404
|
+
`static` (a pasted value), `gateway` (this gateway ran the OAuth flow and refreshes the
|
|
405
|
+
token itself) or `external` (a control plane pushes tokens in). It is written by the
|
|
406
|
+
route that creates the entry; see [API.md](./API.md#connectors-api). **Only the placeholder names are stored here** — the values live in
|
|
407
|
+
`~/.claude-gateway/mcp-token.env` (mode `0600`), namespaced
|
|
408
|
+
`CUSTOM__<connectorId>__<placeholderName>`, and are substituted in when a session spawns.
|
|
409
|
+
Override that file's path with `GATEWAY_MCP_TOKEN_ENV_PATH`.
|
|
410
|
+
|
|
411
|
+
Custom connectors are **admin-trusted but not code-reviewed** — the config is whatever
|
|
412
|
+
the admin pasted, and it becomes an MCP server in every agent's session. Per-agent
|
|
413
|
+
enablement is opt-out and lives on the agent instead (`PATCH /api/v1/agents/:id` with
|
|
414
|
+
`connectors`); connecting a connector at all is the security gate. See
|
|
415
|
+
[API.md](./API.md#connectors-api) for the full model, the OAuth flow, and the refresh
|
|
416
|
+
behaviour.
|
|
417
|
+
|
|
418
|
+
### `gateway.connectorsDefaultEnabled` (optional)
|
|
419
|
+
|
|
420
|
+
Whether a connected connector is available to an agent that has no explicit entry in its
|
|
421
|
+
own `connectors` map. Defaults to `true` — opt-out: connecting a connector makes it
|
|
422
|
+
available everywhere, and an agent only misses it if explicitly disabled.
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"gateway": {
|
|
427
|
+
"connectorsDefaultEnabled": false
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Set it to `false` on a gateway that hosts agents for **more than one person**. The default
|
|
433
|
+
suits the common single-operator install, but with several owners it hands a credential
|
|
434
|
+
connected by one of them to every agent on the box — including agents whose chat users are
|
|
435
|
+
not that person. With `false`, each agent has to be opted in explicitly (`PATCH
|
|
436
|
+
/api/v1/agents/:id` with `{"connectors": {"<id>": {"enabled": true}}}`).
|
|
437
|
+
|
|
438
|
+
Changing this affects the next session spawn, like any other connector change.
|
|
439
|
+
|
|
355
440
|
### `gateway.logs` (optional)
|
|
356
441
|
|
|
357
442
|
Verbosity, rotation and retention for the files in `logDir`. The whole block is optional —
|
|
@@ -744,6 +829,8 @@ Tools are **prefixed by channel name** to avoid collisions. Each module controls
|
|
|
744
829
|
|
|
745
830
|
**Adding a new channel** (e.g. Slack) means implementing `ChannelModule` interface in `mcp/tools/slack/module.ts` and registering it in `server.ts`.
|
|
746
831
|
|
|
832
|
+
**Connectors** are the other half of the MCP picture: where the modules above are tools the gateway itself implements, a connector is an **external** MCP server the gateway injects into a session's `mcp-config.json`. The gateway stores only the connector definition, the per-connector secret (`~/.claude-gateway/mcp-token.env`) and the per-agent enablement — Claude Code then talks to that server directly. See [`gateway.customConnectors`](#gatewaycustomconnectors-optional) and [API.md](./API.md#connectors-api).
|
|
833
|
+
|
|
747
834
|
### Process Modes
|
|
748
835
|
|
|
749
836
|
| Mode | Process | Behaviour |
|
|
@@ -918,6 +1005,14 @@ Pass API key via `X-Api-Key: <key>` or `Authorization: Bearer <key>` header.
|
|
|
918
1005
|
| `POST` | `/api/v1/apps/:name/start\|stop\|restart` | Start/stop/restart app containers (admin) |
|
|
919
1006
|
| `POST` | `/api/v1/apps/:name/update` | Blue-green update with auto-rollback → `jobId` (admin) |
|
|
920
1007
|
| `POST` | `/api/v1/apps/:name/reconfigure` | Change env vars / host ports on an installed app, with rollback → `jobId` (admin) |
|
|
1008
|
+
| `GET` | `/api/v1/connectors` | List connectors with connected state |
|
|
1009
|
+
| `GET` | `/api/v1/connectors/:id/status` | Connected state for one connector (for polling) |
|
|
1010
|
+
| `POST` | `/api/v1/connectors/:id/connect` | Store a pasted token (admin) |
|
|
1011
|
+
| `POST` | `/api/v1/connectors/:id/oauth/receive` | Accept a token pushed by an external control plane (admin) |
|
|
1012
|
+
| `DELETE` | `/api/v1/connectors/:id` | Disconnect a connector (admin) |
|
|
1013
|
+
| `POST` | `/api/v1/connectors/custom` | Add a user-pasted connector (admin) |
|
|
1014
|
+
| `POST` | `/api/v1/connectors/custom/:id/oauth/start` | Begin OAuth 2.1 + PKCE sign-in → `authorizeUrl` (admin) |
|
|
1015
|
+
| `GET` | `/oauth/mcp/callback` | OAuth redirect target (public — guarded by a single-use `state`) |
|
|
921
1016
|
| `GET` | `/app/:name/:portName/*` | Reverse proxy to installed app (no auth) |
|
|
922
1017
|
|
|
923
1018
|
**Wizard API** — create agents programmatically with the same flow as the interactive `claude-gateway agents create` terminal wizard. The wizard generates workspace files via Claude, writes them on confirm, and optionally pairs a Telegram/Discord bot. State is in-memory with a 30-minute TTL; nothing is written until `/confirm`. See [API.md](./API.md) for the full wizard flow.
|
|
@@ -1082,6 +1177,7 @@ claude-gateway/
|
|
|
1082
1177
|
```
|
|
1083
1178
|
~/.claude-gateway/
|
|
1084
1179
|
├── config.json ← gateway config
|
|
1180
|
+
├── mcp-token.env ← connector secrets, mode 0600 (see `gateway.customConnectors`)
|
|
1085
1181
|
├── logs/
|
|
1086
1182
|
│ ├── alfred.log
|
|
1087
1183
|
│ ├── alfred.log.1 ← rotated generation (see `gateway.logs`)
|
package/dist/agent/runner.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
-
import { AgentConfig, GatewayConfig, Logger, ModelConfig, ApiAttachment, ImageParams } from '../types';
|
|
2
|
+
import { AgentConfig, CustomConnectorEntry, GatewayConfig, Logger, ModelConfig, ApiAttachment, ImageParams } from '../types';
|
|
3
|
+
import { SessionProcess } from '../session/process';
|
|
3
4
|
import { type SkillRegistry } from '../skills';
|
|
4
5
|
import { type ApiStreamCallbacks, type TurnSink } from './turn-stream';
|
|
5
6
|
import { type CliChannel } from '../cli-viewer/pairing-store';
|
|
@@ -145,6 +146,11 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
145
146
|
private handleCommandRequest;
|
|
146
147
|
/**
|
|
147
148
|
* Persist the current model to config.json using atomic write (tmp + rename).
|
|
149
|
+
*
|
|
150
|
+
* Runs under the process-wide config write lock: the read and the rename below are
|
|
151
|
+
* synchronous and so cannot interleave with each other, but without the lock they
|
|
152
|
+
* can still land between an async writer's read and its own rename — dropping this
|
|
153
|
+
* model change, or being dropped by it. See config/config-write-lock.ts.
|
|
148
154
|
*/
|
|
149
155
|
private persistModelToConfig;
|
|
150
156
|
private static escapeXmlAttr;
|
|
@@ -364,11 +370,51 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
364
370
|
restartOrDefer(opts?: {
|
|
365
371
|
skipBusy?: boolean;
|
|
366
372
|
deferIdle?: boolean;
|
|
373
|
+
filter?: (proc: SessionProcess) => boolean;
|
|
367
374
|
}): Promise<{
|
|
368
375
|
immediate: number;
|
|
369
376
|
deferred: number;
|
|
370
377
|
skipped: number;
|
|
371
378
|
}>;
|
|
379
|
+
/**
|
|
380
|
+
* Called after a connector's secret or definition changes: a connector was
|
|
381
|
+
* connected, an external control plane pushed a fresh access_token for one it
|
|
382
|
+
* owns (POST /oauth/receive), the background refresh sweep in
|
|
383
|
+
* oauth-refresh-sweep.ts (invoked from gateway-router.ts's 60s interval)
|
|
384
|
+
* rotated one, or a connector was deleted / had its refresh given up on.
|
|
385
|
+
*
|
|
386
|
+
* Restarts exactly the sessions whose own mcp-config no longer matches what
|
|
387
|
+
* `connectorId` resolves to for this agent — see
|
|
388
|
+
* SessionProcess.connectorConfigChanged, which owns that comparison. The
|
|
389
|
+
* stdio MCP subprocess reads its env once at spawn (see writeMcpConfig), so
|
|
390
|
+
* there is no way to hot-patch a running child; a session that IS affected
|
|
391
|
+
* genuinely needs a respawn, and one that isn't must not be disrupted.
|
|
392
|
+
*
|
|
393
|
+
* `overlay` is merged over this runner's view of `gateway.customConnectors` —
|
|
394
|
+
* and adopted into it, so the sessions restarted below respawn from the new
|
|
395
|
+
* state rather than the one the watcher has not delivered yet (see the inline
|
|
396
|
+
* note). Callers that have just written a connector entry need it:
|
|
397
|
+
* config.json's watcher propagates asynchronously, so a route that creates a
|
|
398
|
+
* connector and immediately asks what it resolves to would otherwise be
|
|
399
|
+
* answered from a config snapshot taken before its own write. A `null` value
|
|
400
|
+
* expresses the other half of that write — the entry was REMOVED — which a
|
|
401
|
+
* merge alone cannot say. Deleting a connector whose owner is `'none'` has no
|
|
402
|
+
* secrets to clear, so without it the stale snapshot still resolves the entry
|
|
403
|
+
* to the identical fingerprint, nothing looks changed, and every live session
|
|
404
|
+
* keeps the deleted MCP server connected and callable while the API reports it
|
|
405
|
+
* gone.
|
|
406
|
+
*
|
|
407
|
+
* Deliberately takes no "the caller already decided" escape hatch. The delete
|
|
408
|
+
* and give-up paths needed one while this asked whether the connector still
|
|
409
|
+
* resolves — after `deleteSecrets` it never does, so the answer was always no
|
|
410
|
+
* and the restart silently did nothing. Per-session comparison answers those
|
|
411
|
+
* paths correctly on its own: resolving to nothing IS the change.
|
|
412
|
+
*/
|
|
413
|
+
restartSessionsUsingConnector(connectorId: string, opts?: {
|
|
414
|
+
overlay?: Record<string, CustomConnectorEntry | null>;
|
|
415
|
+
}): Promise<{
|
|
416
|
+
restarted: boolean;
|
|
417
|
+
}>;
|
|
372
418
|
/**
|
|
373
419
|
* Format a timestamp as a human-readable age string (e.g. "5m ago", "2h ago").
|
|
374
420
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAOtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAW,WAAW,EAAe,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -42,6 +42,7 @@ const fsPromises = __importStar(require("fs/promises"));
|
|
|
42
42
|
const http = __importStar(require("http"));
|
|
43
43
|
const os = __importStar(require("os"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
|
+
const config_write_lock_1 = require("../config/config-write-lock");
|
|
45
46
|
const logger_1 = require("../logger");
|
|
46
47
|
const process_1 = require("../session/process");
|
|
47
48
|
const store_1 = require("../session/store");
|
|
@@ -68,6 +69,7 @@ const db_1 = require("../history/db");
|
|
|
68
69
|
const media_store_1 = require("../history/media-store");
|
|
69
70
|
const cleanup_1 = require("../history/cleanup");
|
|
70
71
|
const knowledge_1 = require("./knowledge");
|
|
72
|
+
const resolve_1 = require("../connectors/resolve");
|
|
71
73
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
|
|
72
74
|
const DEFAULT_MAX_CONCURRENT = 20;
|
|
73
75
|
const ANTHROPIC_SOCKET_ERROR = 'socket connection was closed unexpectedly';
|
|
@@ -909,23 +911,22 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
909
911
|
}
|
|
910
912
|
/**
|
|
911
913
|
* Persist the current model to config.json using atomic write (tmp + rename).
|
|
914
|
+
*
|
|
915
|
+
* Runs under the process-wide config write lock: the read and the rename below are
|
|
916
|
+
* synchronous and so cannot interleave with each other, but without the lock they
|
|
917
|
+
* can still land between an async writer's read and its own rename — dropping this
|
|
918
|
+
* model change, or being dropped by it. See config/config-write-lock.ts.
|
|
912
919
|
*/
|
|
913
920
|
persistModelToConfig(newModel) {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
agent
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
// prior crashed write is already sitting at this fixed path, so chmod
|
|
924
|
-
// explicitly rather than relying on it.
|
|
925
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
926
|
-
fs.chmodSync(tmp, 0o600);
|
|
927
|
-
fs.renameSync(tmp, this.configPath);
|
|
928
|
-
}
|
|
921
|
+
return (0, config_write_lock_1.withConfigWriteLock)(this.configPath, () => {
|
|
922
|
+
const raw = fs.readFileSync(this.configPath, 'utf-8');
|
|
923
|
+
const config = JSON.parse(raw);
|
|
924
|
+
const agent = config.agents?.find((a) => a.id === this.agentConfig.id);
|
|
925
|
+
if (agent) {
|
|
926
|
+
agent.claude.model = newModel;
|
|
927
|
+
(0, config_write_lock_1.writeConfigAtomicSync)(this.configPath, config);
|
|
928
|
+
}
|
|
929
|
+
});
|
|
929
930
|
}
|
|
930
931
|
static escapeXmlAttr(value) {
|
|
931
932
|
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -1180,12 +1181,23 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1180
1181
|
const refs = (p.image_refs ?? []).filter((r) => typeof r === 'string' && r.trim().length > 0);
|
|
1181
1182
|
if (!attrs.length && !refs.length)
|
|
1182
1183
|
return '';
|
|
1184
|
+
// Make the model authoritative, mirroring the explicit-selection wording used
|
|
1185
|
+
// for reference images below. The composer dropdown is the user's explicit
|
|
1186
|
+
// choice, but the generate_image schema lets the agent pick its own model; when
|
|
1187
|
+
// the model isn't nailed down the agent falls back to action="list" and
|
|
1188
|
+
// self-selects a different one (the composer selection is then silently ignored).
|
|
1189
|
+
const modelNote = p.model
|
|
1190
|
+
? `The user explicitly SELECTED model="${AgentRunner.escapeXmlAttr(p.model)}" in the composer. ` +
|
|
1191
|
+
`Call generate_image with that exact model — do NOT call action="list" to second-guess an ` +
|
|
1192
|
+
`explicit selection or substitute a different model.\n`
|
|
1193
|
+
: '';
|
|
1183
1194
|
if (!refs.length) {
|
|
1184
1195
|
return (`<image-params ${attrs.join(' ')} />\n` +
|
|
1185
1196
|
`The user selected the image-generation options above in the composer. When the request ` +
|
|
1186
1197
|
`involves creating or editing an image, call the generate_image tool (action="generate") ` +
|
|
1187
1198
|
`using these values (pass image_ref as the "image" argument for image-to-image), then ` +
|
|
1188
|
-
`deliver the returned image with your reply tool.\n`
|
|
1199
|
+
`deliver the returned image with your reply tool.\n` +
|
|
1200
|
+
modelNote);
|
|
1189
1201
|
}
|
|
1190
1202
|
// Explicitly selected reference images (#73). Rendered as nested elements so a
|
|
1191
1203
|
// ref containing separators (commas, quotes) stays unambiguous.
|
|
@@ -1200,6 +1212,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1200
1212
|
`The user selected the image-generation options above in the composer. When the request ` +
|
|
1201
1213
|
`involves creating or editing an image, call the generate_image tool (action="generate") ` +
|
|
1202
1214
|
`using these values, then deliver the returned image with your reply tool.\n` +
|
|
1215
|
+
modelNote +
|
|
1203
1216
|
`The user explicitly SELECTED the reference image(s) listed above in the composer, in this order. ` +
|
|
1204
1217
|
`${passInstruction} Do NOT reinterpret which images they are, do NOT call list_refs to ` +
|
|
1205
1218
|
`second-guess an explicit selection, and do not drop any of them. ` +
|
|
@@ -2459,11 +2472,17 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2459
2472
|
async restartOrDefer(opts) {
|
|
2460
2473
|
const skipBusy = opts?.skipBusy ?? false;
|
|
2461
2474
|
const deferIdle = opts?.deferIdle ?? false;
|
|
2475
|
+
const filter = opts?.filter;
|
|
2462
2476
|
let immediate = 0;
|
|
2463
2477
|
let deferred = 0;
|
|
2464
2478
|
let skipped = 0;
|
|
2465
2479
|
const toStopNow = [];
|
|
2466
2480
|
for (const [id, proc] of this.sessions) {
|
|
2481
|
+
// Not counted as `skipped`: that reports sessions this call declined to
|
|
2482
|
+
// restart even though they needed one (the skipBusy case). A session the
|
|
2483
|
+
// change does not reach at all was never a candidate.
|
|
2484
|
+
if (filter && !filter(proc))
|
|
2485
|
+
continue;
|
|
2467
2486
|
if (proc.isProcessing) {
|
|
2468
2487
|
if (skipBusy) {
|
|
2469
2488
|
skipped++;
|
|
@@ -2516,6 +2535,86 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2516
2535
|
this.logger.info('restartOrDefer: sessions restarted', { immediate, deferred, skipped });
|
|
2517
2536
|
return { immediate, deferred, skipped };
|
|
2518
2537
|
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Called after a connector's secret or definition changes: a connector was
|
|
2540
|
+
* connected, an external control plane pushed a fresh access_token for one it
|
|
2541
|
+
* owns (POST /oauth/receive), the background refresh sweep in
|
|
2542
|
+
* oauth-refresh-sweep.ts (invoked from gateway-router.ts's 60s interval)
|
|
2543
|
+
* rotated one, or a connector was deleted / had its refresh given up on.
|
|
2544
|
+
*
|
|
2545
|
+
* Restarts exactly the sessions whose own mcp-config no longer matches what
|
|
2546
|
+
* `connectorId` resolves to for this agent — see
|
|
2547
|
+
* SessionProcess.connectorConfigChanged, which owns that comparison. The
|
|
2548
|
+
* stdio MCP subprocess reads its env once at spawn (see writeMcpConfig), so
|
|
2549
|
+
* there is no way to hot-patch a running child; a session that IS affected
|
|
2550
|
+
* genuinely needs a respawn, and one that isn't must not be disrupted.
|
|
2551
|
+
*
|
|
2552
|
+
* `overlay` is merged over this runner's view of `gateway.customConnectors` —
|
|
2553
|
+
* and adopted into it, so the sessions restarted below respawn from the new
|
|
2554
|
+
* state rather than the one the watcher has not delivered yet (see the inline
|
|
2555
|
+
* note). Callers that have just written a connector entry need it:
|
|
2556
|
+
* config.json's watcher propagates asynchronously, so a route that creates a
|
|
2557
|
+
* connector and immediately asks what it resolves to would otherwise be
|
|
2558
|
+
* answered from a config snapshot taken before its own write. A `null` value
|
|
2559
|
+
* expresses the other half of that write — the entry was REMOVED — which a
|
|
2560
|
+
* merge alone cannot say. Deleting a connector whose owner is `'none'` has no
|
|
2561
|
+
* secrets to clear, so without it the stale snapshot still resolves the entry
|
|
2562
|
+
* to the identical fingerprint, nothing looks changed, and every live session
|
|
2563
|
+
* keeps the deleted MCP server connected and callable while the API reports it
|
|
2564
|
+
* gone.
|
|
2565
|
+
*
|
|
2566
|
+
* Deliberately takes no "the caller already decided" escape hatch. The delete
|
|
2567
|
+
* and give-up paths needed one while this asked whether the connector still
|
|
2568
|
+
* resolves — after `deleteSecrets` it never does, so the answer was always no
|
|
2569
|
+
* and the restart silently did nothing. Per-session comparison answers those
|
|
2570
|
+
* paths correctly on its own: resolving to nothing IS the change.
|
|
2571
|
+
*/
|
|
2572
|
+
async restartSessionsUsingConnector(connectorId, opts) {
|
|
2573
|
+
const overlay = opts?.overlay;
|
|
2574
|
+
const customConnectors = {
|
|
2575
|
+
...this.gatewayConfig?.gateway?.customConnectors,
|
|
2576
|
+
...overlay,
|
|
2577
|
+
};
|
|
2578
|
+
for (const [id, entry] of Object.entries(overlay ?? {})) {
|
|
2579
|
+
if (entry === null)
|
|
2580
|
+
delete customConnectors[id];
|
|
2581
|
+
}
|
|
2582
|
+
// Adopted into the config object, not just used for the comparison below.
|
|
2583
|
+
//
|
|
2584
|
+
// The overlay's whole purpose is that this runner's view of config.json is
|
|
2585
|
+
// older than the write the caller just made — and the sessions this method
|
|
2586
|
+
// restarts respawn from that same view: SessionProcess.writeMcpConfig reads
|
|
2587
|
+
// `gatewayConfig.gateway.customConnectors` at spawn (this is the very object
|
|
2588
|
+
// it was handed — see the constructor call above), so a session stopped
|
|
2589
|
+
// because a connector changed came back up with the pre-change connector
|
|
2590
|
+
// set. The watcher's 500ms debounce means that is the normal case, not a
|
|
2591
|
+
// narrow race: connect a connector and the agent respawns still without the
|
|
2592
|
+
// tool, delete one and it respawns still holding it.
|
|
2593
|
+
//
|
|
2594
|
+
// And it does not self-correct. The fresh spawn records the STALE resolution
|
|
2595
|
+
// as its fingerprint, so when the watcher finally lands nothing compares it
|
|
2596
|
+
// again — this method is the only caller of connectorConfigChanged, and it
|
|
2597
|
+
// has already run. The wrong config sticks until something unrelated
|
|
2598
|
+
// restarts the session.
|
|
2599
|
+
//
|
|
2600
|
+
// Mutating the config object is exactly what index.ts's `changes` handler
|
|
2601
|
+
// does to this same long-lived object on every reload, so this is the
|
|
2602
|
+
// established way to publish a config change to running runners rather than
|
|
2603
|
+
// a new mechanism. Idempotent across runners: they share the object, and
|
|
2604
|
+
// every one of them merges the same overlay onto it.
|
|
2605
|
+
if (overlay) {
|
|
2606
|
+
this.gatewayConfig.gateway.customConnectors =
|
|
2607
|
+
customConnectors;
|
|
2608
|
+
}
|
|
2609
|
+
const resolved = (0, resolve_1.resolveEnabledConnectors)(this.agentConfig, customConnectors, this.gatewayConfig?.gateway?.connectorsDefaultEnabled ?? true);
|
|
2610
|
+
const target = resolved[connectorId];
|
|
2611
|
+
const { immediate, deferred } = await this.restartOrDefer({
|
|
2612
|
+
skipBusy: false,
|
|
2613
|
+
deferIdle: true,
|
|
2614
|
+
filter: (proc) => proc.connectorConfigChanged(connectorId, target),
|
|
2615
|
+
});
|
|
2616
|
+
return { restarted: immediate + deferred > 0 };
|
|
2617
|
+
}
|
|
2519
2618
|
/**
|
|
2520
2619
|
* Format a timestamp as a human-readable age string (e.g. "5m ago", "2h ago").
|
|
2521
2620
|
*/
|
|
@@ -3950,7 +4049,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3950
4049
|
// are validated by the upstream provider, not the local config list.
|
|
3951
4050
|
this.agentConfig.claude.model = newModel;
|
|
3952
4051
|
try {
|
|
3953
|
-
this.persistModelToConfig(newModel);
|
|
4052
|
+
await this.persistModelToConfig(newModel);
|
|
3954
4053
|
}
|
|
3955
4054
|
catch (err) {
|
|
3956
4055
|
this.logger.error('Failed to persist model to config', { error: err.message });
|