agent-relay-runner 0.19.2 → 0.20.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 +2 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/adapter.ts +2 -0
- package/src/adapters/claude.ts +2 -0
- package/src/adapters/codex.ts +60 -6
- package/src/relay-mcp.ts +50 -0
- package/src/runner.ts +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
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.11"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
package/src/adapter.ts
CHANGED
package/src/adapters/claude.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { tmuxCommand, tmuxHasSession } from "agent-relay-sdk/tmux-utils";
|
|
|
7
7
|
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
8
8
|
import { profileAllowsRelayFeature, type ManagedProcess, type ProviderAdapter, type ProviderConfig, type ProviderStatusUpdate, type RunnerSpawnConfig, type SemanticStatus, type SpawnArgs } from "../adapter";
|
|
9
9
|
import { prepareClaudeProfileHome, profileUsesHostProviderGlobals } from "../profile-home";
|
|
10
|
+
import { relayMcpClaudeConfigArg } from "../relay-mcp";
|
|
10
11
|
import { claudeProviderMessageText } from "./claude-delivery";
|
|
11
12
|
|
|
12
13
|
export class ClaudeAdapter implements ProviderAdapter {
|
|
@@ -203,6 +204,7 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
203
204
|
const args = [
|
|
204
205
|
...rigPrefix,
|
|
205
206
|
...pluginDirs.flatMap((dir) => ["--plugin-dir", dir]),
|
|
207
|
+
...(profileAllowsRelayFeature(config, "mcp") ? relayMcpClaudeConfigArg(config.relayUrl) : []),
|
|
206
208
|
...(profileAllowsRelayFeature(config, "statusLine") ? sessionStatusLineSettingsArgs(defaultArgs, config.providerArgs) : []),
|
|
207
209
|
...(config.systemPromptAppend ? ["--append-system-prompt", config.systemPromptAppend] : []),
|
|
208
210
|
...providerArgs,
|
package/src/adapters/codex.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { isRecord, stringValue } from "agent-relay-sdk";
|
|
|
6
6
|
import { isPidAlive, killPid, waitForPidsExit } from "agent-relay-sdk/process-utils";
|
|
7
7
|
import { profileAllowsRelayFeature, providerMessageText, RELAY_CONTEXT, type ManagedProcess, type ProviderAdapter, type ProviderConfig, type ProviderPermissionDecisionInput, type ProviderSessionEvent, type ProviderStatusUpdate, type RunnerSpawnConfig, type SpawnArgs, type TerminalAttachSpec } from "../adapter";
|
|
8
8
|
import { workspaceDepsNoteFromEnv } from "../relay-instructions";
|
|
9
|
+
import { relayMcpCodexConfigArgs, tomlString } from "../relay-mcp";
|
|
9
10
|
import { logger } from "../logger";
|
|
10
11
|
|
|
11
12
|
/** Relay context prepended to a Codex agent's first turn: the standard relay
|
|
@@ -36,6 +37,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
36
37
|
// Assistant message text accumulated across the current turn's agentMessage items,
|
|
37
38
|
// flushed as one session response on turn/completed (mirrors Claude's chatCaptureMode).
|
|
38
39
|
private turnMessages: string[] = [];
|
|
40
|
+
private readonly itemTextBuffers = new Map<string, string>();
|
|
39
41
|
private captureMode: "final" | "full" = "final";
|
|
40
42
|
|
|
41
43
|
onStatusChange(cb: (status: ProviderStatusUpdate) => void): void {
|
|
@@ -257,6 +259,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
257
259
|
...codexModelConfigArgs(config.model, config.effort),
|
|
258
260
|
...codexApprovalConfigArgs(config.approvalMode),
|
|
259
261
|
...(profileAllowsRelayFeature(config, "skills") ? bundledSkillConfigArgs() : []),
|
|
262
|
+
...(profileAllowsRelayFeature(config, "mcp") ? relayMcpCodexConfigArgs(config.relayUrl) : []),
|
|
260
263
|
...codexManagedConfigArgs(),
|
|
261
264
|
"--listen",
|
|
262
265
|
appServerUrl,
|
|
@@ -331,6 +334,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
331
334
|
const turn = isRecord(params?.turn) ? params.turn : undefined;
|
|
332
335
|
this.activeTurnId = stringValue(turn?.id);
|
|
333
336
|
this.turnMessages = [];
|
|
337
|
+
this.itemTextBuffers.clear();
|
|
334
338
|
this.statusCb({ status: "busy", reason: "provider-turn", id: this.activeTurnId });
|
|
335
339
|
}
|
|
336
340
|
}
|
|
@@ -347,6 +351,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
347
351
|
if ((method.includes("item/completed") || method.includes("item.completed")) && !isSubagent) {
|
|
348
352
|
this.handleCodexItem(isRecord(params?.item) ? params.item : undefined);
|
|
349
353
|
}
|
|
354
|
+
if (!isSubagent) this.handleCodexItemDelta(method, params);
|
|
350
355
|
if (method.includes("thread/status")) {
|
|
351
356
|
const status = statusType(params?.status);
|
|
352
357
|
if (threadId && this.subagentThreads.has(threadId)) {
|
|
@@ -366,9 +371,11 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
366
371
|
if (!item) return;
|
|
367
372
|
const type = stringValue(item.type);
|
|
368
373
|
const turnId = this.activeTurnId;
|
|
374
|
+
const itemId = codexItemId(item);
|
|
369
375
|
if (type === "agentMessage") {
|
|
370
|
-
const text = stringValue(item.text)?.trim();
|
|
376
|
+
const text = (stringValue(item.text) ?? (itemId ? this.itemTextBuffers.get(itemId) : undefined))?.trim();
|
|
371
377
|
if (text) this.turnMessages.push(text);
|
|
378
|
+
if (itemId) this.itemTextBuffers.delete(itemId);
|
|
372
379
|
return;
|
|
373
380
|
}
|
|
374
381
|
if (type === "userMessage") {
|
|
@@ -377,12 +384,39 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
377
384
|
return;
|
|
378
385
|
}
|
|
379
386
|
if (type === "reasoning") {
|
|
380
|
-
const
|
|
387
|
+
const buffered = itemId ? this.itemTextBuffers.get(itemId) : undefined;
|
|
388
|
+
const text = (codexReasoningText(item) || buffered || "").trim();
|
|
381
389
|
if (text) this.sessionEventCb({ type: "reasoning", origin: "provider", body: text, ...(turnId ? { turnId } : {}) });
|
|
390
|
+
if (itemId) this.itemTextBuffers.delete(itemId);
|
|
382
391
|
return;
|
|
383
392
|
}
|
|
384
393
|
const tool = codexToolSummary(type, item);
|
|
385
|
-
if (tool) this.sessionEventCb({ type: "tool", origin: "provider", body: tool.body, label: tool.label, ...(turnId ? { turnId } : {}) });
|
|
394
|
+
if (tool) this.sessionEventCb({ type: "tool", origin: "provider", body: tool.body, label: tool.label, status: "completed", ...(turnId ? { turnId } : {}) });
|
|
395
|
+
if (itemId) this.itemTextBuffers.delete(itemId);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private handleCodexItemDelta(method: string, params: Record<string, unknown> | undefined): void {
|
|
399
|
+
if (!method.includes("item/") && !method.includes("item.")) return;
|
|
400
|
+
const item = isRecord(params?.item) ? params.item : undefined;
|
|
401
|
+
const itemId = codexItemId(params) ?? codexItemId(item);
|
|
402
|
+
const type = codexItemTypeFromMethod(method) ?? stringValue(item?.type);
|
|
403
|
+
const turnId = this.activeTurnId;
|
|
404
|
+
|
|
405
|
+
if (method.includes("/started") || method.includes(".started")) {
|
|
406
|
+
const tool = codexToolSummary(type, item ?? params ?? {});
|
|
407
|
+
if (tool) this.sessionEventCb({ type: "tool", origin: "provider", body: tool.body, label: tool.label, status: "running", streaming: true, ...(turnId ? { turnId } : {}) });
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (type === "agentMessage" || type === "reasoning" || type === "plan") {
|
|
412
|
+
const delta = codexDeltaText(params);
|
|
413
|
+
if (delta && itemId) this.itemTextBuffers.set(itemId, `${this.itemTextBuffers.get(itemId) ?? ""}${delta}`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Raw stdout/stderr deltas can be huge. Surface the tool lifecycle via started/completed
|
|
418
|
+
// summaries only; never persist raw process output into the chat mirror.
|
|
419
|
+
if (method.includes("outputDelta") || method.includes("output_delta") || method.includes("progress")) return;
|
|
386
420
|
}
|
|
387
421
|
|
|
388
422
|
private flushTurnResponse(): void {
|
|
@@ -401,6 +435,25 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
401
435
|
}
|
|
402
436
|
}
|
|
403
437
|
|
|
438
|
+
function codexItemId(item: Record<string, unknown> | undefined): string | undefined {
|
|
439
|
+
return stringValue(item?.itemId) ?? stringValue(item?.id);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function codexDeltaText(params: Record<string, unknown> | undefined): string {
|
|
443
|
+
const delta = params?.delta;
|
|
444
|
+
if (typeof delta === "string") return delta;
|
|
445
|
+
if (isRecord(delta)) {
|
|
446
|
+
const text = stringValue(delta.text) ?? stringValue(delta.value) ?? stringValue(delta.content);
|
|
447
|
+
if (text) return text;
|
|
448
|
+
}
|
|
449
|
+
return stringValue(params?.text) ?? stringValue(params?.chunk) ?? stringValue(params?.content) ?? "";
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function codexItemTypeFromMethod(method: string): string | undefined {
|
|
453
|
+
const match = method.match(/item[/.]([^/.]+)[/.]/);
|
|
454
|
+
return match?.[1];
|
|
455
|
+
}
|
|
456
|
+
|
|
404
457
|
function codexApprovalFromServerRequest(message: { id: string | number; method: string; params?: unknown }): { pending: PendingCodexApproval; view: Record<string, unknown> } | null {
|
|
405
458
|
if (!isRecord(message.params)) return null;
|
|
406
459
|
const method = message.method;
|
|
@@ -841,6 +894,10 @@ export function bundledSkillConfigArgs(skillDirs = bundledCodexSkillDirs()): str
|
|
|
841
894
|
return ["-c", `skills.config=[${skillsConfig}]`];
|
|
842
895
|
}
|
|
843
896
|
|
|
897
|
+
// tomlString now lives in ../relay-mcp (shared with the relay MCP injection args);
|
|
898
|
+
// re-exported here so existing codex.ts consumers/tests keep their import path.
|
|
899
|
+
export { tomlString };
|
|
900
|
+
|
|
844
901
|
export function codexAppServerConfigArgs(...argLists: string[][]): string[] {
|
|
845
902
|
const result: string[] = [];
|
|
846
903
|
for (const args of argLists) {
|
|
@@ -860,9 +917,6 @@ export function codexAppServerConfigArgs(...argLists: string[][]): string[] {
|
|
|
860
917
|
return result;
|
|
861
918
|
}
|
|
862
919
|
|
|
863
|
-
function tomlString(value: string): string {
|
|
864
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
865
|
-
}
|
|
866
920
|
|
|
867
921
|
async function connectWithRetry(client: CodexAppClient, attempts = 40): Promise<void> {
|
|
868
922
|
// Give the freshly-spawned app-server a moment to bind its socket before the
|
package/src/relay-mcp.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Single home for the relay HTTP MCP endpoint descriptor + per-provider injection
|
|
2
|
+
// args. Both adapters (and Stage 2's proxy) import from here so the endpoint path,
|
|
3
|
+
// server name, and token-handling rules live in exactly one place.
|
|
4
|
+
//
|
|
5
|
+
// Token handling: the bearer token is NEVER placed in argv (it would leak via `ps`
|
|
6
|
+
// and the agent's own process inspection). Claude expands `${AGENT_RELAY_TOKEN}`
|
|
7
|
+
// from the env at MCP-config parse time; Codex reads it from the named env var.
|
|
8
|
+
|
|
9
|
+
export const RELAY_MCP_SERVER_NAME = "agent-relay";
|
|
10
|
+
export const RELAY_MCP_PATH = "/api/mcp";
|
|
11
|
+
|
|
12
|
+
export function relayMcpEndpoint(relayUrl: string): string {
|
|
13
|
+
return `${relayUrl.replace(/\/+$/, "")}${RELAY_MCP_PATH}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Claude: additive `--mcp-config` JSON (NOT --strict-mcp-config, which would clobber
|
|
17
|
+
// the user's own servers). HTTP transport, token via env-var expansion so it never
|
|
18
|
+
// hits argv. Returns the full ["--mcp-config", "<json>"] arg pair.
|
|
19
|
+
export function relayMcpClaudeConfigArg(relayUrl: string): string[] {
|
|
20
|
+
return [
|
|
21
|
+
"--mcp-config",
|
|
22
|
+
JSON.stringify({
|
|
23
|
+
mcpServers: {
|
|
24
|
+
[RELAY_MCP_SERVER_NAME]: {
|
|
25
|
+
type: "http",
|
|
26
|
+
url: relayMcpEndpoint(relayUrl),
|
|
27
|
+
headers: { Authorization: "Bearer ${AGENT_RELAY_TOKEN}" },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Codex: `-c mcp_servers.<name>.*` overrides. `bearer_token_env_var` tells Codex to
|
|
35
|
+
// read the token from the env var itself → transport resolves to streamable_http.
|
|
36
|
+
export function relayMcpCodexConfigArgs(relayUrl: string): string[] {
|
|
37
|
+
const key = `mcp_servers.${RELAY_MCP_SERVER_NAME}`;
|
|
38
|
+
return [
|
|
39
|
+
"-c",
|
|
40
|
+
`${key}.url=${tomlString(relayMcpEndpoint(relayUrl))}`,
|
|
41
|
+
"-c",
|
|
42
|
+
`${key}.bearer_token_env_var=${tomlString("AGENT_RELAY_TOKEN")}`,
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Shared TOML string escaper for Codex `-c key=value` overrides. One home; codex.ts
|
|
47
|
+
// imports this rather than re-declaring it.
|
|
48
|
+
export function tomlString(value: string): string {
|
|
49
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
50
|
+
}
|
package/src/runner.ts
CHANGED
|
@@ -1158,8 +1158,9 @@ export class AgentRunner {
|
|
|
1158
1158
|
return;
|
|
1159
1159
|
}
|
|
1160
1160
|
if (event.type === "response") {
|
|
1161
|
-
//
|
|
1162
|
-
//
|
|
1161
|
+
// Dashboard prompt injection is already answered by this captured App Server
|
|
1162
|
+
// response. Other Relay inbox obligations still belong to the agent's explicit
|
|
1163
|
+
// reply path, so those keep suppressing auto-capture to avoid duplicates.
|
|
1163
1164
|
let replyToMessageId: number | undefined;
|
|
1164
1165
|
const pendingPrompt = this.pendingPromptMessageId;
|
|
1165
1166
|
if (pendingPrompt) {
|
|
@@ -1176,6 +1177,7 @@ export class AgentRunner {
|
|
|
1176
1177
|
...(replyToMessageId ? { replyTo: replyToMessageId } : {}),
|
|
1177
1178
|
session: { type: "response", origin: event.origin ?? "provider", ...(turnId ? { turnId } : {}) },
|
|
1178
1179
|
});
|
|
1180
|
+
if (replyToMessageId) this.obligationCache.markDirty();
|
|
1179
1181
|
return;
|
|
1180
1182
|
}
|
|
1181
1183
|
if (this.options.providerConfig.reasoningCapture === false) return;
|
|
@@ -1183,7 +1185,14 @@ export class AgentRunner {
|
|
|
1183
1185
|
from: this.agentId,
|
|
1184
1186
|
to: "user",
|
|
1185
1187
|
body,
|
|
1186
|
-
session: {
|
|
1188
|
+
session: {
|
|
1189
|
+
type: event.type,
|
|
1190
|
+
origin: event.origin ?? "provider",
|
|
1191
|
+
...(turnId ? { turnId } : {}),
|
|
1192
|
+
...(event.label ? { label: event.label } : {}),
|
|
1193
|
+
...(event.status ? { status: event.status } : {}),
|
|
1194
|
+
...(event.streaming !== undefined ? { streaming: event.streaming } : {}),
|
|
1195
|
+
},
|
|
1187
1196
|
});
|
|
1188
1197
|
}
|
|
1189
1198
|
|