@prohost/cli 0.2.2 → 0.2.3
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/CHANGELOG.md +115 -0
- package/README.md +147 -20
- package/dist/agent/claude.d.ts +21 -3
- package/dist/agent/claude.js +7 -1
- package/dist/agent/codex.d.ts +215 -11
- package/dist/agent/codex.js +555 -23
- package/dist/agent/extras.d.ts +73 -0
- package/dist/agent/extras.js +118 -0
- package/dist/agent/mcp.d.ts +7 -6
- package/dist/agent/mcp.js +7 -6
- package/dist/agent/prompt.d.ts +6 -5
- package/dist/agent/prompt.js +16 -0
- package/dist/agent/run.d.ts +33 -0
- package/dist/agent/run.js +216 -61
- package/dist/index.js +10 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/agent/run.js
CHANGED
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
* the command a second time and post a second reply.
|
|
19
19
|
*/
|
|
20
20
|
import os from 'node:os';
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
21
22
|
import WebSocket from 'ws';
|
|
22
23
|
import { completeRun, postReply } from './api.js';
|
|
23
24
|
import { claudeCommand, isClaudeExec, looksLikeResumeFailure, parseClaudeOutput, writeMcpConfig, } from './claude.js';
|
|
24
|
-
import { MCP_API_KEY_ENV_VAR, codexCommand, isCodexExec, looksLikeCodexResumeFailure, parseCodexOutput, } from './codex.js';
|
|
25
|
+
import { MCP_API_KEY_ENV_VAR, codexBinary, codexCommand, codexMcpInventoryFlag, codexProfileName, codexShellSyntax, disableEntriesThatLoad, extraMcpEntries, isCodexExec, listMcpServers, mcpServersOverride, prohostMcpEntry, looksLikeCodexResumeFailure, parseCodexOutput, } from './codex.js';
|
|
26
|
+
import { EXTRA_MCP_FILENAME, loadExtraMcpServers } from './extras.js';
|
|
25
27
|
import { MCP_SERVER_NAME, MCP_TOOL_PATTERN } from './mcp.js';
|
|
26
28
|
import { EVENT_AGENT_RUN_REQUESTED, EVENT_MENTION_CREATED, EVENT_MESSAGE_TEAM_CHAT, eventData, parseRunRequest, } from './contract.js';
|
|
27
29
|
import { ensureWorkspace, streamUrlFor, workspacePath } from './credentials.js';
|
|
@@ -116,13 +118,12 @@ function resolveWorkdir(options, log) {
|
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
119
|
-
* Assemble the per-process runtime: workspace, session ledger, and
|
|
120
|
-
*
|
|
121
|
+
* Assemble the per-process runtime: workspace, session ledger, and the
|
|
122
|
+
* brain-specific MCP preparation used immediately before each run.
|
|
121
123
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* dressed up as a safety check.
|
|
124
|
+
* Brain-specific MCP preparation happens per run. A daemon is long-lived, so
|
|
125
|
+
* an extras-file deletion or newly installed Codex server must take effect on
|
|
126
|
+
* the next invocation rather than after a restart or cache timeout.
|
|
126
127
|
*/
|
|
127
128
|
function buildRuntime(options, log) {
|
|
128
129
|
const workdir = resolveWorkdir(options, log);
|
|
@@ -134,53 +135,51 @@ function buildRuntime(options, log) {
|
|
|
134
135
|
: 'plain';
|
|
135
136
|
const skipPermissions = options.safeTools !== true;
|
|
136
137
|
if (brain === 'plain') {
|
|
137
|
-
return { workdir, sessions, brain, skipPermissions, mcpWired: false };
|
|
138
|
+
return { workdir, sessions, brain, skipPermissions, prepareMcp: () => ({ mcpWired: false }) };
|
|
138
139
|
}
|
|
139
140
|
const mcpUrl = options.credentials.mcp_url;
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
let announced = '';
|
|
142
|
+
const announce = (lines) => {
|
|
143
|
+
const message = lines.join('\n');
|
|
144
|
+
if (message === announced)
|
|
145
|
+
return;
|
|
146
|
+
for (const line of lines)
|
|
147
|
+
log(line);
|
|
148
|
+
announced = message;
|
|
149
|
+
};
|
|
143
150
|
if (!mcpUrl) {
|
|
144
151
|
log(`${timestamp()} ! this pairing carries no MCP endpoint; the agent will run without ProhostAI tools`);
|
|
145
152
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
const prepareMcp = brain === 'claude'
|
|
154
|
+
? () => {
|
|
155
|
+
const extras = loadExtraMcpServers({ env: options.env });
|
|
156
|
+
announce(extras.warnings.map((warning) => `${timestamp()} ! ${warning}`));
|
|
157
|
+
// Always pass an explicit strict config, including when it is empty.
|
|
158
|
+
// Omitting the path would make `--strict-mcp-config` ineffective and
|
|
159
|
+
// expose Claude's user/project MCP servers under skipped permissions.
|
|
160
|
+
try {
|
|
161
|
+
const mcpConfigPath = writeMcpConfig({
|
|
162
|
+
mcpUrl,
|
|
163
|
+
apiKey: options.credentials.api_key,
|
|
164
|
+
extraServers: extras.servers,
|
|
165
|
+
env: options.env,
|
|
166
|
+
});
|
|
167
|
+
return { mcpConfigPath, mcpWired: Boolean(mcpUrl) };
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
171
|
+
return {
|
|
172
|
+
mcpWired: false,
|
|
173
|
+
error: `could not write the strict Claude MCP config (${detail})`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
161
176
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Codex needs no file: the server is a `-c` override on each invocation and
|
|
165
|
-
// the key is read from the child's environment. Nothing to fail, so nothing
|
|
166
|
-
// to fall back from.
|
|
167
|
-
codexMcpUrl = mcpUrl;
|
|
168
|
-
execEnv = { [MCP_API_KEY_ENV_VAR]: options.credentials.api_key };
|
|
169
|
-
}
|
|
170
|
-
const mcpWired = Boolean(mcpConfigPath ?? codexMcpUrl);
|
|
171
|
-
const tools = mcpWired ? `, ProhostAI tools wired in as ${MCP_SERVER_NAME}` : '';
|
|
177
|
+
: codexMcpEntryProvider(options, log, workdir);
|
|
178
|
+
const tools = mcpUrl ? `, ProhostAI tools prepared per run as ${MCP_SERVER_NAME}` : '';
|
|
172
179
|
const where = workdir ? ` (workspace: ${workdir})` : '';
|
|
173
180
|
log(brain === 'claude'
|
|
174
181
|
? `${timestamp()} ⚙ Claude Code detected — resuming a session per conversation${tools}${where}`
|
|
175
182
|
: `${timestamp()} ⚙ Codex detected — resuming a thread per conversation${tools}${where}`);
|
|
176
|
-
if (brain === 'codex' && mcpWired) {
|
|
177
|
-
// The one asymmetry worth an operator knowing about, because it is a
|
|
178
|
-
// security property they may be assuming they have. Claude Code is told to
|
|
179
|
-
// load our config and nothing else; Codex has no equivalent switch, so its
|
|
180
|
-
// own `~/.codex/config.toml` servers come along.
|
|
181
|
-
log(`${timestamp()} ! Codex has no --strict-mcp-config equivalent: this run also holds ` +
|
|
182
|
-
'whatever MCP servers your ~/.codex/config.toml defines.');
|
|
183
|
-
}
|
|
184
183
|
// Said out loud every start-up, not buried in --help. This is the line that
|
|
185
184
|
// tells an operator their machine will run tool calls, unprompted, on text
|
|
186
185
|
// written by someone else in their workspace.
|
|
@@ -191,7 +190,153 @@ function buildRuntime(options, log) {
|
|
|
191
190
|
'access, on instructions from your workspace. Use --safe-tools to gate them.'
|
|
192
191
|
: `${timestamp()} ⚙ --safe-tools: tools are permission-gated; anything needing ` +
|
|
193
192
|
'approval will not run (nobody is at a terminal to approve it).');
|
|
194
|
-
return { workdir, sessions, brain,
|
|
193
|
+
return { workdir, sessions, brain, prepareMcp, skipPermissions };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Prepare a fresh Codex MCP override immediately before each invocation.
|
|
197
|
+
*
|
|
198
|
+
* No cache is safe here: Codex rereads its config per invocation and the
|
|
199
|
+
* extras file is the operator's live grant list. Fresh reads make both a new
|
|
200
|
+
* configured server and a revoked extra visible to the very next run.
|
|
201
|
+
*/
|
|
202
|
+
export function codexMcpEntryProvider(options, log, workdir) {
|
|
203
|
+
let announced = '';
|
|
204
|
+
return () => {
|
|
205
|
+
const extras = loadExtraMcpServers({ env: options.env });
|
|
206
|
+
const lines = extras.warnings.map((warning) => `${timestamp()} ! ${warning}`);
|
|
207
|
+
const prepared = codexMcpEntries(options, extras.servers, (line) => lines.push(line), workdir);
|
|
208
|
+
const said = lines.join('\n');
|
|
209
|
+
if (said !== announced) {
|
|
210
|
+
for (const line of lines)
|
|
211
|
+
log(line);
|
|
212
|
+
announced = said;
|
|
213
|
+
}
|
|
214
|
+
return prepared;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/*
|
|
218
|
+
* Every `mcp_servers` entry a Codex run gets: ours, the operator's extras, and
|
|
219
|
+
* verified switches that keep the operator's own servers out. Preparation is
|
|
220
|
+
* fail-closed: an unprovable boundary never reaches an unsandboxed agent.
|
|
221
|
+
*/
|
|
222
|
+
function codexMcpEntries(options, extraServers, log, workdir) {
|
|
223
|
+
const fail = (detail) => {
|
|
224
|
+
log(`${timestamp()} ! ${detail}`);
|
|
225
|
+
return { mcpWired: false, error: detail };
|
|
226
|
+
};
|
|
227
|
+
const probe = options.probeImpl ?? defaultProbe;
|
|
228
|
+
if (codexShellSyntax(options.exec)) {
|
|
229
|
+
return fail('--exec uses shell quoting, expansion, glob, comment, escape, or control syntax that the Codex ' +
|
|
230
|
+
'isolation verifier cannot tokenize safely; use a plain argv-shaped Codex command');
|
|
231
|
+
}
|
|
232
|
+
const binary = codexBinary(options.exec);
|
|
233
|
+
const entries = [];
|
|
234
|
+
const execEnv = {};
|
|
235
|
+
if (options.credentials.mcp_url) {
|
|
236
|
+
entries.push(prohostMcpEntry(options.credentials.mcp_url));
|
|
237
|
+
execEnv[MCP_API_KEY_ENV_VAR] = options.credentials.api_key;
|
|
238
|
+
}
|
|
239
|
+
const profile = codexProfileName(options.exec);
|
|
240
|
+
if (profile) {
|
|
241
|
+
return fail(`--exec runs Codex under profile "${profile}", whose MCP servers cannot be enumerated; ` +
|
|
242
|
+
'remove the profile flag before running this harness');
|
|
243
|
+
}
|
|
244
|
+
const inventoryFlag = codexMcpInventoryFlag(options.exec);
|
|
245
|
+
if (inventoryFlag) {
|
|
246
|
+
return fail(`--exec includes ${inventoryFlag}, which can change Codex's MCP or plugin inventory, or its ` +
|
|
247
|
+
`working directory, outside the configuration the harness can verify; remove cwd, config, feature, ` +
|
|
248
|
+
'and --ignore-user-config flags');
|
|
249
|
+
}
|
|
250
|
+
const configured = listMcpServers(binary, probe, undefined, workdir);
|
|
251
|
+
if (configured === null) {
|
|
252
|
+
return fail('could not ask Codex which MCP servers it has configured');
|
|
253
|
+
}
|
|
254
|
+
const configuredNames = new Set(configured.map((server) => server.name));
|
|
255
|
+
if (configuredNames.has(MCP_SERVER_NAME)) {
|
|
256
|
+
return fail(`your Codex config already defines the reserved MCP server name "${MCP_SERVER_NAME}"; ` +
|
|
257
|
+
'rename or remove it so credentials cannot be merged into the paired server');
|
|
258
|
+
}
|
|
259
|
+
const collisions = Object.keys(extraServers).filter((name) => configuredNames.has(name));
|
|
260
|
+
if (collisions.length > 0) {
|
|
261
|
+
log(`${timestamp()} ! ${EXTRA_MCP_FILENAME} reuses names your Codex config already defines, ` +
|
|
262
|
+
`so those entries were ignored (Codex would merge their credentials): ${collisions.join(', ')}. Rename them to use them.`);
|
|
263
|
+
}
|
|
264
|
+
const usableExtras = Object.fromEntries(Object.entries(extraServers).filter(([name]) => !collisions.includes(name)));
|
|
265
|
+
const extra = extraMcpEntries(usableExtras);
|
|
266
|
+
if (extra.skipped.length > 0) {
|
|
267
|
+
log(`${timestamp()} ! could not pass these extra MCP servers to Codex ` +
|
|
268
|
+
`(value not expressible as a safe config override): ${extra.skipped.join(', ')}`);
|
|
269
|
+
}
|
|
270
|
+
let acceptedExtraEntries = [];
|
|
271
|
+
if (extra.entries.length > 0) {
|
|
272
|
+
if (listMcpServers(binary, probe, mcpServersOverride(extra.entries), workdir) === null) {
|
|
273
|
+
log(`${timestamp()} ! Codex rejected the server definitions in ${EXTRA_MCP_FILENAME}; ` +
|
|
274
|
+
'the run will continue without those optional extras');
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
acceptedExtraEntries = extra.entries;
|
|
278
|
+
entries.push(...acceptedExtraEntries);
|
|
279
|
+
Object.assign(execEnv, extra.env);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// A configured collision was dropped above, so it is deliberately disabled.
|
|
283
|
+
const acceptedExtraNames = new Set(Object.keys(usableExtras).filter((name) => !extra.skipped.includes(name)));
|
|
284
|
+
if (acceptedExtraEntries.length === 0)
|
|
285
|
+
acceptedExtraNames.clear();
|
|
286
|
+
const targets = [...configuredNames].filter((name) => !acceptedExtraNames.has(name));
|
|
287
|
+
const { entries: disabled, failed } = disableEntriesThatLoad(binary, probe, targets, workdir);
|
|
288
|
+
if (failed.length > 0) {
|
|
289
|
+
return fail(`these Codex MCP servers could not be disabled: ${failed.join(', ')}`);
|
|
290
|
+
}
|
|
291
|
+
entries.push(...disabled);
|
|
292
|
+
if (disabled.length > 0) {
|
|
293
|
+
log(`${timestamp()} ⚙ your own Codex MCP servers are disabled for agent runs: ${targets.join(', ')}`);
|
|
294
|
+
}
|
|
295
|
+
// Verify the exact combined override, not its pieces. This catches a config
|
|
296
|
+
// merge that re-enables a server and a server installed between the initial
|
|
297
|
+
// survey and this final probe.
|
|
298
|
+
const override = mcpServersOverride(entries);
|
|
299
|
+
const finalListing = listMcpServers(binary, probe, override, workdir);
|
|
300
|
+
if (finalListing === null)
|
|
301
|
+
return fail('Codex rejected the final isolated MCP configuration');
|
|
302
|
+
const allowed = new Set([
|
|
303
|
+
...(options.credentials.mcp_url ? [MCP_SERVER_NAME] : []),
|
|
304
|
+
...acceptedExtraNames,
|
|
305
|
+
]);
|
|
306
|
+
const enabled = new Set(finalListing.filter((server) => server.enabled).map((server) => server.name));
|
|
307
|
+
const missing = [...allowed].filter((name) => !enabled.has(name));
|
|
308
|
+
if (missing.length > 0) {
|
|
309
|
+
return fail(`expected MCP servers are missing or disabled after isolation: ${missing.join(', ')}`);
|
|
310
|
+
}
|
|
311
|
+
const unexpected = finalListing.filter((server) => server.enabled && !allowed.has(server.name));
|
|
312
|
+
if (unexpected.length > 0) {
|
|
313
|
+
return fail(`unexpected MCP servers remain enabled after isolation: ${unexpected.map((server) => server.name).join(', ')}`);
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
codexMcpEntries: entries,
|
|
317
|
+
execEnv: Object.keys(execEnv).length > 0 ? execEnv : undefined,
|
|
318
|
+
mcpWired: Boolean(options.credentials.mcp_url),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Run one short command and capture stdout.
|
|
323
|
+
*
|
|
324
|
+
* `shell: true` for the same reason `exec.ts` uses it — the binary name comes
|
|
325
|
+
* out of the operator's own `--exec` string and may be a quoted path — and for
|
|
326
|
+
* the same reason it is safe: nothing event-derived reaches this command line.
|
|
327
|
+
* `cwd` must match the eventual run exactly, because Codex loads trusted
|
|
328
|
+
* project `.codex/config.toml` layers from there.
|
|
329
|
+
*/
|
|
330
|
+
function defaultProbe(command, cwd) {
|
|
331
|
+
const result = spawnSync(command, {
|
|
332
|
+
shell: true,
|
|
333
|
+
cwd,
|
|
334
|
+
encoding: 'utf8',
|
|
335
|
+
// Generous for a config read, short enough that a wedged binary delays
|
|
336
|
+
// a run rather than wedging the daemon.
|
|
337
|
+
timeout: 15_000,
|
|
338
|
+
});
|
|
339
|
+
return { status: result.status, stdout: result.stdout ?? '' };
|
|
195
340
|
}
|
|
196
341
|
/**
|
|
197
342
|
* Run the agent command once and turn its output into a reply.
|
|
@@ -202,7 +347,7 @@ function buildRuntime(options, log) {
|
|
|
202
347
|
* tell a failed turn from a successful one. A plain command's behaviour here is
|
|
203
348
|
* byte-for-byte what it was before this function existed.
|
|
204
349
|
*/
|
|
205
|
-
async function runTurn(run, prompt, options, runtime, log) {
|
|
350
|
+
async function runTurn(run, prompt, options, runtime, mcp, log) {
|
|
206
351
|
// `--timeout` is a ceiling on the run, not on each attempt: the retry below
|
|
207
352
|
// must not let one run take twice what the operator allowed. Both attempts
|
|
208
353
|
// share one deadline, which costs the retry nothing in practice — a rejected
|
|
@@ -215,12 +360,15 @@ async function runTurn(run, prompt, options, runtime, log) {
|
|
|
215
360
|
prompt,
|
|
216
361
|
timeoutMs: remainingMs(),
|
|
217
362
|
cwd: runtime.workdir,
|
|
218
|
-
env:
|
|
363
|
+
env: mcp.execEnv,
|
|
219
364
|
spawnImpl: options.spawnImpl,
|
|
220
365
|
});
|
|
221
366
|
// Wall clock for the whole turn, retry included — what the operator waited,
|
|
222
367
|
// rather than what the last attempt took.
|
|
223
368
|
const elapsed = () => Date.now() - startedAt;
|
|
369
|
+
if (mcp.error) {
|
|
370
|
+
return { ok: false, reason: 'MCP isolation could not be established', detail: mcp.error, durationMs: 0 };
|
|
371
|
+
}
|
|
224
372
|
const failure = (result) => ({
|
|
225
373
|
ok: false,
|
|
226
374
|
reason: result.error ??
|
|
@@ -249,12 +397,12 @@ async function runTurn(run, prompt, options, runtime, log) {
|
|
|
249
397
|
const build = (resume) => codex
|
|
250
398
|
? codexCommand(options.exec, {
|
|
251
399
|
sessionId: resume,
|
|
252
|
-
|
|
400
|
+
mcpEntries: mcp.codexMcpEntries,
|
|
253
401
|
skipPermissions: runtime.skipPermissions,
|
|
254
402
|
})
|
|
255
403
|
: claudeCommand(options.exec, {
|
|
256
404
|
sessionId: resume,
|
|
257
|
-
mcpConfigPath:
|
|
405
|
+
mcpConfigPath: mcp.mcpConfigPath,
|
|
258
406
|
skipPermissions: runtime.skipPermissions,
|
|
259
407
|
});
|
|
260
408
|
let result = await exec(build(sessionId));
|
|
@@ -275,8 +423,18 @@ async function runTurn(run, prompt, options, runtime, log) {
|
|
|
275
423
|
}
|
|
276
424
|
if (!result.ok)
|
|
277
425
|
return failure(result);
|
|
426
|
+
// A denied tool is not an error either brain reports — the turn succeeds and
|
|
427
|
+
// the reply just quietly lacks whatever the tool would have found. Printing
|
|
428
|
+
// it is the difference between "the agent seems dumb" and "the agent asked
|
|
429
|
+
// for WebSearch and there was nobody to approve it".
|
|
430
|
+
const reportDenials = (denials) => {
|
|
431
|
+
if (denials.length > 0) {
|
|
432
|
+
log(` ! tools the agent asked for and was refused: ${denials.join(', ')}`);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
278
435
|
if (codex) {
|
|
279
436
|
const output = parseCodexOutput(result.stdout);
|
|
437
|
+
reportDenials(output.denials);
|
|
280
438
|
if (!output.ok) {
|
|
281
439
|
return {
|
|
282
440
|
ok: false,
|
|
@@ -288,13 +446,7 @@ async function runTurn(run, prompt, options, runtime, log) {
|
|
|
288
446
|
return { ok: true, reply: output.text, durationMs: elapsed(), sessionId: output.sessionId };
|
|
289
447
|
}
|
|
290
448
|
const parsed = parseClaudeOutput(result.stdout);
|
|
291
|
-
|
|
292
|
-
// reply just quietly lacks whatever the tool would have found. Printing it is
|
|
293
|
-
// the difference between "the agent seems dumb" and "the agent asked for
|
|
294
|
-
// WebSearch and there was nobody to approve it".
|
|
295
|
-
if (parsed.denials.length > 0) {
|
|
296
|
-
log(` ! tools the agent asked for and was refused: ${parsed.denials.join(', ')}`);
|
|
297
|
-
}
|
|
449
|
+
reportDenials(parsed.denials);
|
|
298
450
|
if (!parsed.ok) {
|
|
299
451
|
return {
|
|
300
452
|
ok: false,
|
|
@@ -315,18 +467,21 @@ async function handleRun(run, options, runtime, api, log, sleep) {
|
|
|
315
467
|
const label = `run ${run.run_id.slice(0, 12)}`;
|
|
316
468
|
const surface = run.conversation_id ?? run.kind ?? 'no conversation';
|
|
317
469
|
log(`${timestamp()} ▶ ${label} started (${surface})`);
|
|
470
|
+
const mcp = runtime.prepareMcp();
|
|
318
471
|
const prompt = buildPrompt(run, {
|
|
319
472
|
agentName: options.credentials.agent?.name ?? 'your agent',
|
|
320
473
|
// Named only when the tools are actually reachable from this invocation.
|
|
321
474
|
// Telling an agent about an endpoint it has no client for is what produced
|
|
322
475
|
// replies describing lookups that never happened.
|
|
323
|
-
mcpServerName:
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
476
|
+
mcpServerName: mcp.mcpWired ? MCP_SERVER_NAME : undefined,
|
|
477
|
+
// Both shaped brains spell MCP tools `mcp__<server>__<tool>` — Codex's was
|
|
478
|
+
// an open question until it was measured against the binary in 0.2.3. A
|
|
479
|
+
// `plain` command is still told the server name only: we have no idea what
|
|
480
|
+
// it is. See PromptContext.mcpToolPattern.
|
|
481
|
+
mcpToolPattern: runtime.brain !== 'plain' && mcp.mcpWired ? MCP_TOOL_PATTERN : undefined,
|
|
327
482
|
toolsGated: !runtime.skipPermissions,
|
|
328
483
|
});
|
|
329
|
-
const result = await runTurn(run, prompt, options, runtime, log);
|
|
484
|
+
const result = await runTurn(run, prompt, options, runtime, mcp, log);
|
|
330
485
|
const seconds = (result.durationMs / 1000).toFixed(1);
|
|
331
486
|
if (!result.ok) {
|
|
332
487
|
const { reason } = result;
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,16 @@ function printHelp() {
|
|
|
57
57
|
' act; with --safe-tools expect it to answer rather than do. Alias:',
|
|
58
58
|
' --no-skip-permissions.',
|
|
59
59
|
'',
|
|
60
|
+
'Extra MCP servers (opt-in):',
|
|
61
|
+
' A run gets the ProhostAI MCP server and nothing else. To add more, create',
|
|
62
|
+
' $PROHOST_HOME/extra-mcp.json yourself — the CLI never writes it — holding the same',
|
|
63
|
+
' {"mcpServers": {...}} object Claude Code\'s --mcp-config takes. Works on Claude Code',
|
|
64
|
+
' and Codex.',
|
|
65
|
+
' EVERY server you add is exercisable by anyone who can trigger this agent, with',
|
|
66
|
+
' permission prompts off and no human in the loop. Scope each one as narrowly as it',
|
|
67
|
+
' will go. The name "prohost" is reserved; a malformed file is a warning and a run',
|
|
68
|
+
' without extras. Which servers loaded is printed to agent.log at start-up.',
|
|
69
|
+
'',
|
|
60
70
|
'agent install-daemon flags: (macOS: launchd; Linux: prints a systemd-user unit)',
|
|
61
71
|
' --exec The same command you would pass to `agent run` (required).',
|
|
62
72
|
' --workdir, --timeout, --machine, --url, --allow-unverified, --safe-tools — passed to `agent run`.',
|
package/dist/version.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* `package.json`, so a release bump that forgets this file fails the suite
|
|
8
8
|
* instead of shipping a `User-Agent` that lies about which build is calling.
|
|
9
9
|
*/
|
|
10
|
-
export declare const CLI_VERSION = "0.2.
|
|
10
|
+
export declare const CLI_VERSION = "0.2.3";
|
|
11
11
|
/** Sent on every HTTP request the CLI makes back into ProhostAI. */
|
|
12
|
-
export declare const USER_AGENT = "prohost-cli/0.2.
|
|
12
|
+
export declare const USER_AGENT = "prohost-cli/0.2.3";
|
package/dist/version.js
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* `package.json`, so a release bump that forgets this file fails the suite
|
|
8
8
|
* instead of shipping a `User-Agent` that lies about which build is calling.
|
|
9
9
|
*/
|
|
10
|
-
export const CLI_VERSION = '0.2.
|
|
10
|
+
export const CLI_VERSION = '0.2.3';
|
|
11
11
|
/** Sent on every HTTP request the CLI makes back into ProhostAI. */
|
|
12
12
|
export const USER_AGENT = `prohost-cli/${CLI_VERSION}`;
|