agent-relay-runner 0.20.0 → 0.22.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 +1 -1
- package/src/adapters/codex.ts +18 -0
- package/src/outbox.ts +4 -0
- package/src/relay-instructions.ts +2 -1
- package/src/relay-mcp.ts +14 -4
- package/src/runner.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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.13"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
package/src/adapter.ts
CHANGED
|
@@ -159,7 +159,7 @@ export function profileAllowsRelayFeature(config: RunnerSpawnConfig, feature: ke
|
|
|
159
159
|
return config.agentProfile?.relay?.[feature] !== false;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
export const RELAY_CONTEXT = `[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_spawn_agent, and relay_shutdown_agent. CLI fallback: agent-relay /reply <messageId> --stdin < response.md; if a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide`;
|
|
162
|
+
export const RELAY_CONTEXT = `[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id — relay fills it from your token; use relay_whoami only if you need to reason about yourself. relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present you can stand up long-living child agents and shut down your own — find them later with relay_find_agents spawnedBy:me. CLI fallback: agent-relay /reply <messageId> --stdin < response.md; if a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide`;
|
|
163
163
|
|
|
164
164
|
const PROVIDER_MESSAGE_BODY_PREVIEW_CHARS = 4000;
|
|
165
165
|
|
package/src/adapters/codex.ts
CHANGED
|
@@ -17,6 +17,8 @@ function codexRelayContextBlock(): string {
|
|
|
17
17
|
import { prepareCodexProfileHome, profileUsesHostProviderGlobals } from "../profile-home";
|
|
18
18
|
import { CodexAppClient, type ClientEvent } from "./codex-client";
|
|
19
19
|
|
|
20
|
+
export const DEFAULT_CODEX_TOOL_OUTPUT_TOKEN_LIMIT = 12_000;
|
|
21
|
+
|
|
20
22
|
type PendingCodexApproval = {
|
|
21
23
|
id: string;
|
|
22
24
|
requestId: string | number;
|
|
@@ -260,6 +262,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
260
262
|
...codexApprovalConfigArgs(config.approvalMode),
|
|
261
263
|
...(profileAllowsRelayFeature(config, "skills") ? bundledSkillConfigArgs() : []),
|
|
262
264
|
...(profileAllowsRelayFeature(config, "mcp") ? relayMcpCodexConfigArgs(config.relayUrl) : []),
|
|
265
|
+
...codexToolOutputTokenLimitConfigArgs(config),
|
|
263
266
|
...codexManagedConfigArgs(),
|
|
264
267
|
"--listen",
|
|
265
268
|
appServerUrl,
|
|
@@ -917,6 +920,21 @@ export function codexAppServerConfigArgs(...argLists: string[][]): string[] {
|
|
|
917
920
|
return result;
|
|
918
921
|
}
|
|
919
922
|
|
|
923
|
+
export function codexToolOutputTokenLimitConfigArgs(config: Pick<RunnerSpawnConfig, "agentProfile">): string[] {
|
|
924
|
+
const configured = codexToolOutputTokenLimit(config.agentProfile?.providerOptions);
|
|
925
|
+
if (configured === null) return [];
|
|
926
|
+
const limit = configured ?? DEFAULT_CODEX_TOOL_OUTPUT_TOKEN_LIMIT;
|
|
927
|
+
return ["-c", `tool_output_token_limit=${limit}`];
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function codexToolOutputTokenLimit(providerOptions: unknown): number | null | undefined {
|
|
931
|
+
if (!isRecord(providerOptions)) return undefined;
|
|
932
|
+
const codex = providerOptions.codex;
|
|
933
|
+
if (!isRecord(codex)) return undefined;
|
|
934
|
+
const limit = codex.toolOutputTokenLimit;
|
|
935
|
+
return typeof limit === "number" || limit === null ? limit : undefined;
|
|
936
|
+
}
|
|
937
|
+
|
|
920
938
|
|
|
921
939
|
async function connectWithRetry(client: CodexAppClient, attempts = 40): Promise<void> {
|
|
922
940
|
// Give the freshly-spawned app-server a moment to bind its socket before the
|
package/src/outbox.ts
CHANGED
|
@@ -248,6 +248,10 @@ export class Outbox {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// Pure per-row delay calc: the attempt count lives in SQLite (one outbox row
|
|
252
|
+
// at a time may be due), so this stays a stateless function rather than the
|
|
253
|
+
// stateful SDK ReconnectionManager (single in-memory counter) used for the
|
|
254
|
+
// orchestrator/voice reconnect loops — different model, deliberately not shared.
|
|
251
255
|
private backoff(attempts: number): number {
|
|
252
256
|
const exp = Math.min(this.maxBackoffMs, this.baseBackoffMs * 2 ** (attempts - 1));
|
|
253
257
|
return Math.round(exp / 2 + Math.random() * (exp / 2)); // full-ish jitter, never below half
|
|
@@ -58,7 +58,8 @@ export function workspaceLifecycleNote(input: { mode?: string | null; branch?: s
|
|
|
58
58
|
const base = input.baseRef ? `\`${input.baseRef}\`` : "the base branch";
|
|
59
59
|
return [
|
|
60
60
|
`[agent-relay] Isolated workspace: you are in a git worktree on branch ${branch}, based on ${base} — NOT the main checkout. Other agents may work in parallel and land to ${base}, so ${base} will move under you. That is expected; don't fight it.`,
|
|
61
|
-
`Do NOT push this branch yourself — not with \`git push\`, not with \`tl push\` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run \`agent-relay workspace ready\` — Relay rebases onto the latest ${base}, lands your work, and pushes for you.
|
|
61
|
+
`Do NOT push this branch yourself — not with \`git push\`, not with \`tl push\` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run \`agent-relay workspace ready\` — Relay rebases onto the latest ${base}, lands your work, and pushes for you. If the installed \`agent-relay\` binary is stale and says the workspace command is unknown, run the repo-local fallback: \`bun src/index.ts workspace ready\`.`,
|
|
62
|
+
`After \`ready\`, the status becomes \`review_requested\` — this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Wait with \`agent-relay workspace status --wait\` (it returns the moment your branch lands). On landing you'll be moved onto a fresh rebased branch whose name gains a \`--N\` suffix — expected, keep working there. Never \`cd\` into the main checkout, and never merge/push/resolve conflicts yourself — Relay and the steward own all of that. \`agent-relay workspace status\` anytime shows your current state and the next step.`,
|
|
62
63
|
].join("\n");
|
|
63
64
|
}
|
|
64
65
|
|
package/src/relay-mcp.ts
CHANGED
|
@@ -3,11 +3,21 @@
|
|
|
3
3
|
// server name, and token-handling rules live in exactly one place.
|
|
4
4
|
//
|
|
5
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 `${
|
|
6
|
+
// and the agent's own process inspection). Claude expands `${AGENT_RELAY_SESSION_TOKEN}`
|
|
7
7
|
// from the env at MCP-config parse time; Codex reads it from the named env var.
|
|
8
|
-
|
|
8
|
+
//
|
|
9
|
+
// Why a dedicated var and NOT `AGENT_RELAY_TOKEN`: a managed session inherits the
|
|
10
|
+
// runner's scoped, identity-bearing session token, but Claude Code applies a rig/user
|
|
11
|
+
// `settings.json` `env` block OVER the inherited env. A rig that sets
|
|
12
|
+
// `env.AGENT_RELAY_TOKEN` (e.g. to the admin token, for interactive CLI use) would
|
|
13
|
+
// clobber the scoped token at parse time, so the relay MCP connection authenticates as
|
|
14
|
+
// the `server` actor with no agent identity — `relay_whoami` returns null and
|
|
15
|
+
// `relay_send_message` demands a `from` it can't supply (#233). The runner exports the
|
|
16
|
+
// scoped token under this dedicated name that rigs don't set, so nothing on the host can
|
|
17
|
+
// hijack the managed agent's identity.
|
|
9
18
|
export const RELAY_MCP_SERVER_NAME = "agent-relay";
|
|
10
19
|
export const RELAY_MCP_PATH = "/api/mcp";
|
|
20
|
+
export const RELAY_MCP_TOKEN_ENV = "AGENT_RELAY_SESSION_TOKEN";
|
|
11
21
|
|
|
12
22
|
export function relayMcpEndpoint(relayUrl: string): string {
|
|
13
23
|
return `${relayUrl.replace(/\/+$/, "")}${RELAY_MCP_PATH}`;
|
|
@@ -24,7 +34,7 @@ export function relayMcpClaudeConfigArg(relayUrl: string): string[] {
|
|
|
24
34
|
[RELAY_MCP_SERVER_NAME]: {
|
|
25
35
|
type: "http",
|
|
26
36
|
url: relayMcpEndpoint(relayUrl),
|
|
27
|
-
headers: { Authorization:
|
|
37
|
+
headers: { Authorization: `Bearer \${${RELAY_MCP_TOKEN_ENV}}` },
|
|
28
38
|
},
|
|
29
39
|
},
|
|
30
40
|
}),
|
|
@@ -39,7 +49,7 @@ export function relayMcpCodexConfigArgs(relayUrl: string): string[] {
|
|
|
39
49
|
"-c",
|
|
40
50
|
`${key}.url=${tomlString(relayMcpEndpoint(relayUrl))}`,
|
|
41
51
|
"-c",
|
|
42
|
-
`${key}.bearer_token_env_var=${tomlString(
|
|
52
|
+
`${key}.bearer_token_env_var=${tomlString(RELAY_MCP_TOKEN_ENV)}`,
|
|
43
53
|
];
|
|
44
54
|
}
|
|
45
55
|
|
package/src/runner.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Outbox, type OutboxRecord } from "./outbox";
|
|
|
14
14
|
import { extractLastAssistantTurn, extractFinalAssistantMessage, extractHookAssistantMessage, extractLatestTurnSteps, transcriptLooksComplete, analyzeSession } from "./adapters/claude-transcript";
|
|
15
15
|
import { agentProfileProjectionReport } from "./profile-projection";
|
|
16
16
|
import { profileUsesHostProviderGlobals } from "./profile-home";
|
|
17
|
+
import { RELAY_MCP_TOKEN_ENV } from "./relay-mcp";
|
|
17
18
|
import { runtimeMetadata } from "./version";
|
|
18
19
|
import { logger, parseLogLevel } from "./logger";
|
|
19
20
|
import { ensureSessionScratch, reapSessionScratch, sweepStaleSessions, type SessionScratchLayout } from "./session-scratch";
|
|
@@ -368,6 +369,11 @@ export class AgentRunner {
|
|
|
368
369
|
AGENT_RELAY_URL: this.options.relayUrl,
|
|
369
370
|
AGENT_RELAY_APPROVAL: this.options.approvalMode,
|
|
370
371
|
...(this.currentToken ? { AGENT_RELAY_TOKEN: this.currentToken } : {}),
|
|
372
|
+
// Dedicated, un-clobberable credential for the injected relay MCP endpoint. A rig's
|
|
373
|
+
// settings.json `env.AGENT_RELAY_TOKEN` would override the scoped token above at
|
|
374
|
+
// MCP-parse time → server-actor auth, no identity (#233). The MCP config references
|
|
375
|
+
// ${AGENT_RELAY_SESSION_TOKEN}, which rigs never set. See runner/src/relay-mcp.ts.
|
|
376
|
+
...(this.currentToken ? { [RELAY_MCP_TOKEN_ENV]: this.currentToken } : {}),
|
|
371
377
|
...(this.currentTokenJti ? { AGENT_RELAY_TOKEN_JTI: this.currentTokenJti } : {}),
|
|
372
378
|
...(this.currentTokenProfileId ? { AGENT_RELAY_TOKEN_PROFILE: this.currentTokenProfileId } : {}),
|
|
373
379
|
...(this.currentTokenExpiresAt ? { AGENT_RELAY_TOKEN_EXPIRES_AT: String(this.currentTokenExpiresAt) } : {}),
|