agent-relay-runner 0.73.0 → 0.74.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.
|
|
3
|
+
"version": "0.74.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.49"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
type EmitAgentMessage = (body: string) => void;
|
|
2
|
+
|
|
3
|
+
export class CodexAgentMessageCapture {
|
|
4
|
+
private segments: string[][] = [];
|
|
5
|
+
private currentSegment: string[] = [];
|
|
6
|
+
private readonly buffers = new Map<string, string>();
|
|
7
|
+
private readonly flushedLengths = new Map<string, number>();
|
|
8
|
+
|
|
9
|
+
reset(): void {
|
|
10
|
+
this.segments = [];
|
|
11
|
+
this.currentSegment = [];
|
|
12
|
+
this.buffers.clear();
|
|
13
|
+
this.flushedLengths.clear();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
appendDelta(itemId: string | undefined, delta: string): void {
|
|
17
|
+
if (!itemId || !delta) return;
|
|
18
|
+
this.buffers.set(itemId, `${this.buffers.get(itemId) ?? ""}${delta}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
completeItem(itemId: string | undefined, text: string | undefined, emit: EmitAgentMessage): void {
|
|
22
|
+
this.flushText(itemId, text ?? (itemId ? this.buffers.get(itemId) : undefined), emit);
|
|
23
|
+
if (!itemId) return;
|
|
24
|
+
this.buffers.delete(itemId);
|
|
25
|
+
this.flushedLengths.delete(itemId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
closeSegment(emit: EmitAgentMessage): void {
|
|
29
|
+
this.flushPending(emit);
|
|
30
|
+
if (!this.currentSegment.length) return;
|
|
31
|
+
this.segments.push(this.currentSegment);
|
|
32
|
+
this.currentSegment = [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
finish(emit: EmitAgentMessage): string | undefined {
|
|
36
|
+
this.closeSegment(emit);
|
|
37
|
+
const finalSegment = this.segments.at(-1);
|
|
38
|
+
this.segments = [];
|
|
39
|
+
return finalSegment?.length ? finalSegment.join("\n\n").trim() : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private flushPending(emit: EmitAgentMessage): void {
|
|
43
|
+
for (const [itemId, text] of [...this.buffers.entries()]) this.flushText(itemId, text, emit);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private flushText(itemId: string | undefined, text: string | undefined, emit: EmitAgentMessage): void {
|
|
47
|
+
if (!text) return;
|
|
48
|
+
const offset = itemId ? (this.flushedLengths.get(itemId) ?? 0) : 0;
|
|
49
|
+
const chunk = text.slice(offset);
|
|
50
|
+
if (itemId) this.flushedLengths.set(itemId, text.length);
|
|
51
|
+
const body = chunk.trim();
|
|
52
|
+
if (!body) return;
|
|
53
|
+
this.currentSegment.push(body);
|
|
54
|
+
emit(body);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/adapters/codex.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { tomlString } from "../relay-mcp";
|
|
|
10
10
|
import { assembleLaunch, bundledCodexSkillDirs, bundledSkillConfigArgs, materializeLaunchAssembly } from "../launch-assembly";
|
|
11
11
|
import { logger } from "../logger";
|
|
12
12
|
import type { SessionEvent } from "../session-insights";
|
|
13
|
+
import { CodexAgentMessageCapture } from "./codex-agent-message-capture";
|
|
13
14
|
|
|
14
15
|
/** Relay context prepended to a Codex agent's first turn: the standard relay
|
|
15
16
|
* blurb plus, when running in an isolated workspace, the deps caveat (#159). */
|
|
@@ -49,12 +50,9 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
49
50
|
// Active turn id for the main thread, captured from turn/started so an interrupt
|
|
50
51
|
// can target the in-flight turn. Cleared on turn/completed.
|
|
51
52
|
private activeTurnId?: string;
|
|
52
|
-
|
|
53
|
-
// flushed as one session response on turn/completed (mirrors Claude's chatCaptureMode).
|
|
54
|
-
private turnMessages: string[] = [];
|
|
53
|
+
private readonly agentMessageCapture = new CodexAgentMessageCapture();
|
|
55
54
|
private readonly itemTextBuffers = new Map<string, string>();
|
|
56
55
|
private readonly itemTextBufferTypes = new Map<string, string>();
|
|
57
|
-
private captureMode: "final" | "full" = "final";
|
|
58
56
|
// #183/#184: the normalized session-event log for the current process lifetime, fed
|
|
59
57
|
// from the same completed-item stream that drives the chat mirror. The runner slices
|
|
60
58
|
// this per-segment (since the last compact/clear/restart) via its own cursor, so we
|
|
@@ -81,7 +79,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
81
79
|
this.subagentThreads.clear();
|
|
82
80
|
this.pendingApprovals.clear();
|
|
83
81
|
this.activeTurnId = undefined;
|
|
84
|
-
this.
|
|
82
|
+
this.agentMessageCapture.reset();
|
|
85
83
|
this.itemTextBuffers.clear();
|
|
86
84
|
this.itemTextBufferTypes.clear();
|
|
87
85
|
}
|
|
@@ -133,7 +131,6 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
133
131
|
|
|
134
132
|
async spawn(config: RunnerSpawnConfig): Promise<ManagedProcess> {
|
|
135
133
|
this.resetProcessState();
|
|
136
|
-
this.captureMode = (config.providerConfig as ProviderConfig).chatCaptureMode ?? "final";
|
|
137
134
|
const args = this.buildSpawnArgs(config, config.providerConfig as ProviderConfig);
|
|
138
135
|
const appServer = Bun.spawn([args.command, ...args.args], {
|
|
139
136
|
cwd: args.cwd,
|
|
@@ -459,8 +456,9 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
459
456
|
} else {
|
|
460
457
|
const turn = isRecord(params?.turn) ? params.turn : undefined;
|
|
461
458
|
this.activeTurnId = stringValue(turn?.id);
|
|
462
|
-
this.
|
|
459
|
+
this.agentMessageCapture.reset();
|
|
463
460
|
this.itemTextBuffers.clear();
|
|
461
|
+
this.itemTextBufferTypes.clear();
|
|
464
462
|
this.statusCb({ status: "busy", reason: "provider-turn", id: this.activeTurnId });
|
|
465
463
|
}
|
|
466
464
|
}
|
|
@@ -492,29 +490,21 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
492
490
|
}
|
|
493
491
|
}
|
|
494
492
|
|
|
495
|
-
// Turn one completed Codex thread item into a session-mirror event. agentMessage
|
|
496
|
-
//
|
|
497
|
-
// (user prompt echo, reasoning, tool steps) is surfaced
|
|
493
|
+
// Turn one completed Codex thread item into a session-mirror event. agentMessage text is
|
|
494
|
+
// mirrored as narration as it lands; only the final contiguous assistant segment is repeated
|
|
495
|
+
// as the turn-final response. The rest (user prompt echo, reasoning, tool steps) is surfaced
|
|
496
|
+
// as it lands.
|
|
498
497
|
private handleCodexItem(item: Record<string, unknown> | undefined): void {
|
|
499
498
|
if (!item) return;
|
|
500
499
|
const type = stringValue(item.type);
|
|
501
500
|
const turnId = this.activeTurnId;
|
|
502
501
|
const itemId = codexItemId(item);
|
|
502
|
+
if (type !== "agentMessage") this.agentMessageCapture.closeSegment((body) => this.emitAgentNarration(body));
|
|
503
503
|
// A completed non-reasoning item ends the reasoning segment that preceded it — flush the
|
|
504
504
|
// buffered reasoning first so it lands in transcript order, ahead of this tool/message.
|
|
505
505
|
if (type !== "reasoning") this.flushBufferedReasoning();
|
|
506
506
|
if (type === "agentMessage") {
|
|
507
|
-
|
|
508
|
-
if (text) {
|
|
509
|
-
this.turnMessages.push(text);
|
|
510
|
-
this.recordInsightEvent({ type: "turn" }); // a substantive assistant turn
|
|
511
|
-
// Stream the assistant text into the trace as narration (Claude parity). The closing
|
|
512
|
-
// response bubble at turn end repeats the final block; the dashboard suppresses the
|
|
513
|
-
// matching narration so it isn't shown twice.
|
|
514
|
-
this.sessionEventCb({ type: "narration", origin: "provider", body: text, ...(turnId ? { turnId } : {}) });
|
|
515
|
-
}
|
|
516
|
-
if (itemId) this.itemTextBuffers.delete(itemId);
|
|
517
|
-
if (itemId) this.itemTextBufferTypes.delete(itemId);
|
|
507
|
+
this.agentMessageCapture.completeItem(itemId, stringValue(item.text), (body) => this.emitAgentNarration(body));
|
|
518
508
|
return;
|
|
519
509
|
}
|
|
520
510
|
if (type === "userMessage") {
|
|
@@ -549,6 +539,14 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
549
539
|
if (itemId) this.itemTextBufferTypes.delete(itemId);
|
|
550
540
|
}
|
|
551
541
|
|
|
542
|
+
private emitAgentNarration(body: string): void {
|
|
543
|
+
this.recordInsightEvent({ type: "turn" }); // a substantive assistant turn
|
|
544
|
+
// Stream the assistant text into the trace as narration (Claude parity). The closing
|
|
545
|
+
// response bubble at turn end repeats the final block; the dashboard suppresses the
|
|
546
|
+
// matching narration so it isn't shown twice.
|
|
547
|
+
this.sessionEventCb({ type: "narration", origin: "provider", body, ...(this.activeTurnId ? { turnId: this.activeTurnId } : {}) });
|
|
548
|
+
}
|
|
549
|
+
|
|
552
550
|
// Emit any buffered reasoning as discrete trace blocks (appended, in transcript order). Codex
|
|
553
551
|
// streams reasoning as deltas with no reliable completion event, so we flush coarsely at item
|
|
554
552
|
// and turn boundaries — the signal the Claude reasoning tail uses — never a codex-only timer.
|
|
@@ -588,6 +586,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
588
586
|
const turnId = this.activeTurnId;
|
|
589
587
|
|
|
590
588
|
if (method.includes("/started") || method.includes(".started")) {
|
|
589
|
+
if (type !== "agentMessage") this.agentMessageCapture.closeSegment((body) => this.emitAgentNarration(body));
|
|
591
590
|
// A new item starting ends the prior reasoning segment — flush it ahead of this step.
|
|
592
591
|
this.flushBufferedReasoning();
|
|
593
592
|
const tool = codexToolSummary(type, item ?? params ?? {});
|
|
@@ -596,7 +595,14 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
596
595
|
return;
|
|
597
596
|
}
|
|
598
597
|
|
|
599
|
-
if (type === "agentMessage"
|
|
598
|
+
if (type === "agentMessage") {
|
|
599
|
+
const delta = codexDeltaText(params);
|
|
600
|
+
if (delta) this.agentMessageCapture.appendDelta(itemId, delta);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (type === "reasoning" || type === "plan") {
|
|
605
|
+
this.agentMessageCapture.closeSegment((body) => this.emitAgentNarration(body));
|
|
600
606
|
const delta = codexDeltaText(params);
|
|
601
607
|
if (delta && itemId) {
|
|
602
608
|
this.itemTextBuffers.set(itemId, `${this.itemTextBuffers.get(itemId) ?? ""}${delta}`);
|
|
@@ -611,15 +617,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
611
617
|
}
|
|
612
618
|
|
|
613
619
|
private flushTurnResponse(): void {
|
|
614
|
-
const
|
|
615
|
-
.filter(([itemId]) => this.itemTextBufferTypes.get(itemId) === "agentMessage")
|
|
616
|
-
.map(([, text]) => text.trim())
|
|
617
|
-
.filter(Boolean);
|
|
618
|
-
const messages = [...this.turnMessages, ...pendingAgentMessages];
|
|
619
|
-
if (!messages.length) return;
|
|
620
|
-
const joined = this.captureMode === "full" ? messages.join("\n\n") : messages[messages.length - 1]!;
|
|
621
|
-
this.turnMessages = [];
|
|
622
|
-
const text = joined.trim();
|
|
620
|
+
const text = this.agentMessageCapture.finish((body) => this.emitAgentNarration(body));
|
|
623
621
|
if (text) this.sessionEventCb({ type: "response", origin: "provider", body: text, final: true, ...(this.activeTurnId ? { turnId: this.activeTurnId } : {}) });
|
|
624
622
|
}
|
|
625
623
|
|
|
@@ -631,6 +629,7 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
631
629
|
this.pendingApprovals.clear();
|
|
632
630
|
this.itemTextBuffers.clear();
|
|
633
631
|
this.itemTextBufferTypes.clear();
|
|
632
|
+
this.agentMessageCapture.reset();
|
|
634
633
|
this.statusCb({ status: "idle", reason: "provider-turn", id: turnId });
|
|
635
634
|
}
|
|
636
635
|
|