agent-relay-runner 0.52.0 → 0.53.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "Unified provider lifecycle runner for Agent Relay",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
3
  "description": "Thin Agent Relay runner bridge for Claude Code",
4
- "version": "0.52.0",
4
+ "version": "0.53.0",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
@@ -392,6 +392,21 @@ export class CodexAdapter implements ProviderAdapter {
392
392
  updatedAt: Date.now(),
393
393
  },
394
394
  });
395
+ } else if (event.message.method === CODEX_MCP_ELICITATION_METHOD && isCodexHeadless(process)) {
396
+ // Codex routes per-MCP-tool-call approval through the MCP elicitation channel
397
+ // (request_id `mcp_tool_call_approval_*`): even with `approval_policy="never"`, a
398
+ // downstream server's elicitation — or codex's own per-call approval gate — is
399
+ // forwarded to the app-server client as `mcpServer/elicitation/request`. A headless
400
+ // managed agent has no human to answer it; the old -32601 reject makes codex resolve
401
+ // the elicitation as `Decline`, so the MCP tool call dies with "user rejected MCP tool
402
+ // call" — stranding read-only recon/reviewer workers with no egress to report back
403
+ // over the bus (#395). The agent is already bounded by its scoped relay token + the
404
+ // codex sandbox, so auto-accept (spec-compliant `{ action: "accept" }`, not codex's
405
+ // legacy `{ decision: "approved" }`) so the agent's reporting/recon MCP tools run.
406
+ const client = process.meta?.client as CodexAppClient | undefined;
407
+ const serverName = isRecord(event.message.params) ? stringValue(event.message.params.serverName) : undefined;
408
+ logger.info("codex", `auto-accepting headless MCP elicitation from ${serverName ?? "unknown server"} so the agent's MCP tools can run (#395)`);
409
+ client?.respondToServerRequest(event.message.id, codexHeadlessElicitationResponse());
395
410
  } else {
396
411
  const client = process.meta?.client as CodexAppClient | undefined;
397
412
  logger.warn("codex", `rejecting unknown Codex server-request method: ${event.message.method}`);
@@ -683,6 +698,25 @@ function codexItemTypeFromMethod(method: string): string | undefined {
683
698
  return match?.[1];
684
699
  }
685
700
 
701
+ // Codex forwards MCP elicitation (incl. its per-tool-call approval gate) to the app-server
702
+ // client under this server-request method (#395). See handleCodexEvent for why a headless
703
+ // agent must answer it instead of rejecting it.
704
+ export const CODEX_MCP_ELICITATION_METHOD = "mcpServer/elicitation/request";
705
+
706
+ // A managed agent with no attended TUI (the spawn intent, `config.headless`) has no human to
707
+ // resolve an elicitation. Such agents are the #395 case; interactive (TUI) sessions leave the
708
+ // elicitation to the human in the attached terminal.
709
+ function isCodexHeadless(process: ManagedProcess): boolean {
710
+ return (process.meta?.config as RunnerSpawnConfig | undefined)?.headless === true;
711
+ }
712
+
713
+ // Spec-compliant auto-accept for a headless agent's MCP elicitation. `action` (not codex's
714
+ // legacy `decision`) is the load-bearing field; `content: {}` approves codex's per-tool-call
715
+ // gate and any no-required-field form without fabricating runtime field values for the agent.
716
+ export function codexHeadlessElicitationResponse(): Record<string, unknown> {
717
+ return { action: "accept", content: {}, _meta: null };
718
+ }
719
+
686
720
  function codexApprovalFromServerRequest(message: { id: string | number; method: string; params?: unknown }): { pending: PendingCodexApproval; view: Record<string, unknown> } | null {
687
721
  if (!isRecord(message.params)) return null;
688
722
  const method = message.method;