@runuai/host 0.9.108 → 0.9.110
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/lib/agents/grok.ts +11 -1
- package/lib/browser-testing.ts +27 -8
- package/lib/mcp-gateway.ts +19 -13
- package/lib/orchestrator.ts +21 -0
- package/package.json +1 -1
package/lib/agents/grok.ts
CHANGED
|
@@ -183,7 +183,17 @@ export class GrokSession implements AgentSession {
|
|
|
183
183
|
argv: argv as [string, ...string[]],
|
|
184
184
|
user: "node",
|
|
185
185
|
inheritEnv,
|
|
186
|
-
env:
|
|
186
|
+
env: {
|
|
187
|
+
...this.agentEnv,
|
|
188
|
+
// Grok gates MCP/LSP servers (even user-scope ones) behind folder
|
|
189
|
+
// trust, and /workspace is never trusted: it is not a git repo and
|
|
190
|
+
// headless runs have no prompt. `grok mcp doctor` on a task machine:
|
|
191
|
+
// "folder untrusted … not started for an untrusted folder"; with this
|
|
192
|
+
// off, "2 healthy". The container/machine IS the isolation boundary —
|
|
193
|
+
// the same reasoning as --permission-mode bypassPermissions above
|
|
194
|
+
// (live 2026-09-08, first Grok agents with MCP servers).
|
|
195
|
+
GROK_FOLDER_TRUST: "0",
|
|
196
|
+
},
|
|
187
197
|
maxOutputBytes: AGENT_SESSION_OUTPUT_BUFFER_BYTES,
|
|
188
198
|
};
|
|
189
199
|
}
|
package/lib/browser-testing.ts
CHANGED
|
@@ -243,14 +243,24 @@ const SERVER_DEF = { command: SERVER_COMMAND, args: SERVER_ARGS };
|
|
|
243
243
|
* `--approve-mcps`); same JSON shape as Claude's project file. */
|
|
244
244
|
const CURSOR_MCP_PATH = "/home/node/.cursor/mcp.json";
|
|
245
245
|
|
|
246
|
-
/** Grok: `grok mcp add` (add-or-update) in
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
246
|
+
/** Grok: `grok mcp add` (add-or-update) in USER scope, once per GROK_HOME a
|
|
247
|
+
* task may run under (the default ~/.grok and each ~/.grok-acct-<id>) —
|
|
248
|
+
* `GROK_HOME` redirects the user config. PROJECT scope looked simpler but
|
|
249
|
+
* grok refuses repo-local servers in an untrusted folder, and /workspace is
|
|
250
|
+
* not a git repo so nothing ever trusts it (live 2026-09-08: "folder
|
|
251
|
+
* untrusted … re-run with --trust", no such flag headless). The launcher has
|
|
252
|
+
* no single quotes by construction (see SERVER_LAUNCHER). */
|
|
250
253
|
const GROK_BIN = "/home/node/.grok/bin/grok";
|
|
251
|
-
export const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
export const DEFAULT_GROK_HOME = "/home/node/.grok";
|
|
255
|
+
export function grokBrowserAddCommand(home: string): string {
|
|
256
|
+
if (!/^\/home\/node\/\.grok(-acct-[A-Za-z0-9_-]+)?$/.test(home)) {
|
|
257
|
+
throw new Error("invalid Grok home");
|
|
258
|
+
}
|
|
259
|
+
return (
|
|
260
|
+
`GROK_HOME=${home} ${GROK_BIN} mcp add browser -s user -t stdio ` +
|
|
261
|
+
`${SERVER_COMMAND} -- ${SERVER_ARGS.map((arg) => `'${arg}'`).join(" ")}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
254
264
|
|
|
255
265
|
const CLAUDE_SETTINGS_JSON = JSON.stringify(
|
|
256
266
|
{ enableAllProjectMcpServers: true },
|
|
@@ -1081,9 +1091,14 @@ export async function setupBrowserTesting(
|
|
|
1081
1091
|
* (2026-09-08 — until then only Claude/Codex were wired and both answered
|
|
1082
1092
|
* "no browser MCP in this session" on the Studio). */
|
|
1083
1093
|
engineKinds: readonly string[] = [],
|
|
1094
|
+
/** Every GROK_HOME the task may run under (default + account homes). */
|
|
1095
|
+
grokHomes: readonly string[] = [DEFAULT_GROK_HOME],
|
|
1084
1096
|
): Promise<BrowserSetup> {
|
|
1085
1097
|
const hasCursor = engineKinds.includes("cursor");
|
|
1086
1098
|
const hasGrok = engineKinds.includes("grok");
|
|
1099
|
+
const selectedGrokHomes = hasGrok
|
|
1100
|
+
? [...new Set(grokHomes.length > 0 ? grokHomes : [DEFAULT_GROK_HOME])]
|
|
1101
|
+
: [];
|
|
1087
1102
|
// Machine sessions run under MACHINE_RUNTIME_AUTHORITY_ENV, so every setup
|
|
1088
1103
|
// exec here must too — the prewarm in particular. Warming npx under the
|
|
1089
1104
|
// CONTAINER authority (private NPM_CONFIG_CACHE) leaves the session-default
|
|
@@ -1359,7 +1374,11 @@ export async function setupBrowserTesting(
|
|
|
1359
1374
|
// GROK_BROWSER_ADD_CMD) rather than a file we own; a failure here leaves
|
|
1360
1375
|
// the task configured-false so the next ensure retries.
|
|
1361
1376
|
if (hasGrok) {
|
|
1362
|
-
const grokArgv = [
|
|
1377
|
+
const grokArgv = [
|
|
1378
|
+
"sh",
|
|
1379
|
+
"-lc",
|
|
1380
|
+
selectedGrokHomes.map(grokBrowserAddCommand).join(" && "),
|
|
1381
|
+
] as [string, ...string[]];
|
|
1363
1382
|
const registered = environment
|
|
1364
1383
|
? await environment.exec({
|
|
1365
1384
|
argv: grokArgv,
|
package/lib/mcp-gateway.ts
CHANGED
|
@@ -1056,8 +1056,8 @@ const MCP_CONFIG_HOST_TIMEOUT_MS = 40_000;
|
|
|
1056
1056
|
* - Codex — mcp-remote shims appended once per slug to config.toml.
|
|
1057
1057
|
* - Cursor — ~/.cursor/mcp.json ({ url } form; the adapter passes
|
|
1058
1058
|
* `--approve-mcps`).
|
|
1059
|
-
* - Grok — `grok mcp add -s
|
|
1060
|
-
* (idempotent per slug;
|
|
1059
|
+
* - Grok — `grok mcp add -s user` into every GROK_HOME the task may use
|
|
1060
|
+
* (idempotent per slug; project scope is refused in an untrusted folder).
|
|
1061
1061
|
* - OpenCode — ~/.config/opencode/opencode.json `mcp` block (type:"remote",
|
|
1062
1062
|
* oauth:false so it treats the gateway URL as a plain server; the path
|
|
1063
1063
|
* token is the auth). Written only when the task has connections.
|
|
@@ -1071,6 +1071,8 @@ export async function setupMcpTaskConfig(
|
|
|
1071
1071
|
codexHomes: readonly string[] = ["/home/node/.codex"],
|
|
1072
1072
|
environment?: Pick<TaskEnvironmentHandle, "exec"> &
|
|
1073
1073
|
Partial<Pick<TaskEnvironmentHandle, "descriptor">>,
|
|
1074
|
+
/** Every GROK_HOME the task may run under (default + account homes). */
|
|
1075
|
+
grokHomes: readonly string[] = ["/home/node/.grok"],
|
|
1074
1076
|
): Promise<boolean> {
|
|
1075
1077
|
// No early return on empty: the claude adapter passes
|
|
1076
1078
|
// `--mcp-config /workspace/.mcp.json` unconditionally (ADR-057), so the
|
|
@@ -1153,18 +1155,22 @@ export async function setupMcpTaskConfig(
|
|
|
1153
1155
|
)}`,
|
|
1154
1156
|
]
|
|
1155
1157
|
: []),
|
|
1156
|
-
// Grok:
|
|
1157
|
-
//
|
|
1158
|
-
//
|
|
1159
|
-
//
|
|
1160
|
-
//
|
|
1158
|
+
// Grok: USER-scope `grok mcp add` once per GROK_HOME the task may run
|
|
1159
|
+
// under (GROK_HOME redirects the user config). Project scope is refused
|
|
1160
|
+
// for an untrusted folder and /workspace is no git repo (live
|
|
1161
|
+
// 2026-09-08). The binary lives at ~/.grok/bin (x.ai installer); the
|
|
1162
|
+
// old ~/.local/bin path failed silently behind `|| true`.
|
|
1161
1163
|
...(has("grok")
|
|
1162
|
-
?
|
|
1163
|
-
(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1164
|
+
? grokHomes
|
|
1165
|
+
.filter((home) => /^\/home\/node\/\.grok(-acct-[A-Za-z0-9_-]+)?$/.test(home))
|
|
1166
|
+
.flatMap((home) =>
|
|
1167
|
+
connections.map(
|
|
1168
|
+
(c) =>
|
|
1169
|
+
`GROK_HOME=${home} ${GROK_BIN} mcp add ${shellQuote(c.slug)} ${shellQuote(
|
|
1170
|
+
urlFor(c.slug),
|
|
1171
|
+
)} -t http -s user >/dev/null 2>&1 || true`,
|
|
1172
|
+
),
|
|
1173
|
+
)
|
|
1168
1174
|
: []),
|
|
1169
1175
|
// OpenCode: global config `mcp` block (auto-discovered regardless of the
|
|
1170
1176
|
// exec cwd, and independent of any per-account OPENCODE_DATA_DIR).
|
package/lib/orchestrator.ts
CHANGED
|
@@ -101,6 +101,7 @@ import {
|
|
|
101
101
|
} from "./agent-cli";
|
|
102
102
|
import {
|
|
103
103
|
DEFAULT_CODEX_HOME,
|
|
104
|
+
DEFAULT_GROK_HOME,
|
|
104
105
|
setupBrowserTesting,
|
|
105
106
|
type BrowserSetup,
|
|
106
107
|
} from "./browser-testing";
|
|
@@ -814,6 +815,16 @@ export class Orchestrator {
|
|
|
814
815
|
return [...new Set([DEFAULT_CODEX_HOME, ...homes])];
|
|
815
816
|
}
|
|
816
817
|
|
|
818
|
+
/** Every GROK_HOME a task in `scope` may run under: the default plus each
|
|
819
|
+
* sign-in account's isolated home (API-key accounts use the default). */
|
|
820
|
+
private grokHomes(hasGrok: boolean, scope: string): string[] {
|
|
821
|
+
if (!hasGrok) return [];
|
|
822
|
+
const homes = resolveEngineAccounts("grok", {}, scope)
|
|
823
|
+
.map((account) => account.configDir?.containerDir)
|
|
824
|
+
.filter((home): home is string => Boolean(home));
|
|
825
|
+
return [...new Set([DEFAULT_GROK_HOME, ...homes])];
|
|
826
|
+
}
|
|
827
|
+
|
|
817
828
|
private noteProvisionMutation(
|
|
818
829
|
channel: Channel,
|
|
819
830
|
waitForBrowserReadiness: boolean,
|
|
@@ -1026,6 +1037,7 @@ export class Orchestrator {
|
|
|
1026
1037
|
requestedHomes,
|
|
1027
1038
|
channel.agentHandle,
|
|
1028
1039
|
requestedKinds,
|
|
1040
|
+
this.grokHomes(requestedKinds.includes("grok"), channel.engineAccountScope),
|
|
1029
1041
|
)
|
|
1030
1042
|
: await setupBrowserTesting(
|
|
1031
1043
|
channel.taskId,
|
|
@@ -1034,6 +1046,7 @@ export class Orchestrator {
|
|
|
1034
1046
|
requestedHomes,
|
|
1035
1047
|
undefined,
|
|
1036
1048
|
requestedKinds,
|
|
1049
|
+
this.grokHomes(requestedKinds.includes("grok"), channel.engineAccountScope),
|
|
1037
1050
|
);
|
|
1038
1051
|
} catch (err) {
|
|
1039
1052
|
console.warn(
|
|
@@ -1190,10 +1203,15 @@ export class Orchestrator {
|
|
|
1190
1203
|
const codexHomes = engineKinds.includes("codex")
|
|
1191
1204
|
? this.codexHomes(true, channel.engineAccountScope)
|
|
1192
1205
|
: [];
|
|
1206
|
+
const grokHomes = this.grokHomes(
|
|
1207
|
+
engineKinds.includes("grok"),
|
|
1208
|
+
channel.engineAccountScope,
|
|
1209
|
+
);
|
|
1193
1210
|
const fingerprint = JSON.stringify({
|
|
1194
1211
|
connections,
|
|
1195
1212
|
engineKinds,
|
|
1196
1213
|
codexHomes,
|
|
1214
|
+
...(grokHomes.length > 0 ? { grokHomes } : {}),
|
|
1197
1215
|
});
|
|
1198
1216
|
const dirtyEpoch = channel.mcpConfigDirtyEpoch;
|
|
1199
1217
|
const repairingProvisionedConfig =
|
|
@@ -1240,6 +1258,7 @@ export class Orchestrator {
|
|
|
1240
1258
|
engineKinds,
|
|
1241
1259
|
codexHomes,
|
|
1242
1260
|
providerEnvironment,
|
|
1261
|
+
grokHomes,
|
|
1243
1262
|
)
|
|
1244
1263
|
: await setupMcpTaskConfig(
|
|
1245
1264
|
channel.taskId,
|
|
@@ -1247,6 +1266,8 @@ export class Orchestrator {
|
|
|
1247
1266
|
connections,
|
|
1248
1267
|
engineKinds,
|
|
1249
1268
|
codexHomes,
|
|
1269
|
+
undefined,
|
|
1270
|
+
grokHomes,
|
|
1250
1271
|
);
|
|
1251
1272
|
if (!configured || !this.isActiveChannel(channel)) return;
|
|
1252
1273
|
channel.configGeneration += 1;
|