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.
Files changed (98) hide show
  1. package/docs/openapi.json +57 -1
  2. package/package.json +3 -3
  3. package/public/assets/{MessageBubble-U5sX3SNZ.js → MessageBubble-BJXjk3GB.js} +2 -2
  4. package/public/assets/{MessageBubble-U5sX3SNZ.js.map → MessageBubble-BJXjk3GB.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-Bi2gvXeE.js → PlanGraphPanel-DI5JOnm1.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-Bi2gvXeE.js.map → PlanGraphPanel-DI5JOnm1.js.map} +1 -1
  7. package/public/assets/{activity-Cwkz51pG.js → activity-BUt4FiFQ.js} +2 -2
  8. package/public/assets/{activity-Cwkz51pG.js.map → activity-BUt4FiFQ.js.map} +1 -1
  9. package/public/assets/{agent-profiles-BgddVqRp.js → agent-profiles-C1dvEaGS.js} +2 -2
  10. package/public/assets/{agent-profiles-BgddVqRp.js.map → agent-profiles-C1dvEaGS.js.map} +1 -1
  11. package/public/assets/{agents-CiGYBw0h.js → agents-DvJZ0Tcz.js} +2 -2
  12. package/public/assets/{agents-CiGYBw0h.js.map → agents-DvJZ0Tcz.js.map} +1 -1
  13. package/public/assets/{analytics-CmSHiDAI.js → analytics-DMLoMwta.js} +2 -2
  14. package/public/assets/{analytics-CmSHiDAI.js.map → analytics-DMLoMwta.js.map} +1 -1
  15. package/public/assets/{automation-BBJB4RaE.js → automation-DICpRUGK.js} +2 -2
  16. package/public/assets/{automation-BBJB4RaE.js.map → automation-DICpRUGK.js.map} +1 -1
  17. package/public/assets/{branch-state-badge-Db6uU7yT.js → branch-state-badge-BYE2U_y4.js} +2 -2
  18. package/public/assets/{branch-state-badge-Db6uU7yT.js.map → branch-state-badge-BYE2U_y4.js.map} +1 -1
  19. package/public/assets/{channels-trEI29j0.js → channels-3SlDQL3j.js} +2 -2
  20. package/public/assets/{channels-trEI29j0.js.map → channels-3SlDQL3j.js.map} +1 -1
  21. package/public/assets/{chat-DS578gZy.js → chat-_I9nvxj2.js} +2 -2
  22. package/public/assets/{chat-DS578gZy.js.map → chat-_I9nvxj2.js.map} +1 -1
  23. package/public/assets/{connectors-DNV138gK.js → connectors-CrEW591Q.js} +2 -2
  24. package/public/assets/{connectors-DNV138gK.js.map → connectors-CrEW591Q.js.map} +1 -1
  25. package/public/assets/{formatted-body-DYUVqWym.js → formatted-body-ChERiS0y.js} +3 -3
  26. package/public/assets/{formatted-body-DYUVqWym.js.map → formatted-body-ChERiS0y.js.map} +1 -1
  27. package/public/assets/{formatted-body-impl-DdcyxVO0.js → formatted-body-impl-CiaUIrP3.js} +2 -2
  28. package/public/assets/{formatted-body-impl-DdcyxVO0.js.map → formatted-body-impl-CiaUIrP3.js.map} +1 -1
  29. package/public/assets/{index-CqcAqQKW.js → index-CtBqO8vm.js} +7 -7
  30. package/public/assets/index-CtBqO8vm.js.map +1 -0
  31. package/public/assets/{insights-BunHbsTA.js → insights-DBjaf3N5.js} +2 -2
  32. package/public/assets/{insights-BunHbsTA.js.map → insights-DBjaf3N5.js.map} +1 -1
  33. package/public/assets/{integrations-COnkRfWW.js → integrations-B-bdliwU.js} +2 -2
  34. package/public/assets/{integrations-COnkRfWW.js.map → integrations-B-bdliwU.js.map} +1 -1
  35. package/public/assets/{maintenance-cICBVcpJ.js → maintenance-EWlKbynM.js} +2 -2
  36. package/public/assets/{maintenance-cICBVcpJ.js.map → maintenance-EWlKbynM.js.map} +1 -1
  37. package/public/assets/{managed-agents-BK9_Gi-M.js → managed-agents-DzjKwBh4.js} +2 -2
  38. package/public/assets/{managed-agents-BK9_Gi-M.js.map → managed-agents-DzjKwBh4.js.map} +1 -1
  39. package/public/assets/{markdown-preview-impl-EZfcTHG9.js → markdown-preview-impl-bwLB6Gc-.js} +2 -2
  40. package/public/assets/{markdown-preview-impl-EZfcTHG9.js.map → markdown-preview-impl-bwLB6Gc-.js.map} +1 -1
  41. package/public/assets/{memory-Vi-8HvK3.js → memory-DEwJsFNK.js} +2 -2
  42. package/public/assets/{memory-Vi-8HvK3.js.map → memory-DEwJsFNK.js.map} +1 -1
  43. package/public/assets/{messages-DScIiBMI.js → messages-BSMkT9X2.js} +2 -2
  44. package/public/assets/{messages-DScIiBMI.js.map → messages-BSMkT9X2.js.map} +1 -1
  45. package/public/assets/{orchestrators-DLogldXx.js → orchestrators-Bl8k3KxQ.js} +2 -2
  46. package/public/assets/{orchestrators-DLogldXx.js.map → orchestrators-Bl8k3KxQ.js.map} +1 -1
  47. package/public/assets/{overview-B_RQAQR8.js → overview-CdFe7Dmc.js} +2 -2
  48. package/public/assets/{overview-B_RQAQR8.js.map → overview-CdFe7Dmc.js.map} +1 -1
  49. package/public/assets/{pairs-CSiPQ7E2.js → pairs-0ewxtvFZ.js} +2 -2
  50. package/public/assets/{pairs-CSiPQ7E2.js.map → pairs-0ewxtvFZ.js.map} +1 -1
  51. package/public/assets/{projects-Dd8gYzcn.js → projects-DEn85LQb.js} +2 -2
  52. package/public/assets/{projects-Dd8gYzcn.js.map → projects-DEn85LQb.js.map} +1 -1
  53. package/public/assets/{prompt-settings-DsDi_Xpd.js → prompt-settings-CMQ2tHe0.js} +2 -2
  54. package/public/assets/{prompt-settings-DsDi_Xpd.js.map → prompt-settings-CMQ2tHe0.js.map} +1 -1
  55. package/public/assets/{registry-DHFUWIdb.js → registry-DntwLc8a.js} +2 -2
  56. package/public/assets/{registry-DHFUWIdb.js.map → registry-DntwLc8a.js.map} +1 -1
  57. package/public/assets/{security-D2nHkJH6.js → security-CYwC-26O.js} +2 -2
  58. package/public/assets/{security-D2nHkJH6.js.map → security-CYwC-26O.js.map} +1 -1
  59. package/public/assets/{select-BjNcommA.js → select-B19D5ToH.js} +2 -2
  60. package/public/assets/{select-BjNcommA.js.map → select-B19D5ToH.js.map} +1 -1
  61. package/public/assets/{settings-DiUEq54n.js → settings-BzYvlKIL.js} +2 -2
  62. package/public/assets/{settings-DiUEq54n.js.map → settings-BzYvlKIL.js.map} +1 -1
  63. package/public/assets/store-BuuJUn70.js +9 -0
  64. package/public/assets/store-BuuJUn70.js.map +1 -0
  65. package/public/assets/{tasks-Dx8GVQiR.js → tasks-vZ5VRoO8.js} +2 -2
  66. package/public/assets/{tasks-Dx8GVQiR.js.map → tasks-vZ5VRoO8.js.map} +1 -1
  67. package/public/assets/{teams-DQtf7bha.js → teams-E3rI5gkA.js} +2 -2
  68. package/public/assets/{teams-DQtf7bha.js.map → teams-E3rI5gkA.js.map} +1 -1
  69. package/public/assets/{terminal-viewer-impl-D5rhXV4u.js → terminal-viewer-impl-24nB6STe.js} +2 -2
  70. package/public/assets/{terminal-viewer-impl-D5rhXV4u.js.map → terminal-viewer-impl-24nB6STe.js.map} +1 -1
  71. package/public/assets/types-uVOXgvMT.js.map +1 -1
  72. package/public/assets/{work-queue-BfWNDp7U.js → work-queue-Bql5E-C3.js} +2 -2
  73. package/public/assets/{work-queue-BfWNDp7U.js.map → work-queue-Bql5E-C3.js.map} +1 -1
  74. package/public/assets/{workspaces-M3xEMNXu.js → workspaces-DjQ6EdrZ.js} +2 -2
  75. package/public/assets/{workspaces-M3xEMNXu.js.map → workspaces-DjQ6EdrZ.js.map} +1 -1
  76. package/public/index.html +3 -3
  77. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  78. package/src/config-store.ts +1 -1
  79. package/src/db/migrations.ts +5 -1
  80. package/src/db/scheduled-timers.ts +48 -4
  81. package/src/db/schema.ts +2 -1
  82. package/src/mcp/index.ts +2 -1
  83. package/src/mcp/tools-scheduler.ts +22 -3
  84. package/src/prompt-resolver.ts +6 -4
  85. package/src/routes/agent-profiles.ts +9 -0
  86. package/src/routes/commands.ts +8 -1
  87. package/src/routes/index.ts +4 -2
  88. package/src/routes/tasks.ts +19 -1
  89. package/src/services/dispatch-command.ts +136 -5
  90. package/src/services/scheduler-command.ts +360 -0
  91. package/src/services/scheduler.ts +25 -5
  92. package/src/services/task-done-when.ts +1 -0
  93. package/src/services/task-entity.ts +148 -3
  94. package/src/services/task-lifecycle.ts +12 -2
  95. package/src/sse.ts +58 -10
  96. package/public/assets/index-CqcAqQKW.js.map +0 -1
  97. package/public/assets/store-OLe8gFh0.js +0 -9
  98. 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
- try { controller.enqueue(encoder.encode(": keepalive\n\n")); }
42
- catch { removeConnection(connId); }
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 send(conn: Connection, event: string, data: unknown) {
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(encoder.encode(
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
- send(conn, event.type, event.data);
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
- send(conn, "message.new", msg);
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
- send(conn, "message.new", msg);
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
- send(conn, eventType, task);
268
+ sendEncoded(conn, chunk);
221
269
  }
222
270
  }
223
271