@runuai/host 0.8.17 → 0.8.18
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/lib/agents/transport.ts +23 -2
- package/lib/orchestrator.ts +5 -1
- package/package.json +1 -1
package/lib/agents/transport.ts
CHANGED
|
@@ -66,6 +66,25 @@ export interface AgentTransportOptions {
|
|
|
66
66
|
/** Runner heartbeat is 5 s; older than this = not attachable. */
|
|
67
67
|
const ATTACH_HEARTBEAT_FRESH_MS = 20_000;
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* ONE live outbox consumer per (task, agent) in this host process. A session
|
|
71
|
+
* dropped from the orchestrator's map without close()/detach() (e.g. the
|
|
72
|
+
* error-event path — a pipes-era "process is dead" assumption) leaves its
|
|
73
|
+
* DurableProcess polling FOREVER: its liveness check only trips when the
|
|
74
|
+
* RUNNER dies, and the runner is alive. The next channelEnsure then attaches
|
|
75
|
+
* a second tail to the same outbox, and every agent turn is emitted once per
|
|
76
|
+
* leaked tail — live incident 2026-07-20: one agent's messages posted ×5 to
|
|
77
|
+
* the feed, multiplicity growing by one per errored turn. Enforcing the
|
|
78
|
+
* invariant here covers every replacement path, including future ones.
|
|
79
|
+
*/
|
|
80
|
+
const liveTails = new Map<string, DurableProcess>();
|
|
81
|
+
|
|
82
|
+
function claimTail(key: string, proc: DurableProcess): DurableProcess {
|
|
83
|
+
liveTails.get(key)?.detach();
|
|
84
|
+
liveTails.set(key, proc);
|
|
85
|
+
return proc;
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
function durableEnabled(): boolean {
|
|
70
89
|
return process.env.UAI_DURABLE_SESSIONS !== "0";
|
|
71
90
|
}
|
|
@@ -117,6 +136,8 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
117
136
|
.run();
|
|
118
137
|
};
|
|
119
138
|
|
|
139
|
+
const tailKey = `${opts.taskId}:${opts.agentId}`;
|
|
140
|
+
|
|
120
141
|
// ---- Attach: a previous host process left this agent's runner alive. ----
|
|
121
142
|
if (opts.allowAttach && row && row.status === "running" && heartbeatFresh(row.sessionDir)) {
|
|
122
143
|
const proc = new DurableProcess({
|
|
@@ -127,7 +148,7 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
127
148
|
debugLabel: opts.debugLabel,
|
|
128
149
|
});
|
|
129
150
|
proc.onExit(markClosed);
|
|
130
|
-
return proc;
|
|
151
|
+
return claimTail(tailKey, proc);
|
|
131
152
|
}
|
|
132
153
|
|
|
133
154
|
// ---- Spawn a fresh runner, asking any predecessor to stop. --------------
|
|
@@ -231,7 +252,7 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
231
252
|
.run();
|
|
232
253
|
|
|
233
254
|
proc.onExit(markClosed);
|
|
234
|
-
return proc;
|
|
255
|
+
return claimTail(tailKey, proc);
|
|
235
256
|
}
|
|
236
257
|
|
|
237
258
|
function heartbeatFresh(sessionDir: string): boolean {
|
package/lib/orchestrator.ts
CHANGED
|
@@ -688,7 +688,11 @@ class Orchestrator {
|
|
|
688
688
|
// becomes a zombie: the map still holds the dead session and every
|
|
689
689
|
// later deliver "succeeds" into a closed pipe (found live 2026-07-08
|
|
690
690
|
// after a double SIGKILL). Bounded by the respawn budget, checked in
|
|
691
|
-
// reconcileSessions.
|
|
691
|
+
// reconcileSessions. NOTE (durable sessions): an errored TURN does
|
|
692
|
+
// not mean a dead RUNNER — the dropped session's outbox tail would
|
|
693
|
+
// poll forever and duplicate every later turn (×5 live 2026-07-20).
|
|
694
|
+
// createAgentTransport's one-tail-per-agent registry detaches the
|
|
695
|
+
// stale consumer when the respawn attaches.
|
|
692
696
|
channel.respawns.set(agentId, (channel.respawns.get(agentId) ?? 0) + 1);
|
|
693
697
|
channel.respawnLastAt.set(agentId, Date.now());
|
|
694
698
|
channel.sessions.delete(agentId);
|
package/package.json
CHANGED