agent-relay-runner 0.71.0 → 0.73.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 +7 -1
- package/src/adapters/claude-delivery.ts +5 -1
- package/src/adapters/codex.ts +1 -1
- package/src/config.ts +4 -0
- package/src/index.ts +23 -2
- package/src/relay-injection-events.ts +97 -0
- package/src/reply-obligation-cache.ts +1 -0
- package/src/response-capture-report.ts +6 -2
- package/src/runner-core.ts +10 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.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.48"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
package/src/adapter.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface ProviderSessionEvent {
|
|
|
41
41
|
body: string;
|
|
42
42
|
origin?: "chat" | "terminal" | "provider";
|
|
43
43
|
turnId?: string;
|
|
44
|
+
/** True only when this response is the completed provider turn, not an intermediate flush. */
|
|
45
|
+
final?: boolean;
|
|
44
46
|
label?: string;
|
|
45
47
|
status?: "running" | "completed" | "failed";
|
|
46
48
|
streaming?: boolean;
|
|
@@ -267,9 +269,13 @@ export function isNotificationMessage(message: Message): boolean {
|
|
|
267
269
|
return isPersistedRelayMessage(message) && message.replyExpected === false;
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
function isSpawnObligationMessage(message: Message): boolean {
|
|
273
|
+
return isRecord(message.payload) && message.payload.source === "spawn-obligation";
|
|
274
|
+
}
|
|
275
|
+
|
|
270
276
|
function latestReplyableMessage(messages: Message[]): Message | undefined {
|
|
271
277
|
return messages
|
|
272
|
-
.filter((message) => isPersistedRelayMessage(message) && !isMemoryInjection(message) && !isReactionNotification(message) && message.replyExpected !== false)
|
|
278
|
+
.filter((message) => isPersistedRelayMessage(message) && !isMemoryInjection(message) && !isReactionNotification(message) && !isSpawnObligationMessage(message) && message.replyExpected !== false)
|
|
273
279
|
.at(-1);
|
|
274
280
|
}
|
|
275
281
|
|
|
@@ -39,6 +39,10 @@ function isReactionNotification(message: Message): boolean {
|
|
|
39
39
|
return message.payload?.reactionNotification === true || event?.type === "message.reaction";
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function isSpawnObligationMessage(message: Message): boolean {
|
|
43
|
+
return isRecord(message.payload) && message.payload.source === "spawn-obligation";
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
function isPersistedRelayMessage(message: Message): boolean {
|
|
43
47
|
return Number.isSafeInteger(message.id) && message.id > 0;
|
|
44
48
|
}
|
|
@@ -60,7 +64,7 @@ function shouldShowReplyReminder(deliveryCount: number): boolean {
|
|
|
60
64
|
|
|
61
65
|
function latestReplyableMessage(messages: Message[]): Message | undefined {
|
|
62
66
|
return messages
|
|
63
|
-
.filter((message) => isPersistedRelayMessage(message) && !isMemoryInjection(message) && !isReactionNotification(message) && message.replyExpected !== false)
|
|
67
|
+
.filter((message) => isPersistedRelayMessage(message) && !isMemoryInjection(message) && !isReactionNotification(message) && !isSpawnObligationMessage(message) && message.replyExpected !== false)
|
|
64
68
|
.at(-1);
|
|
65
69
|
}
|
|
66
70
|
|
package/src/adapters/codex.ts
CHANGED
|
@@ -620,7 +620,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
620
620
|
const joined = this.captureMode === "full" ? messages.join("\n\n") : messages[messages.length - 1]!;
|
|
621
621
|
this.turnMessages = [];
|
|
622
622
|
const text = joined.trim();
|
|
623
|
-
if (text) this.sessionEventCb({ type: "response", origin: "provider", body: text, ...(this.activeTurnId ? { turnId: this.activeTurnId } : {}) });
|
|
623
|
+
if (text) this.sessionEventCb({ type: "response", origin: "provider", body: text, final: true, ...(this.activeTurnId ? { turnId: this.activeTurnId } : {}) });
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
private finishMainTurn(): void {
|
package/src/config.ts
CHANGED
|
@@ -111,6 +111,10 @@ export function workspaceJsonFromEnv(): string | undefined {
|
|
|
111
111
|
return process.env.AGENT_RELAY_WORKSPACE_JSON;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
export function relayInjectionEventsJsonFromEnv(): string | undefined {
|
|
115
|
+
return process.env.AGENT_RELAY_INJECTION_EVENTS_JSON;
|
|
116
|
+
}
|
|
117
|
+
|
|
114
118
|
export function tmuxSessionFromEnv(): string | undefined {
|
|
115
119
|
return process.env.AGENT_RELAY_TMUX_SESSION;
|
|
116
120
|
}
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
loadGlobalConfig,
|
|
13
13
|
loadProviderConfigFromRelay,
|
|
14
14
|
policyNameFromEnv,
|
|
15
|
+
relayInjectionEventsJsonFromEnv,
|
|
15
16
|
resolveCwd,
|
|
16
17
|
runnerId,
|
|
17
18
|
runtimeTokenExpiresAtFromEnv,
|
|
@@ -23,8 +24,9 @@ import {
|
|
|
23
24
|
} from "./config";
|
|
24
25
|
import { createProviderAdapter } from "./providers";
|
|
25
26
|
import { VERSION } from "./version";
|
|
26
|
-
import type { AgentProfile, SpawnProvider, WorkspaceMetadata } from "agent-relay-sdk";
|
|
27
|
-
import { errMessage, normalizeAgentLifecycle, RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
27
|
+
import type { AgentProfile, RelayInjectionCategory, SpawnProvider, WorkspaceMetadata } from "agent-relay-sdk";
|
|
28
|
+
import { errMessage, isRecord, normalizeAgentLifecycle, RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
29
|
+
import type { RunnerRelayInjectionEvent } from "./relay-injection-events";
|
|
28
30
|
|
|
29
31
|
interface CliOptions {
|
|
30
32
|
provider: SpawnProvider;
|
|
@@ -42,6 +44,7 @@ interface CliOptions {
|
|
|
42
44
|
agentId?: string;
|
|
43
45
|
prompt?: string;
|
|
44
46
|
systemPromptAppend?: string;
|
|
47
|
+
relayInjectionEvents?: RunnerRelayInjectionEvent[];
|
|
45
48
|
tags: string[];
|
|
46
49
|
caps: string[];
|
|
47
50
|
providerArgs: string[];
|
|
@@ -106,6 +109,7 @@ export async function main(argv = process.argv): Promise<void> {
|
|
|
106
109
|
workspace: parseWorkspaceEnv(workspaceJsonFromEnv()),
|
|
107
110
|
prompt: opts.prompt,
|
|
108
111
|
systemPromptAppend: opts.systemPromptAppend,
|
|
112
|
+
relayInjectionEvents: parseRelayInjectionEventsEnv(relayInjectionEventsJsonFromEnv()),
|
|
109
113
|
tmuxSession: tmuxSessionFromEnv(),
|
|
110
114
|
tags: opts.tags,
|
|
111
115
|
capabilities: opts.caps,
|
|
@@ -306,6 +310,23 @@ function parseWorkspaceEnv(raw: string | undefined): WorkspaceMetadata | undefin
|
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
|
|
313
|
+
function parseRelayInjectionEventsEnv(raw: string | undefined): RunnerRelayInjectionEvent[] | undefined {
|
|
314
|
+
if (!raw) return undefined;
|
|
315
|
+
try {
|
|
316
|
+
const parsed = JSON.parse(raw);
|
|
317
|
+
if (!Array.isArray(parsed)) return undefined;
|
|
318
|
+
const categories = new Set(["memory", "instructions", "tools", "comms", "knowledge", "context"]);
|
|
319
|
+
const events = parsed.flatMap((item): RunnerRelayInjectionEvent[] => {
|
|
320
|
+
if (!isRecord(item) || typeof item.summary !== "string" || !categories.has(String(item.category))) return [];
|
|
321
|
+
const detail = isRecord(item.detail) ? item.detail : {};
|
|
322
|
+
return [{ category: item.category as RelayInjectionCategory, summary: item.summary, detail }];
|
|
323
|
+
});
|
|
324
|
+
return events.length ? events : undefined;
|
|
325
|
+
} catch {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
309
330
|
function printHelp(provider: string): void {
|
|
310
331
|
console.log(`${provider}-relay [--headless] [--model ALIAS] [--effort LEVEL] [--rig NAME] [--profile NAME] [--cwd PATH] [--relay-url URL] [--approval MODE] [--label NAME] [--agent-id ID] [--prompt TEXT] [--system-prompt-append TEXT] [--tags a,b] [--caps x,y] [-- provider-args...]`);
|
|
311
332
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { MessageSessionMeta, RelayInjectionCategory, SpawnProvider } from "agent-relay-sdk";
|
|
2
|
+
import type { ProviderConfig, RunnerSpawnConfig } from "./adapter";
|
|
3
|
+
import { assembleLaunch } from "./launch-assembly";
|
|
4
|
+
|
|
5
|
+
export interface RunnerRelayInjectionEvent {
|
|
6
|
+
category: RelayInjectionCategory;
|
|
7
|
+
summary: string;
|
|
8
|
+
detail: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface RunnerRelayInjectionSessionEvent {
|
|
12
|
+
from: string;
|
|
13
|
+
to: string;
|
|
14
|
+
body: string;
|
|
15
|
+
session: MessageSessionMeta;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface KeyedRelayInjectionEvent {
|
|
19
|
+
key: string;
|
|
20
|
+
event: RunnerRelayInjectionEvent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RunnerLaunchInjectionInput {
|
|
24
|
+
carried?: RunnerRelayInjectionEvent[];
|
|
25
|
+
provider: string;
|
|
26
|
+
config: RunnerSpawnConfig;
|
|
27
|
+
providerConfig: ProviderConfig;
|
|
28
|
+
agentId: string;
|
|
29
|
+
providerSessionId: string;
|
|
30
|
+
timestamp?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function supportsLaunchAssembly(provider: string): provider is SpawnProvider {
|
|
34
|
+
return provider === "claude" || provider === "codex";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function launchInjectionEvents(input: RunnerLaunchInjectionInput): KeyedRelayInjectionEvent[] {
|
|
38
|
+
const events: KeyedRelayInjectionEvent[] = (input.carried ?? []).map((event, index) => ({ key: `spawn-${index}`, event }));
|
|
39
|
+
if (!supportsLaunchAssembly(input.provider)) return events;
|
|
40
|
+
const assembled = assembleLaunch(input.provider, input.config, input.providerConfig);
|
|
41
|
+
const toolCount = assembled.skills.length + assembled.plugins.length + assembled.mcp.servers.length
|
|
42
|
+
+ (assembled.relaySkills ? 1 : 0)
|
|
43
|
+
+ (assembled.relayPlugin ? 1 : 0)
|
|
44
|
+
+ (assembled.mcp.relayEndpoint ? 1 : 0)
|
|
45
|
+
+ (assembled.statusLine ? 1 : 0);
|
|
46
|
+
if (toolCount === 0) return events;
|
|
47
|
+
const parts = [
|
|
48
|
+
assembled.relaySkills ? "relay skills" : "",
|
|
49
|
+
assembled.relayPlugin ? "relay plugin" : "",
|
|
50
|
+
assembled.statusLine ? "status line" : "",
|
|
51
|
+
assembled.mcp.relayEndpoint ? "relay MCP" : "",
|
|
52
|
+
assembled.skills.length ? `${assembled.skills.length} skill${assembled.skills.length === 1 ? "" : "s"}` : "",
|
|
53
|
+
assembled.plugins.length ? `${assembled.plugins.length} plugin${assembled.plugins.length === 1 ? "" : "s"}` : "",
|
|
54
|
+
assembled.mcp.servers.length ? `${assembled.mcp.servers.length} MCP server${assembled.mcp.servers.length === 1 ? "" : "s"}` : "",
|
|
55
|
+
].filter(Boolean);
|
|
56
|
+
events.push({
|
|
57
|
+
key: "tools",
|
|
58
|
+
event: {
|
|
59
|
+
category: "tools",
|
|
60
|
+
summary: `provisioned ${parts.join(", ")}`,
|
|
61
|
+
detail: {
|
|
62
|
+
source: "launch-assembly",
|
|
63
|
+
provider: assembled.provider,
|
|
64
|
+
base: assembled.base,
|
|
65
|
+
vanilla: assembled.vanilla,
|
|
66
|
+
relayContext: assembled.relayContext,
|
|
67
|
+
relaySkills: assembled.relaySkills,
|
|
68
|
+
relayPlugin: assembled.relayPlugin,
|
|
69
|
+
statusLine: assembled.statusLine,
|
|
70
|
+
skills: assembled.skills,
|
|
71
|
+
plugins: assembled.plugins,
|
|
72
|
+
mcp: assembled.mcp,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return events;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function runnerLaunchInjectionSessionEvents(input: RunnerLaunchInjectionInput): RunnerRelayInjectionSessionEvent[] {
|
|
80
|
+
const timestamp = input.timestamp ?? Date.now();
|
|
81
|
+
return launchInjectionEvents(input).map(({ event, key }) => ({
|
|
82
|
+
from: input.agentId,
|
|
83
|
+
to: "user",
|
|
84
|
+
body: event.summary,
|
|
85
|
+
session: {
|
|
86
|
+
type: "relay-injection",
|
|
87
|
+
stepId: `relay-injection:${input.providerSessionId}:${key}`,
|
|
88
|
+
relayInjection: {
|
|
89
|
+
category: event.category,
|
|
90
|
+
summary: event.summary,
|
|
91
|
+
detail: event.detail,
|
|
92
|
+
agentId: input.agentId,
|
|
93
|
+
timestamp,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
@@ -24,6 +24,7 @@ import { logger } from "./logger";
|
|
|
24
24
|
// both the Claude Stop-hook nag (control-server) and the non-claude re-injection (runner)
|
|
25
25
|
// gate on it, so the rule must not drift between them.
|
|
26
26
|
export function obligationRequiresExplicitReply(obligation: ReplyObligation): boolean {
|
|
27
|
+
if (obligation.source === "spawn-obligation") return false;
|
|
27
28
|
return obligation.from !== "user";
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -12,6 +12,10 @@ type SessionPublisher = (input: {
|
|
|
12
12
|
replyTo?: number;
|
|
13
13
|
}) => void | Promise<void>;
|
|
14
14
|
|
|
15
|
+
function finalSession(session: MessageSessionMeta): MessageSessionMeta {
|
|
16
|
+
return { ...session, final: true };
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
export async function publishCapturedResponse(input: {
|
|
16
20
|
publishSessionEvent: SessionPublisher;
|
|
17
21
|
outbox: ReportOutbox;
|
|
@@ -27,7 +31,7 @@ export async function publishCapturedResponse(input: {
|
|
|
27
31
|
to: input.to,
|
|
28
32
|
body: input.body,
|
|
29
33
|
...(input.replyTo ? { replyTo: input.replyTo } : {}),
|
|
30
|
-
session: input.session,
|
|
34
|
+
session: finalSession(input.session),
|
|
31
35
|
});
|
|
32
36
|
enqueueCapturedResponseReport(input);
|
|
33
37
|
}
|
|
@@ -53,7 +57,7 @@ export function enqueueCapturedResponseReport(input: {
|
|
|
53
57
|
kind: "chat",
|
|
54
58
|
body: input.body,
|
|
55
59
|
replyExpected: false,
|
|
56
|
-
payload: { responseCapture: { provider: input.provider, ...input.session } },
|
|
60
|
+
payload: { responseCapture: { provider: input.provider, ...finalSession(input.session) } },
|
|
57
61
|
},
|
|
58
62
|
});
|
|
59
63
|
}
|
package/src/runner-core.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { deliverBufferedMcpCall as deliverBufferedMcpOutboxCall, deliverRunnerOu
|
|
|
23
23
|
import { RunnerInsights } from "./runner-insights";
|
|
24
24
|
import { BusyReconciler } from "./busy-reconciler";
|
|
25
25
|
import { publishCapturedResponse } from "./response-capture-report";
|
|
26
|
+
import { runnerLaunchInjectionSessionEvents, type RunnerRelayInjectionEvent } from "./relay-injection-events";
|
|
26
27
|
import { capsFromEnv, contextStateDirFromEnv, logLevelFromEnv, mcpProxyEnabledFromEnv, orchestratorBaseDirFromEnv, orchestratorUrlFromEnv, registrationTimeoutMsFromEnv, runnerInfoFileFromEnv, runnerLogFileFromEnv, runnerOutboxDirWithInfoFallback, sessionDebugEnabled, tagsFromEnv, workspaceModeFromEnv } from "./config";
|
|
27
28
|
import {
|
|
28
29
|
appliedAgentProfileMetadata,
|
|
@@ -115,6 +116,7 @@ interface RunnerOptions {
|
|
|
115
116
|
agentProfile?: AgentProfile;
|
|
116
117
|
prompt?: string;
|
|
117
118
|
systemPromptAppend?: string;
|
|
119
|
+
relayInjectionEvents?: RunnerRelayInjectionEvent[];
|
|
118
120
|
tags: string[];
|
|
119
121
|
capabilities: string[];
|
|
120
122
|
providerArgs: string[];
|
|
@@ -601,7 +603,12 @@ export class AgentRunner {
|
|
|
601
603
|
},
|
|
602
604
|
};
|
|
603
605
|
const launchSeededPrompt = this.options.adapter.seedsInitialPromptAtLaunch ? this.options.prompt?.trim() : ""; if (launchSeededPrompt) this.recordInjectedPrompt(launchSeededPrompt);
|
|
604
|
-
|
|
606
|
+
const managedProcess = await this.options.adapter.spawn(config);
|
|
607
|
+
const injectionEvents = runnerLaunchInjectionSessionEvents({ carried: this.options.relayInjectionEvents, provider: this.options.provider, config, providerConfig: this.options.providerConfig, agentId: this.agentId, providerSessionId: this.providerSessionId });
|
|
608
|
+
for (const event of injectionEvents) {
|
|
609
|
+
this.publishSessionEvent(event);
|
|
610
|
+
}
|
|
611
|
+
return managedProcess;
|
|
605
612
|
}
|
|
606
613
|
|
|
607
614
|
private async terminalAttachSpec(): Promise<TerminalAttachSpec> {
|
|
@@ -1391,7 +1398,7 @@ export class AgentRunner {
|
|
|
1391
1398
|
}
|
|
1392
1399
|
|
|
1393
1400
|
this.sessionLog(`response captured for turn ${turnId ?? "?"} (${body.length} chars${replyToMessageId ? `, replyTo #${replyToMessageId}` : ", no replyTo"})`);
|
|
1394
|
-
await publishCapturedResponse({ publishSessionEvent: (event) => this.publishSessionEvent(event), outbox: this.sessionOutbox, provider: this.options.provider, from: this.agentId, to: replyTarget, body, replyTo: replyToMessageId, session: { type: "response", origin: "provider", ...(turnId ? { turnId } : {}) } });
|
|
1401
|
+
await publishCapturedResponse({ publishSessionEvent: (event) => this.publishSessionEvent(event), outbox: this.sessionOutbox, provider: this.options.provider, from: this.agentId, to: replyTarget, body, replyTo: replyToMessageId, session: { type: "response", origin: "provider", final: true, ...(turnId ? { turnId } : {}) } });
|
|
1395
1402
|
// The agent's reply may have cleared an obligation — refresh the snapshot so the next
|
|
1396
1403
|
// turn-end doesn't re-prompt for a message already answered (#196).
|
|
1397
1404
|
if (replyToMessageId) this.obligationCache.markDirty();
|
|
@@ -1625,6 +1632,7 @@ export class AgentRunner {
|
|
|
1625
1632
|
...(event.status ? { status: event.status } : {}),
|
|
1626
1633
|
...(event.streaming !== undefined ? { streaming: event.streaming } : {}),
|
|
1627
1634
|
...(event.stepId ? { stepId: event.stepId } : {}),
|
|
1635
|
+
...(event.final ? { final: true } : {}),
|
|
1628
1636
|
},
|
|
1629
1637
|
});
|
|
1630
1638
|
}
|