agent-relay-runner 0.88.3 → 0.89.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.1",
|
|
4
4
|
"description": "Unified provider lifecycle runner for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "runner"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"agent-relay-sdk": "0.2.
|
|
23
|
+
"agent-relay-sdk": "0.2.68"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
package/src/adapters/codex.ts
CHANGED
|
@@ -1287,7 +1287,10 @@ function codexRelayContextEnabled(process: ManagedProcess): boolean {
|
|
|
1287
1287
|
return config ? profileAllowsRelayFeature(config, "context") : true;
|
|
1288
1288
|
}
|
|
1289
1289
|
|
|
1290
|
-
|
|
1290
|
+
// Exported for integration coverage (#462): Codex has no `--append-system-prompt`
|
|
1291
|
+
// flag — the composed systemPromptAppend rides in as the launch-context message
|
|
1292
|
+
// prepended to the first turn. This is the Codex twin of Claude's append arg.
|
|
1293
|
+
export function codexLaunchContext(process: ManagedProcess): string | undefined {
|
|
1291
1294
|
const config = process.meta?.config as RunnerSpawnConfig | undefined;
|
|
1292
1295
|
const text = config?.systemPromptAppend?.trim();
|
|
1293
1296
|
if (!text || process.meta?.systemPromptAppendSent) return undefined;
|
package/src/launch-assembly.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type ProviderConfig,
|
|
8
8
|
type RunnerSpawnConfig,
|
|
9
9
|
} from "./adapter";
|
|
10
|
-
import { profileUsesHostProviderGlobals, providerHomePathFor } from "./profile-home";
|
|
10
|
+
import { hostUserScopeMcpServers, profileUsesHostProviderGlobals, providerHomePathFor } from "./profile-home";
|
|
11
11
|
import {
|
|
12
12
|
materializeResolvedAssets,
|
|
13
13
|
resolveClaudeProvisionedPlugins,
|
|
@@ -66,10 +66,18 @@ export interface AssembledInstructions {
|
|
|
66
66
|
export interface AssembledMcp {
|
|
67
67
|
/** The identity-bearing relay HTTP MCP endpoint is injected. */
|
|
68
68
|
relayEndpoint: boolean;
|
|
69
|
-
/** Only the assembled servers may load (Claude --strict-mcp-config)
|
|
69
|
+
/** Only the assembled servers may load (Claude --strict-mcp-config). #662: true for ALL
|
|
70
|
+
* managed Claude launches so the project `.mcp.json` enable-modal can't wedge a headless
|
|
71
|
+
* agent; relay's endpoint + provisioned servers (via --mcp-config) are preserved. Always
|
|
72
|
+
* false for Codex (no equivalent discovery modal). */
|
|
70
73
|
strict: boolean;
|
|
71
74
|
/** Relay-provisioned server names (excludes the relay endpoint itself). */
|
|
72
75
|
servers: string[];
|
|
76
|
+
/** #662 — host USER-scope MCP server names (`~/.claude.json` `mcpServers`) re-injected
|
|
77
|
+
* into `--mcp-config` for an `mcp.mode:"host"` profile, so `--strict-mcp-config` keeps
|
|
78
|
+
* the ambient host MCP (callmux/qmd/gh) instead of dropping it. Empty for non-host mcp
|
|
79
|
+
* modes, isolated bases (own config home, never had host MCP), and Codex. */
|
|
80
|
+
hostServers: string[];
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
export interface AssembledLaunch {
|
|
@@ -194,6 +202,31 @@ function assembleClaude(config: RunnerSpawnConfig, providerConfig: ProviderConfi
|
|
|
194
202
|
const mcpServers = Object.keys(provisionedMcp).filter((name) => name !== RELAY_MCP_SERVER_NAME);
|
|
195
203
|
const relayEndpoint = profileAllowsRelayFeature(config, "mcp");
|
|
196
204
|
|
|
205
|
+
// #662 — EVERY managed Claude launch is --strict-mcp-config, not just vanilla. A managed
|
|
206
|
+
// agent runs headless (no human at the keyboard), so Claude's blocking "N new MCP servers
|
|
207
|
+
// found in this project — select to enable" modal (raised on UNDECIDED cwd `.mcp.json`
|
|
208
|
+
// servers) wedges the session forever, burning quota. Relay owns MCP: everything relay
|
|
209
|
+
// decides should load is injected via --mcp-config, which strict PRESERVES; strict only
|
|
210
|
+
// blocks what relay DIDN'T inject — above all the cwd project `.mcp.json` (the modal's
|
|
211
|
+
// source), which stays excluded. The relay plugin (monitor + turn hooks) loads via
|
|
212
|
+
// --plugin-dir and ships no MCP server, so it is untouched. Was `vanilla`-only, which left
|
|
213
|
+
// host/minimal/isolated bases exposed to the modal wedge.
|
|
214
|
+
const strictMcp = true;
|
|
215
|
+
|
|
216
|
+
// #662 follow-up — strict drops ALL on-disk config, including the host user-scope
|
|
217
|
+
// `~/.claude.json` MCP that a host-base agent loaded ambiently before strict. That
|
|
218
|
+
// over-reached: it stopped honoring `mcp.mode:"host"`, so default-relay workers/reviewers
|
|
219
|
+
// lost callmux/qmd/gh. Re-inject the host USER-scope servers (NOT the project `.mcp.json`,
|
|
220
|
+
// still excluded) when the launch both uses the host config home AND its mcp mode is host.
|
|
221
|
+
// An isolated/minimal base has its own config home and never had the real host's user MCP,
|
|
222
|
+
// so it stays relay+provisioned only even if a profile sets mode:"host".
|
|
223
|
+
const usesHostMcp = profileUsesHostProviderGlobals(config)
|
|
224
|
+
&& (!config.agentProfile || config.agentProfile.mcp.mode === "host");
|
|
225
|
+
const hostMcp = usesHostMcp ? hostUserScopeMcpServers() : {};
|
|
226
|
+
const hostMcpServers = Object.keys(hostMcp).filter(
|
|
227
|
+
(name) => name !== RELAY_MCP_SERVER_NAME && !(name in provisionedMcp),
|
|
228
|
+
);
|
|
229
|
+
|
|
197
230
|
const instructions = assembleInstructions(config);
|
|
198
231
|
|
|
199
232
|
// Launch order matches the historical claude buildSpawnArgs block exactly so existing
|
|
@@ -206,7 +239,8 @@ function assembleClaude(config: RunnerSpawnConfig, providerConfig: ProviderConfi
|
|
|
206
239
|
...(config.relayMcpEndpoint ? { endpoint: config.relayMcpEndpoint } : {}),
|
|
207
240
|
includeRelay: relayEndpoint,
|
|
208
241
|
servers: provisionedMcp,
|
|
209
|
-
|
|
242
|
+
hostServers: hostMcp,
|
|
243
|
+
strict: strictMcp,
|
|
210
244
|
}),
|
|
211
245
|
...(statusLine ? sessionStatusLineSettingsArgs(hostDefaultArgs, config.providerArgs) : []),
|
|
212
246
|
...(config.systemPromptAppend ? ["--append-system-prompt", config.systemPromptAppend] : []),
|
|
@@ -224,7 +258,7 @@ function assembleClaude(config: RunnerSpawnConfig, providerConfig: ProviderConfi
|
|
|
224
258
|
statusLine,
|
|
225
259
|
skills: skills.map((s) => ({ name: s.name, dir: s.dir })),
|
|
226
260
|
plugins: provisionedPlugins.map((p) => ({ name: p.name, dir: p.dir })),
|
|
227
|
-
mcp: { relayEndpoint, strict:
|
|
261
|
+
mcp: { relayEndpoint, strict: strictMcp, servers: mcpServers, hostServers: hostMcpServers },
|
|
228
262
|
instructions,
|
|
229
263
|
args,
|
|
230
264
|
env: configHome ? { CLAUDE_CONFIG_DIR: configHome } : {},
|
|
@@ -267,7 +301,7 @@ function assembleCodex(config: RunnerSpawnConfig, _providerConfig: ProviderConfi
|
|
|
267
301
|
statusLine: false,
|
|
268
302
|
skills: provisionedSkills.map((s) => ({ name: s.name, dir: s.dir })),
|
|
269
303
|
plugins: [],
|
|
270
|
-
mcp: { relayEndpoint, strict: false, servers: mcpServers },
|
|
304
|
+
mcp: { relayEndpoint, strict: false, servers: mcpServers, hostServers: [] },
|
|
271
305
|
instructions,
|
|
272
306
|
args,
|
|
273
307
|
env: configHome ? { CODEX_HOME: configHome } : {},
|
package/src/profile-home.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
5
|
+
import type { ProvisioningMcpServer } from "agent-relay-sdk";
|
|
5
6
|
import { profileAllowsRelayFeature, type RunnerSpawnConfig } from "./adapter";
|
|
6
7
|
import { CLAUDE_RELAY_MANUAL } from "./relay-instructions";
|
|
7
8
|
import { providerHomeRootFromEnv } from "./config";
|
|
@@ -117,6 +118,20 @@ function readHostClaudeConfig(): Record<string, unknown> | undefined {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
// #662 — the host USER-scope MCP servers: the top-level `mcpServers` of `~/.claude.json`.
|
|
122
|
+
// These are the ambient host MCP a host-base Claude auto-loads on EVERY launch when NOT
|
|
123
|
+
// strict (user scope is global across all projects — callmux/qmd/gh on this host). Since
|
|
124
|
+
// the #662 wedge fix makes every managed launch `--strict-mcp-config` (so Claude ignores
|
|
125
|
+
// all on-disk config and never renders the project-`.mcp.json` enable modal), an
|
|
126
|
+
// `mcp.mode:"host"` profile must re-inject these explicitly into `--mcp-config` to keep
|
|
127
|
+
// honoring host mode. Deliberately NOT the project-cwd `.mcp.json` (project scope — the
|
|
128
|
+
// modal's source, which must stay excluded) nor `projects[cwd].mcpServers` (local scope).
|
|
129
|
+
export function hostUserScopeMcpServers(): Record<string, ProvisioningMcpServer> {
|
|
130
|
+
const servers = readHostClaudeConfig()?.mcpServers;
|
|
131
|
+
if (!servers || typeof servers !== "object") return {};
|
|
132
|
+
return servers as Record<string, ProvisioningMcpServer>;
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
function providerHomePath(provider: "claude" | "codex", config: RunnerSpawnConfig): string {
|
|
121
136
|
const root = providerHomeRootFromEnv();
|
|
122
137
|
const profileName = sanitizePathPart(config.agentProfile?.name || config.profile || "profile");
|
package/src/relay-mcp.ts
CHANGED
|
@@ -43,10 +43,12 @@ export function tomlString(value: string): string {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// #555 — compose the launch MCP set from (the relay endpoint, if relay.mcp) PLUS the
|
|
46
|
-
// profile's relay-provisioned servers
|
|
47
|
-
// `
|
|
48
|
-
//
|
|
49
|
-
//
|
|
46
|
+
// profile's relay-provisioned servers, PLUS (#662) the host USER-scope servers for an
|
|
47
|
+
// `mcp.mode:"host"` profile. One `--mcp-config` JSON carries them all. `strict` adds
|
|
48
|
+
// `--strict-mcp-config` so ONLY these load — Claude reads no on-disk config, so the
|
|
49
|
+
// project-cwd `.mcp.json` is never discovered (its enable modal can't wedge a headless
|
|
50
|
+
// agent). Whatever the assembler decides host mode should keep is injected here explicitly;
|
|
51
|
+
// strict then preserves exactly that set and nothing else.
|
|
50
52
|
import type { ProvisioningMcpServer } from "agent-relay-sdk";
|
|
51
53
|
|
|
52
54
|
function claudeMcpServerEntry(server: ProvisioningMcpServer): Record<string, unknown> {
|
|
@@ -65,9 +67,21 @@ export function claudeMcpLaunchArgs(opts: {
|
|
|
65
67
|
endpoint?: string;
|
|
66
68
|
includeRelay: boolean;
|
|
67
69
|
servers?: Record<string, ProvisioningMcpServer>;
|
|
70
|
+
/** #662 — host USER-scope MCP servers (`~/.claude.json` `mcpServers`), re-injected for
|
|
71
|
+
* `mcp.mode:"host"` so `--strict-mcp-config` PRESERVES them instead of dropping the
|
|
72
|
+
* ambient host MCP a host-base agent loaded before strict. Lowest precedence: the relay
|
|
73
|
+
* endpoint and relay-provisioned servers win on a name collision (relay decides). */
|
|
74
|
+
hostServers?: Record<string, ProvisioningMcpServer>;
|
|
68
75
|
strict?: boolean;
|
|
69
76
|
}): string[] {
|
|
70
77
|
const mcpServers: Record<string, unknown> = {};
|
|
78
|
+
// Host user-scope servers go in first so relay endpoint + provisioned servers below can
|
|
79
|
+
// override them by name (relay-owned config is authoritative over ambient host MCP).
|
|
80
|
+
for (const [name, server] of Object.entries(opts.hostServers ?? {})) {
|
|
81
|
+
if (name === RELAY_MCP_SERVER_NAME) continue;
|
|
82
|
+
const entry = claudeMcpServerEntry(server);
|
|
83
|
+
if (Object.keys(entry).length) mcpServers[name] = entry;
|
|
84
|
+
}
|
|
71
85
|
if (opts.includeRelay) {
|
|
72
86
|
mcpServers[RELAY_MCP_SERVER_NAME] = {
|
|
73
87
|
type: "http",
|