agent-relay-server 0.119.10 → 0.120.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/docs/openapi.json +57 -1
- package/package.json +3 -3
- package/public/assets/{MessageBubble-U5sX3SNZ.js → MessageBubble-BJXjk3GB.js} +2 -2
- package/public/assets/{MessageBubble-U5sX3SNZ.js.map → MessageBubble-BJXjk3GB.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-Bi2gvXeE.js → PlanGraphPanel-DI5JOnm1.js} +2 -2
- package/public/assets/{PlanGraphPanel-Bi2gvXeE.js.map → PlanGraphPanel-DI5JOnm1.js.map} +1 -1
- package/public/assets/{activity-Cwkz51pG.js → activity-BUt4FiFQ.js} +2 -2
- package/public/assets/{activity-Cwkz51pG.js.map → activity-BUt4FiFQ.js.map} +1 -1
- package/public/assets/{agent-profiles-BgddVqRp.js → agent-profiles-C1dvEaGS.js} +2 -2
- package/public/assets/{agent-profiles-BgddVqRp.js.map → agent-profiles-C1dvEaGS.js.map} +1 -1
- package/public/assets/{agents-CiGYBw0h.js → agents-DvJZ0Tcz.js} +2 -2
- package/public/assets/{agents-CiGYBw0h.js.map → agents-DvJZ0Tcz.js.map} +1 -1
- package/public/assets/{analytics-CmSHiDAI.js → analytics-DMLoMwta.js} +2 -2
- package/public/assets/{analytics-CmSHiDAI.js.map → analytics-DMLoMwta.js.map} +1 -1
- package/public/assets/{automation-BBJB4RaE.js → automation-DICpRUGK.js} +2 -2
- package/public/assets/{automation-BBJB4RaE.js.map → automation-DICpRUGK.js.map} +1 -1
- package/public/assets/{branch-state-badge-Db6uU7yT.js → branch-state-badge-BYE2U_y4.js} +2 -2
- package/public/assets/{branch-state-badge-Db6uU7yT.js.map → branch-state-badge-BYE2U_y4.js.map} +1 -1
- package/public/assets/{channels-trEI29j0.js → channels-3SlDQL3j.js} +2 -2
- package/public/assets/{channels-trEI29j0.js.map → channels-3SlDQL3j.js.map} +1 -1
- package/public/assets/{chat-DS578gZy.js → chat-_I9nvxj2.js} +2 -2
- package/public/assets/{chat-DS578gZy.js.map → chat-_I9nvxj2.js.map} +1 -1
- package/public/assets/{connectors-DNV138gK.js → connectors-CrEW591Q.js} +2 -2
- package/public/assets/{connectors-DNV138gK.js.map → connectors-CrEW591Q.js.map} +1 -1
- package/public/assets/{formatted-body-DYUVqWym.js → formatted-body-ChERiS0y.js} +3 -3
- package/public/assets/{formatted-body-DYUVqWym.js.map → formatted-body-ChERiS0y.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DdcyxVO0.js → formatted-body-impl-CiaUIrP3.js} +2 -2
- package/public/assets/{formatted-body-impl-DdcyxVO0.js.map → formatted-body-impl-CiaUIrP3.js.map} +1 -1
- package/public/assets/{index-CqcAqQKW.js → index-CtBqO8vm.js} +7 -7
- package/public/assets/index-CtBqO8vm.js.map +1 -0
- package/public/assets/{insights-BunHbsTA.js → insights-DBjaf3N5.js} +2 -2
- package/public/assets/{insights-BunHbsTA.js.map → insights-DBjaf3N5.js.map} +1 -1
- package/public/assets/{integrations-COnkRfWW.js → integrations-B-bdliwU.js} +2 -2
- package/public/assets/{integrations-COnkRfWW.js.map → integrations-B-bdliwU.js.map} +1 -1
- package/public/assets/{maintenance-cICBVcpJ.js → maintenance-EWlKbynM.js} +2 -2
- package/public/assets/{maintenance-cICBVcpJ.js.map → maintenance-EWlKbynM.js.map} +1 -1
- package/public/assets/{managed-agents-BK9_Gi-M.js → managed-agents-DzjKwBh4.js} +2 -2
- package/public/assets/{managed-agents-BK9_Gi-M.js.map → managed-agents-DzjKwBh4.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-EZfcTHG9.js → markdown-preview-impl-bwLB6Gc-.js} +2 -2
- package/public/assets/{markdown-preview-impl-EZfcTHG9.js.map → markdown-preview-impl-bwLB6Gc-.js.map} +1 -1
- package/public/assets/{memory-Vi-8HvK3.js → memory-DEwJsFNK.js} +2 -2
- package/public/assets/{memory-Vi-8HvK3.js.map → memory-DEwJsFNK.js.map} +1 -1
- package/public/assets/{messages-DScIiBMI.js → messages-BSMkT9X2.js} +2 -2
- package/public/assets/{messages-DScIiBMI.js.map → messages-BSMkT9X2.js.map} +1 -1
- package/public/assets/{orchestrators-DLogldXx.js → orchestrators-Bl8k3KxQ.js} +2 -2
- package/public/assets/{orchestrators-DLogldXx.js.map → orchestrators-Bl8k3KxQ.js.map} +1 -1
- package/public/assets/{overview-B_RQAQR8.js → overview-CdFe7Dmc.js} +2 -2
- package/public/assets/{overview-B_RQAQR8.js.map → overview-CdFe7Dmc.js.map} +1 -1
- package/public/assets/{pairs-CSiPQ7E2.js → pairs-0ewxtvFZ.js} +2 -2
- package/public/assets/{pairs-CSiPQ7E2.js.map → pairs-0ewxtvFZ.js.map} +1 -1
- package/public/assets/{projects-Dd8gYzcn.js → projects-DEn85LQb.js} +2 -2
- package/public/assets/{projects-Dd8gYzcn.js.map → projects-DEn85LQb.js.map} +1 -1
- package/public/assets/{prompt-settings-DsDi_Xpd.js → prompt-settings-CMQ2tHe0.js} +2 -2
- package/public/assets/{prompt-settings-DsDi_Xpd.js.map → prompt-settings-CMQ2tHe0.js.map} +1 -1
- package/public/assets/{registry-DHFUWIdb.js → registry-DntwLc8a.js} +2 -2
- package/public/assets/{registry-DHFUWIdb.js.map → registry-DntwLc8a.js.map} +1 -1
- package/public/assets/{security-D2nHkJH6.js → security-CYwC-26O.js} +2 -2
- package/public/assets/{security-D2nHkJH6.js.map → security-CYwC-26O.js.map} +1 -1
- package/public/assets/{select-BjNcommA.js → select-B19D5ToH.js} +2 -2
- package/public/assets/{select-BjNcommA.js.map → select-B19D5ToH.js.map} +1 -1
- package/public/assets/{settings-DiUEq54n.js → settings-BzYvlKIL.js} +2 -2
- package/public/assets/{settings-DiUEq54n.js.map → settings-BzYvlKIL.js.map} +1 -1
- package/public/assets/store-BuuJUn70.js +9 -0
- package/public/assets/store-BuuJUn70.js.map +1 -0
- package/public/assets/{tasks-Dx8GVQiR.js → tasks-vZ5VRoO8.js} +2 -2
- package/public/assets/{tasks-Dx8GVQiR.js.map → tasks-vZ5VRoO8.js.map} +1 -1
- package/public/assets/{teams-DQtf7bha.js → teams-E3rI5gkA.js} +2 -2
- package/public/assets/{teams-DQtf7bha.js.map → teams-E3rI5gkA.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-D5rhXV4u.js → terminal-viewer-impl-24nB6STe.js} +2 -2
- package/public/assets/{terminal-viewer-impl-D5rhXV4u.js.map → terminal-viewer-impl-24nB6STe.js.map} +1 -1
- package/public/assets/types-uVOXgvMT.js.map +1 -1
- package/public/assets/{work-queue-BfWNDp7U.js → work-queue-Bql5E-C3.js} +2 -2
- package/public/assets/{work-queue-BfWNDp7U.js.map → work-queue-Bql5E-C3.js.map} +1 -1
- package/public/assets/{workspaces-M3xEMNXu.js → workspaces-DjQ6EdrZ.js} +2 -2
- package/public/assets/{workspaces-M3xEMNXu.js.map → workspaces-DjQ6EdrZ.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/config-store.ts +1 -1
- package/src/db/migrations.ts +5 -1
- package/src/db/scheduled-timers.ts +48 -4
- package/src/db/schema.ts +2 -1
- package/src/mcp/index.ts +2 -1
- package/src/mcp/tools-scheduler.ts +22 -3
- package/src/prompt-resolver.ts +6 -4
- package/src/routes/agent-profiles.ts +9 -0
- package/src/routes/commands.ts +8 -1
- package/src/routes/index.ts +4 -2
- package/src/routes/tasks.ts +19 -1
- package/src/services/dispatch-command.ts +136 -5
- package/src/services/scheduler-command.ts +360 -0
- package/src/services/scheduler.ts +25 -5
- package/src/services/task-done-when.ts +1 -0
- package/src/services/task-entity.ts +148 -3
- package/src/services/task-lifecycle.ts +12 -2
- package/src/sse.ts +58 -10
- package/public/assets/index-CqcAqQKW.js.map +0 -1
- package/public/assets/store-OLe8gFh0.js +0 -9
- package/public/assets/store-OLe8gFh0.js.map +0 -1
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
getTaskDetail,
|
|
31
31
|
markTaskAgentReaped,
|
|
32
32
|
} from "../db";
|
|
33
|
-
import { setTaskEntityStage } from "./task-entity";
|
|
33
|
+
import { setTaskEntityStage, setTaskEntityStatus } from "./task-entity";
|
|
34
34
|
import { postTaskMilestoneComment, taskCloseBridge, type TaskCloseBridgeEntry } from "./task-issue-weave";
|
|
35
35
|
import { requestTransientAgentShutdown } from "./transient-agent-reaper";
|
|
36
36
|
import { emitRelayEvent } from "../events";
|
|
37
37
|
import { routeNotification } from "../notification-router";
|
|
38
38
|
import { resolveCoordinatorAgentId } from "../steward-identity";
|
|
39
|
-
import { stageSatisfiesDoneWhen } from "./task-done-when";
|
|
39
|
+
import { isAgentExitDoneWhen, stageSatisfiesDoneWhen } from "./task-done-when";
|
|
40
40
|
import { emitTaskSemanticNotification, type TaskSemanticTransition } from "./task-semantic-events";
|
|
41
41
|
import type { IssueRef, TaskDeliverableStage, TaskGateState, WorkspaceRecord } from "../types";
|
|
42
42
|
import type { IssueTrackerAdapter } from "../issue-tracker";
|
|
@@ -253,6 +253,16 @@ function finalizeReap(outcomes: TaskLandOutcome[], reap: ReapResult, log: LogFn)
|
|
|
253
253
|
if (reap.reaped && reap.agentId) {
|
|
254
254
|
try {
|
|
255
255
|
markTaskAgentReaped(outcome.taskId, { agentId: reap.agentId, reason: "deliverable-landed" });
|
|
256
|
+
const task = getTaskDetail(outcome.taskId);
|
|
257
|
+
if (task && task.status !== "done" && isAgentExitDoneWhen(task.doneWhen)) {
|
|
258
|
+
setTaskEntityStatus(outcome.taskId, {
|
|
259
|
+
status: "done",
|
|
260
|
+
note: "doneWhen=agent-exit satisfied by worker reap after land",
|
|
261
|
+
actor: "task-lifecycle",
|
|
262
|
+
});
|
|
263
|
+
outcome.doneWhenSatisfied = true;
|
|
264
|
+
outcome.statusAdvanced = true;
|
|
265
|
+
}
|
|
256
266
|
} catch (error) {
|
|
257
267
|
log(`task reap marker failed for task ${outcome.taskId}: ${errMessage(error)}`);
|
|
258
268
|
}
|
package/src/sse.ts
CHANGED
|
@@ -11,12 +11,17 @@ interface Connection {
|
|
|
11
11
|
agentId: string | null;
|
|
12
12
|
controller: ReadableStreamDefaultController<Uint8Array>;
|
|
13
13
|
keepalive: Timer;
|
|
14
|
+
// Pending cross-tick backpressure re-check (#971 refinement). Set when a stream
|
|
15
|
+
// dips below the queue floor during a synchronous fanout; cleared on the deferred
|
|
16
|
+
// tick once we know whether it drained (healthy) or stayed stuck (stale → drop).
|
|
17
|
+
drainCheck?: Timer;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
const connections = new Map<string, Connection>();
|
|
17
21
|
const encoder = new TextEncoder();
|
|
18
22
|
const SSE_KEEPALIVE_MS = 15_000;
|
|
19
23
|
const SSE_RETRY_MS = 5_000;
|
|
24
|
+
const SSE_MAX_QUEUED_CHUNKS = 128;
|
|
20
25
|
|
|
21
26
|
subscribeRelayEvents((event) => fanout(event));
|
|
22
27
|
|
|
@@ -38,8 +43,9 @@ export function createSSEStream(agentId: string | null): Response {
|
|
|
38
43
|
start(controller) {
|
|
39
44
|
connId = crypto.randomUUID();
|
|
40
45
|
const keepalive = setInterval(() => {
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
const conn = connections.get(connId);
|
|
47
|
+
if (!conn) return;
|
|
48
|
+
sendEncoded(conn, encoder.encode(": keepalive\n\n"));
|
|
43
49
|
}, SSE_KEEPALIVE_MS);
|
|
44
50
|
|
|
45
51
|
connections.set(connId, { id: connId, agentId, controller, keepalive });
|
|
@@ -68,18 +74,57 @@ function removeConnection(id: string) {
|
|
|
68
74
|
const conn = connections.get(id);
|
|
69
75
|
if (conn) {
|
|
70
76
|
clearInterval(conn.keepalive);
|
|
77
|
+
if (conn.drainCheck) clearTimeout(conn.drainCheck);
|
|
71
78
|
connections.delete(id);
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
function
|
|
82
|
+
function closeConnection(id: string) {
|
|
83
|
+
const conn = connections.get(id);
|
|
84
|
+
removeConnection(id);
|
|
85
|
+
try { conn?.controller.close(); } catch {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sseFrame(event: string, data: unknown): Uint8Array {
|
|
89
|
+
return encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isBackpressured(conn: Connection): boolean {
|
|
93
|
+
return conn.controller.desiredSize !== null && conn.controller.desiredSize < -SSE_MAX_QUEUED_CHUNKS;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function sendEncoded(conn: Connection, chunk: Uint8Array) {
|
|
76
97
|
try {
|
|
77
|
-
conn.controller.enqueue(
|
|
78
|
-
`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
|
|
79
|
-
));
|
|
98
|
+
conn.controller.enqueue(chunk);
|
|
80
99
|
} catch {
|
|
100
|
+
// Controller already closed/errored — the reader is gone.
|
|
81
101
|
removeConnection(conn.id);
|
|
102
|
+
return;
|
|
82
103
|
}
|
|
104
|
+
// #971 — the queue only drains between event-loop ticks, so within a single
|
|
105
|
+
// synchronous fanout (a batch loop emitting >SSE_MAX_QUEUED_CHUNKS frames)
|
|
106
|
+
// desiredSize falls on liveness, not staleness. Closing inline here would kill
|
|
107
|
+
// healthy, actively-read dashboards mid-burst. Instead defer the verdict: mark
|
|
108
|
+
// the stream and re-check on a later tick, after the runtime has had a chance to
|
|
109
|
+
// flush to the socket. Recovered → keep it; still stuck across ticks → drop it.
|
|
110
|
+
if (isBackpressured(conn)) scheduleDrainCheck(conn);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function scheduleDrainCheck(conn: Connection) {
|
|
114
|
+
if (conn.drainCheck) return; // one deferred verdict per stream is enough
|
|
115
|
+
conn.drainCheck = setTimeout(() => {
|
|
116
|
+
conn.drainCheck = undefined;
|
|
117
|
+
// The connection may already have been removed (cancel/close) between ticks —
|
|
118
|
+
// closeConnection is idempotent, but bail early so we never touch a stale ref.
|
|
119
|
+
if (connections.get(conn.id) !== conn) return;
|
|
120
|
+
// Drained across the tick → genuinely healthy-but-slow. Stayed backpressured →
|
|
121
|
+
// genuinely stalled/stale; drop it (the original #971 protection).
|
|
122
|
+
if (isBackpressured(conn)) closeConnection(conn.id);
|
|
123
|
+
}, 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function send(conn: Connection, event: string, data: unknown) {
|
|
127
|
+
sendEncoded(conn, sseFrame(event, data));
|
|
83
128
|
}
|
|
84
129
|
|
|
85
130
|
function fanout(event: RelayEvent): void {
|
|
@@ -91,8 +136,9 @@ function fanout(event: RelayEvent): void {
|
|
|
91
136
|
sendTaskChanged(event.data as unknown as Task, event.type);
|
|
92
137
|
return;
|
|
93
138
|
}
|
|
139
|
+
const chunk = sseFrame(event.type, event.data);
|
|
94
140
|
for (const conn of connections.values()) {
|
|
95
|
-
|
|
141
|
+
sendEncoded(conn, chunk);
|
|
96
142
|
}
|
|
97
143
|
}
|
|
98
144
|
|
|
@@ -100,14 +146,15 @@ function sendNewMessage(msg: Message): void {
|
|
|
100
146
|
const scopedBroadcastAgentIds = msg.to === "broadcast"
|
|
101
147
|
? new Set(matchingDeliveryAgents("broadcast", { from: msg.from, channel: msg.channel }).map((agent) => agent.id))
|
|
102
148
|
: null;
|
|
149
|
+
const chunk = sseFrame("message.new", msg);
|
|
103
150
|
for (const conn of connections.values()) {
|
|
104
151
|
if (!conn.agentId) {
|
|
105
|
-
|
|
152
|
+
sendEncoded(conn, chunk);
|
|
106
153
|
continue;
|
|
107
154
|
}
|
|
108
155
|
if (scopedBroadcastAgentIds && !scopedBroadcastAgentIds.has(conn.agentId)) continue;
|
|
109
156
|
if (!messageMatchesAgent(msg, conn.agentId, getAgent(conn.agentId))) continue;
|
|
110
|
-
|
|
157
|
+
sendEncoded(conn, chunk);
|
|
111
158
|
}
|
|
112
159
|
}
|
|
113
160
|
|
|
@@ -215,9 +262,10 @@ export function emitMessageReactionUpdated(msg: Message) {
|
|
|
215
262
|
}
|
|
216
263
|
|
|
217
264
|
function sendTaskChanged(task: Task, eventType = "task.updated"): void {
|
|
265
|
+
const chunk = sseFrame(eventType, task);
|
|
218
266
|
for (const conn of connections.values()) {
|
|
219
267
|
if (conn.agentId && !targetMatchesAgent(task.target, conn.agentId, getAgent(conn.agentId))) continue;
|
|
220
|
-
|
|
268
|
+
sendEncoded(conn, chunk);
|
|
221
269
|
}
|
|
222
270
|
}
|
|
223
271
|
|