agent-relay-server 0.32.4 → 0.33.1

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 (125) hide show
  1. package/package.json +2 -2
  2. package/public/assets/{activity-DT1JGHnp.js → activity-B0_uE6Yh.js} +2 -2
  3. package/public/assets/{activity-DT1JGHnp.js.map → activity-B0_uE6Yh.js.map} +1 -1
  4. package/public/assets/{agent-profiles-CrMemMkZ.js → agent-profiles-Rwxrcf9F.js} +2 -2
  5. package/public/assets/{agent-profiles-CrMemMkZ.js.map → agent-profiles-Rwxrcf9F.js.map} +1 -1
  6. package/public/assets/{agents-Bl-rrgOy.js → agents-Dp1EXJc8.js} +2 -2
  7. package/public/assets/{agents-Bl-rrgOy.js.map → agents-Dp1EXJc8.js.map} +1 -1
  8. package/public/assets/{analytics-a663ak56.js → analytics-D5OT5ajj.js} +2 -2
  9. package/public/assets/{analytics-a663ak56.js.map → analytics-D5OT5ajj.js.map} +1 -1
  10. package/public/assets/automation-Dm6rXNxK.js +2 -0
  11. package/public/assets/{automation-CiaLThdO.js.map → automation-Dm6rXNxK.js.map} +1 -1
  12. package/public/assets/{branch-state-badge-D4ur3m3_.js → branch-state-badge-FX5Yww2s.js} +2 -2
  13. package/public/assets/{branch-state-badge-D4ur3m3_.js.map → branch-state-badge-FX5Yww2s.js.map} +1 -1
  14. package/public/assets/{channels-o9KLTHoK.js → channels--rdAiX17.js} +2 -2
  15. package/public/assets/{channels-o9KLTHoK.js.map → channels--rdAiX17.js.map} +1 -1
  16. package/public/assets/chat-JZAEDGfX.js +2 -0
  17. package/public/assets/chat-JZAEDGfX.js.map +1 -0
  18. package/public/assets/{connectors-CdC806mA.js → connectors-Bx4gzvNf.js} +2 -2
  19. package/public/assets/{connectors-CdC806mA.js.map → connectors-Bx4gzvNf.js.map} +1 -1
  20. package/public/assets/display-Bebqs1qu.js +3 -0
  21. package/public/assets/display-Bebqs1qu.js.map +1 -0
  22. package/public/assets/{formatted-body-impl-Ca74OAEH.js → formatted-body-impl-CVq4qHix.js} +2 -2
  23. package/public/assets/{formatted-body-impl-Ca74OAEH.js.map → formatted-body-impl-CVq4qHix.js.map} +1 -1
  24. package/public/assets/{index-C_33ymaw.js → index-BHRtR4q7.js} +8 -8
  25. package/public/assets/{index-C_33ymaw.js.map → index-BHRtR4q7.js.map} +1 -1
  26. package/public/assets/{insights-ClI68s39.js → insights-yJFgCa3o.js} +2 -2
  27. package/public/assets/{insights-ClI68s39.js.map → insights-yJFgCa3o.js.map} +1 -1
  28. package/public/assets/{integrations-1nxMizDY.js → integrations-k1HIONjo.js} +2 -2
  29. package/public/assets/{integrations-1nxMizDY.js.map → integrations-k1HIONjo.js.map} +1 -1
  30. package/public/assets/maintenance-CsoOFBXx.js +2 -0
  31. package/public/assets/{maintenance-DiFNzNPN.js.map → maintenance-CsoOFBXx.js.map} +1 -1
  32. package/public/assets/{managed-agents-Do3dKvfj.js → managed-agents-Q3HuVjGg.js} +2 -2
  33. package/public/assets/{managed-agents-Do3dKvfj.js.map → managed-agents-Q3HuVjGg.js.map} +1 -1
  34. package/public/assets/{markdown-preview-impl-CLA0J255.js → markdown-preview-impl-CnsMjrnu.js} +2 -2
  35. package/public/assets/{markdown-preview-impl-CLA0J255.js.map → markdown-preview-impl-CnsMjrnu.js.map} +1 -1
  36. package/public/assets/{memory-IjwqFzBd.js → memory-D3-K5eJS.js} +2 -2
  37. package/public/assets/{memory-IjwqFzBd.js.map → memory-D3-K5eJS.js.map} +1 -1
  38. package/public/assets/{messages-DjvWqHyn.js → messages-B4lCP5rS.js} +2 -2
  39. package/public/assets/{messages-DjvWqHyn.js.map → messages-B4lCP5rS.js.map} +1 -1
  40. package/public/assets/{orchestrators-D2IqDxDT.js → orchestrators-CRoZtLeQ.js} +2 -2
  41. package/public/assets/{orchestrators-D2IqDxDT.js.map → orchestrators-CRoZtLeQ.js.map} +1 -1
  42. package/public/assets/{overview-DKC3TbAh.js → overview-CxCU2fOF.js} +2 -2
  43. package/public/assets/{overview-DKC3TbAh.js.map → overview-CxCU2fOF.js.map} +1 -1
  44. package/public/assets/pairs-unqjPlmq.js +2 -0
  45. package/public/assets/{pairs-WpKCPE1n.js.map → pairs-unqjPlmq.js.map} +1 -1
  46. package/public/assets/{security-BF7ZtPQe.js → security-B7HhSYNy.js} +2 -2
  47. package/public/assets/{security-BF7ZtPQe.js.map → security-B7HhSYNy.js.map} +1 -1
  48. package/public/assets/{settings-CQnjrTa-.js → settings-B9NDhsAb.js} +2 -2
  49. package/public/assets/{settings-CQnjrTa-.js.map → settings-B9NDhsAb.js.map} +1 -1
  50. package/public/assets/store-DiSzYHj9.js +9 -0
  51. package/public/assets/{store-C9VcSo05.js.map → store-DiSzYHj9.js.map} +1 -1
  52. package/public/assets/{tasks-CbN_GSSb.js → tasks-CIQolvNm.js} +2 -2
  53. package/public/assets/{tasks-CbN_GSSb.js.map → tasks-CIQolvNm.js.map} +1 -1
  54. package/public/assets/{terminal-viewer-impl-BJRohThT.js → terminal-viewer-impl-DCifVqFR.js} +2 -2
  55. package/public/assets/{terminal-viewer-impl-BJRohThT.js.map → terminal-viewer-impl-DCifVqFR.js.map} +1 -1
  56. package/public/assets/{work-queue-C5xLBLmm.js → work-queue-Dr3c1V6O.js} +2 -2
  57. package/public/assets/{work-queue-C5xLBLmm.js.map → work-queue-Dr3c1V6O.js.map} +1 -1
  58. package/public/assets/{workspaces-D91H3wDX.js → workspaces-B1Jxop7h.js} +3 -3
  59. package/public/assets/{workspaces-D91H3wDX.js.map → workspaces-B1Jxop7h.js.map} +1 -1
  60. package/public/index.html +3 -3
  61. package/runner/src/adapter.ts +1 -1
  62. package/src/agent-lifecycle-events.ts +137 -0
  63. package/src/artifact-storage.ts +3 -5
  64. package/src/channel-target.ts +24 -0
  65. package/src/cli/_shared.ts +80 -0
  66. package/src/cli/agent-detect.ts +188 -0
  67. package/src/cli/agent-meta.ts +95 -0
  68. package/src/cli/context-probe.ts +88 -0
  69. package/src/cli/daemon.ts +111 -0
  70. package/src/cli/dev.ts +173 -0
  71. package/src/cli/index.ts +361 -0
  72. package/src/cli/introspect.ts +73 -0
  73. package/src/cli/memory.ts +37 -0
  74. package/src/cli/message.ts +201 -0
  75. package/src/cli/orchestrator.ts +227 -0
  76. package/src/cli/pair.ts +125 -0
  77. package/src/cli/provider.ts +209 -0
  78. package/src/cli/recipe.ts +110 -0
  79. package/src/cli/reply.ts +141 -0
  80. package/src/cli/setup.ts +57 -0
  81. package/src/cli/steward.ts +59 -0
  82. package/src/cli/token.ts +81 -0
  83. package/src/cli/upgrade.ts +193 -0
  84. package/src/cli/workspace.ts +215 -0
  85. package/src/cli.ts +4 -2718
  86. package/src/config-store.ts +10 -6
  87. package/src/db/activity.ts +194 -0
  88. package/src/db/agent-search.ts +174 -0
  89. package/src/db/agents.ts +551 -0
  90. package/src/db/artifacts.ts +342 -0
  91. package/src/db/channels.ts +576 -0
  92. package/src/db/connection.ts +71 -0
  93. package/src/db/delivery.ts +395 -0
  94. package/src/db/inbox.ts +249 -0
  95. package/src/db/index.ts +23 -0
  96. package/src/db/integrations.ts +339 -0
  97. package/src/db/mappers.ts +397 -0
  98. package/src/db/merge-lease.ts +160 -0
  99. package/src/db/message-reads.ts +304 -0
  100. package/src/db/messages.ts +434 -0
  101. package/src/db/migrations.ts +431 -0
  102. package/src/db/orchestrators.ts +358 -0
  103. package/src/db/pairs.ts +324 -0
  104. package/src/db/schema.ts +758 -0
  105. package/src/db/stats.ts +337 -0
  106. package/src/db/tasks.ts +407 -0
  107. package/src/db/workspaces.ts +440 -0
  108. package/src/db.ts +4 -5721
  109. package/src/maintenance.ts +4 -0
  110. package/src/mcp-errors.ts +7 -0
  111. package/src/mcp.ts +32 -34
  112. package/src/routes/agents-spawn.ts +9 -1
  113. package/src/routes/agents.ts +5 -0
  114. package/src/routes/commands.ts +15 -0
  115. package/src/routes/integrations.ts +6 -8
  116. package/src/spawn-targets.ts +159 -0
  117. package/src/utils.ts +16 -1
  118. package/public/assets/automation-CiaLThdO.js +0 -2
  119. package/public/assets/chat-5hvHZcAe.js +0 -2
  120. package/public/assets/chat-5hvHZcAe.js.map +0 -1
  121. package/public/assets/display-JI19Vc7L.js +0 -3
  122. package/public/assets/display-JI19Vc7L.js.map +0 -1
  123. package/public/assets/maintenance-DiFNzNPN.js +0 -2
  124. package/public/assets/pairs-WpKCPE1n.js +0 -2
  125. package/public/assets/store-C9VcSo05.js +0 -9
@@ -0,0 +1,395 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { randomUUID } from "node:crypto";
3
+ import { isRecord, stringValue, isMechanicalMessageKind } from "agent-relay-sdk";
4
+ import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
5
+ import { parseJson } from "../utils";
6
+ import { isLiveIsolatedWorkspace } from "../workspace-phase";
7
+ import {
8
+ CONTRACT_REQUIREMENTS,
9
+ contractCompatibility,
10
+ parseRuntimeCapabilities,
11
+ parseRuntimeContracts,
12
+ parseRuntimePackage,
13
+ type RuntimeContracts,
14
+ } from "../contracts";
15
+ import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
16
+ import { matchAgents } from "../agent-ref";
17
+ import { getAgent, listAgents } from "./agents.ts";
18
+ import { channelProviderForAgent } from "./channels.ts";
19
+ import { getDb } from "./connection.ts";
20
+ import { MSG_SELECT, rowToMessage, rowToMessageDeliveryAttempt } from "./mappers.ts";
21
+ import { getMessage } from "./messages.ts";
22
+ import type {
23
+ AgentCard,
24
+ ActivityEvent,
25
+ ActivityEventInput,
26
+ AgentKind,
27
+ AgentSessionGuard,
28
+ Artifact,
29
+ ArtifactBlob,
30
+ ArtifactKind,
31
+ ArtifactLink,
32
+ ArtifactSensitivity,
33
+ ArtifactVisibility,
34
+ AttachmentRef,
35
+ ChannelBinding,
36
+ ChannelBindingMode,
37
+ ChannelRouteTarget,
38
+ ChatHistoryImport,
39
+ ChatHistoryImportEntry,
40
+ ChannelSummary,
41
+ ChannelTargetHealth,
42
+ CreatePairInput,
43
+ HealthCheck,
44
+ HealthReport,
45
+ ManagedAgent,
46
+ ManagedSessionExitDiagnostics,
47
+ Message,
48
+ MessageDeliveryAttempt,
49
+ MessageDeliveryStatus,
50
+ Orchestrator,
51
+ OrchestratorHealth,
52
+ OrchestratorRuntimeInput,
53
+ OrchestratorStatus,
54
+ OrchestratorUpgradeState,
55
+ PairActionInput,
56
+ PairMessageInput,
57
+ PairSession,
58
+ PairStatus,
59
+ RegisterAgentInput,
60
+ ReplyObligation,
61
+ RegisterOrchestratorInput,
62
+ SendMessageInput,
63
+ PollQuery,
64
+ SpawnApprovalMode,
65
+ SpawnProvider,
66
+ Task,
67
+ TaskEvent,
68
+ TaskSeverity,
69
+ TaskStatus,
70
+ IntegrationEventInput,
71
+ IntegrationSummary,
72
+ IntegrationTaskStats,
73
+ InboxDraft,
74
+ InboxState,
75
+ InboxThreadState,
76
+ ContextSnapshot,
77
+ ContextState,
78
+ ProviderCapabilities,
79
+ TaskStatusInput,
80
+ WorkspaceMetadata,
81
+ WorkspaceRecord,
82
+ WorkspaceStatus,
83
+ } from "../types";
84
+
85
+ export function queueDepthLimit(target: string): number {
86
+ const row = getDb().query("SELECT value FROM config WHERE namespace = 'system' AND key = 'message-queue'").get() as { value?: string } | undefined;
87
+ const parsed = row?.value ? parseJson<Record<string, unknown>>(row.value, {}) : {};
88
+ const perTarget = parsed?.maxDepthPerTarget;
89
+ if (typeof perTarget === "number" && Number.isSafeInteger(perTarget) && perTarget > 0) return perTarget;
90
+ const targetLimits = parsed?.targetLimits;
91
+ if (targetLimits && typeof targetLimits === "object" && !Array.isArray(targetLimits)) {
92
+ const value = (targetLimits as Record<string, unknown>)[target];
93
+ if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
94
+ }
95
+ return 100;
96
+ }
97
+
98
+ export function enforceQueueLimit(target: string): void {
99
+ const limit = queueDepthLimit(target);
100
+ const rows = getDb().query(`
101
+ SELECT id FROM messages
102
+ WHERE to_target = ? AND delivery_status = 'queued'
103
+ ORDER BY queued_at DESC, id DESC
104
+ LIMIT -1 OFFSET ?
105
+ `).all(target, limit) as Array<{ id: number }>;
106
+ if (rows.length === 0) return;
107
+ const now = Date.now();
108
+ getDb().transaction(() => {
109
+ for (const row of rows) {
110
+ insertMessageDeliveryAttempt(row.id, {
111
+ action: "mark-dead",
112
+ status: "dead",
113
+ error: "queue depth limit exceeded",
114
+ poisonReason: "queue depth limit exceeded",
115
+ }, now);
116
+ setMessageDeliveryState(row.id, {
117
+ status: "dead",
118
+ error: "queue depth limit exceeded",
119
+ poisonReason: "queue depth limit exceeded",
120
+ nextRetryAt: null,
121
+ }, now);
122
+ }
123
+ })();
124
+ }
125
+
126
+ export function isDeliveryAgent(agent: AgentCard): boolean {
127
+ return agent.status !== "offline" &&
128
+ agent.id !== "user" &&
129
+ agent.id !== "system" &&
130
+ agent.kind !== "channel" &&
131
+ agent.meta?.kind !== "channel";
132
+ }
133
+
134
+ export function isChannelAgentId(agentId: string): boolean {
135
+ const agent = getAgent(agentId);
136
+ return Boolean(agent && (
137
+ agent.kind === "channel" ||
138
+ agent.meta?.kind === "channel" ||
139
+ agent.tags.includes("channel") ||
140
+ agent.capabilities.includes("channel")
141
+ ));
142
+ }
143
+
144
+ export function legacyChannelTargets(agent: AgentCard | null | undefined): string[] {
145
+ if (!agent || !isChannelAgentId(agent.id)) return [];
146
+ const aliases = new Set<string>();
147
+ const provider = channelProviderForAgent(agent);
148
+ if (provider && provider !== "custom") aliases.add(provider);
149
+ const channelType = stringValue(agent.meta?.channelType);
150
+ if (channelType) aliases.add(channelType);
151
+ const transport = stringValue(agent.meta?.transport);
152
+ if (transport) aliases.add(transport);
153
+ const providerTag = agent.tags.find((tag) => tag.startsWith("channel:"))?.slice("channel:".length);
154
+ if (providerTag) aliases.add(providerTag);
155
+ aliases.delete(agent.id);
156
+ return [...aliases];
157
+ }
158
+
159
+ export function matchingDeliveryAgents(target: string): AgentCard[] {
160
+ if (!target) return [];
161
+ const candidates = listAgents().filter(isDeliveryAgent);
162
+ if (target === "broadcast") return candidates;
163
+ const direct = getAgent(target);
164
+ if (direct) return isDeliveryAgent(direct) ? [direct] : [];
165
+ if (target.startsWith("tag:")) {
166
+ const tag = target.slice(4);
167
+ return candidates.filter((agent) => agent.tags.includes(tag));
168
+ }
169
+ if (target.startsWith("cap:")) {
170
+ const cap = target.slice(4);
171
+ return candidates.filter((agent) => agent.capabilities.includes(cap));
172
+ }
173
+ if (target.startsWith("label:")) {
174
+ const label = target.slice(6);
175
+ return candidates.filter((agent) => agent.label === label);
176
+ }
177
+ return [];
178
+ }
179
+
180
+
181
+ export function getMessageDeliveryAttempts(messageId: number, limit = 50): MessageDeliveryAttempt[] {
182
+ const safeLimit = Math.min(Math.max(limit, 1), 200);
183
+ return (getDb().query(`
184
+ SELECT * FROM message_delivery_attempts
185
+ WHERE message_id = ?
186
+ ORDER BY created_at DESC, id DESC
187
+ LIMIT ?
188
+ `).all(messageId, safeLimit) as any[]).map(rowToMessageDeliveryAttempt);
189
+ }
190
+
191
+ export function insertMessageDeliveryAttempt(
192
+ messageId: number,
193
+ input: {
194
+ agentId?: string;
195
+ action?: MessageDeliveryAttempt["action"];
196
+ status: MessageDeliveryStatus;
197
+ error?: string;
198
+ nextRetryAt?: number;
199
+ poisonReason?: string;
200
+ },
201
+ now: number,
202
+ ): void {
203
+ getDb().query(`
204
+ INSERT INTO message_delivery_attempts (message_id, agent_id, action, status, error, next_retry_at, poison_reason, created_at)
205
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
206
+ `).run(
207
+ messageId,
208
+ input.agentId ?? null,
209
+ input.action ?? "attempt",
210
+ input.status,
211
+ input.error ?? null,
212
+ input.nextRetryAt ?? null,
213
+ input.poisonReason ?? null,
214
+ now,
215
+ );
216
+ }
217
+
218
+ export function setMessageDeliveryState(
219
+ messageId: number,
220
+ input: {
221
+ status: MessageDeliveryStatus;
222
+ error?: string | null;
223
+ nextRetryAt?: number | null;
224
+ poisonReason?: string | null;
225
+ incrementAttempts?: boolean;
226
+ },
227
+ now: number,
228
+ ): boolean {
229
+ return getDb().query(`
230
+ UPDATE messages
231
+ SET delivery_status = ?,
232
+ delivery_attempts = delivery_attempts + ?,
233
+ delivery_last_error = ?,
234
+ delivery_next_retry_at = ?,
235
+ delivery_poison_reason = ?,
236
+ delivery_updated_at = ?
237
+ WHERE id = ?
238
+ `).run(
239
+ input.status,
240
+ input.incrementAttempts ? 1 : 0,
241
+ input.error ?? null,
242
+ input.nextRetryAt ?? null,
243
+ input.poisonReason ?? null,
244
+ now,
245
+ messageId,
246
+ ).changes > 0;
247
+ }
248
+
249
+ export function recordMessageDeliveryAttempt(messageId: number, input: {
250
+ agentId?: string;
251
+ status: MessageDeliveryStatus;
252
+ error?: string;
253
+ nextRetryAt?: number;
254
+ poisonReason?: string;
255
+ }): { ok: boolean; error?: string; message?: Message; attempts?: MessageDeliveryAttempt[] } {
256
+ if (!getMessage(messageId)) return { ok: false, error: "message not found" };
257
+ const now = Date.now();
258
+ getDb().transaction(() => {
259
+ insertMessageDeliveryAttempt(messageId, { ...input, action: "attempt" }, now);
260
+ setMessageDeliveryState(messageId, {
261
+ status: input.status,
262
+ error: input.status === "delivered" ? null : input.error ?? null,
263
+ nextRetryAt: input.status === "delivered" ? null : input.nextRetryAt ?? null,
264
+ poisonReason: input.status === "dead" ? input.poisonReason ?? input.error ?? null : null,
265
+ incrementAttempts: true,
266
+ }, now);
267
+ })();
268
+ return { ok: true, message: getMessage(messageId)!, attempts: getMessageDeliveryAttempts(messageId) };
269
+ }
270
+
271
+ export function applyMessageDeliveryAction(messageId: number, input: {
272
+ action: "retry-now" | "mark-dead" | "clear";
273
+ reason?: string;
274
+ agentId?: string;
275
+ }): { ok: boolean; error?: string; message?: Message; attempts?: MessageDeliveryAttempt[] } {
276
+ if (!getMessage(messageId)) return { ok: false, error: "message not found" };
277
+ const now = Date.now();
278
+ const status: MessageDeliveryStatus =
279
+ input.action === "retry-now" ? "pending" :
280
+ input.action === "mark-dead" ? "dead" :
281
+ "delivered";
282
+ const reason = input.reason?.trim() || undefined;
283
+ getDb().transaction(() => {
284
+ insertMessageDeliveryAttempt(messageId, {
285
+ agentId: input.agentId,
286
+ action: input.action,
287
+ status,
288
+ error: input.action === "mark-dead" ? reason : undefined,
289
+ poisonReason: input.action === "mark-dead" ? reason : undefined,
290
+ }, now);
291
+ setMessageDeliveryState(messageId, {
292
+ status,
293
+ error: input.action === "mark-dead" ? reason ?? "marked dead" : null,
294
+ nextRetryAt: null,
295
+ poisonReason: input.action === "mark-dead" ? reason ?? "marked dead" : null,
296
+ incrementAttempts: false,
297
+ }, now);
298
+ })();
299
+ return { ok: true, message: getMessage(messageId)!, attempts: getMessageDeliveryAttempts(messageId) };
300
+ }
301
+
302
+ export function getMessageDeliveryStatus(id: number): Pick<Message, "id" | "to" | "deliveryStatus" | "deliveryAttempts" | "deliveryLastError" | "deliveryNextRetryAt" | "deliveryPoisonReason" | "deliveryUpdatedAt" | "queuedAt" | "maxAgeSeconds" | "resolvedToAgent"> & { attempts: MessageDeliveryAttempt[] } | null {
303
+ const message = getMessage(id);
304
+ if (!message) return null;
305
+ return {
306
+ id: message.id,
307
+ to: message.to,
308
+ deliveryStatus: message.deliveryStatus,
309
+ deliveryAttempts: message.deliveryAttempts,
310
+ deliveryLastError: message.deliveryLastError,
311
+ deliveryNextRetryAt: message.deliveryNextRetryAt,
312
+ deliveryPoisonReason: message.deliveryPoisonReason,
313
+ deliveryUpdatedAt: message.deliveryUpdatedAt,
314
+ queuedAt: message.queuedAt,
315
+ maxAgeSeconds: message.maxAgeSeconds,
316
+ resolvedToAgent: message.resolvedToAgent,
317
+ attempts: getMessageDeliveryAttempts(id),
318
+ };
319
+ }
320
+
321
+ export function listQueuedMessages(target: string, limit = 100): Message[] {
322
+ const safeLimit = Math.min(Math.max(limit, 1), 500);
323
+ return (getDb().query(`
324
+ ${MSG_SELECT}
325
+ WHERE m.to_target = ? AND m.delivery_status = 'queued'
326
+ ORDER BY m.queued_at ASC, m.id ASC
327
+ LIMIT ?
328
+ `).all(target, safeLimit) as any[]).map(rowToMessage);
329
+ }
330
+
331
+ export function resolveQueuedPolicyMessages(policyName: string, agentId: string): Message[] {
332
+ const target = `policy:${policyName}`;
333
+ const rows = getDb().query(`
334
+ SELECT m.id
335
+ FROM messages m
336
+ WHERE m.to_target = ?
337
+ AND m.delivery_status IN ('queued', 'pending')
338
+ AND NOT EXISTS (SELECT 1 FROM message_reads mr WHERE mr.message_id = m.id)
339
+ AND (
340
+ m.delivery_status = 'queued'
341
+ OR m.resolved_to_agent IS NULL
342
+ OR m.resolved_to_agent != ?
343
+ )
344
+ ORDER BY COALESCE(m.queued_at, m.created_at) ASC, m.id ASC
345
+ `).all(target, agentId) as Array<{ id: number }>;
346
+ if (rows.length === 0) return [];
347
+ const ids = rows.map((row) => row.id);
348
+ const placeholders = ids.map(() => "?").join(",");
349
+ getDb().query(`
350
+ UPDATE messages
351
+ SET delivery_status = 'pending',
352
+ resolved_to_agent = ?,
353
+ delivery_attempts = delivery_attempts + 1,
354
+ delivery_last_error = NULL,
355
+ delivery_next_retry_at = NULL,
356
+ delivery_poison_reason = NULL,
357
+ delivery_updated_at = ?
358
+ WHERE id IN (${placeholders})
359
+ `).run(agentId, Date.now(), ...ids);
360
+ const now = Date.now();
361
+ for (const id of ids) {
362
+ insertMessageDeliveryAttempt(id, { agentId, status: "pending" }, now);
363
+ }
364
+ return ids.map((id) => getMessage(id)).filter((message): message is Message => Boolean(message));
365
+ }
366
+
367
+ export function expireQueuedMessages(now: number = Date.now()): Message[] {
368
+ const rows = getDb().query(`
369
+ SELECT id FROM messages
370
+ WHERE delivery_status = 'queued'
371
+ AND queued_at IS NOT NULL
372
+ AND coalesce(max_age_seconds, 86400) >= 0
373
+ AND queued_at + (coalesce(max_age_seconds, 86400) * 1000) <= ?
374
+ `).all(now) as Array<{ id: number }>;
375
+ if (rows.length === 0) return [];
376
+ const ids = rows.map((row) => row.id);
377
+ getDb().transaction(() => {
378
+ for (const id of ids) {
379
+ insertMessageDeliveryAttempt(id, {
380
+ action: "mark-dead",
381
+ status: "dead",
382
+ error: "queued message expired",
383
+ poisonReason: "queued message expired",
384
+ }, now);
385
+ setMessageDeliveryState(id, {
386
+ status: "dead",
387
+ error: "queued message expired",
388
+ poisonReason: "queued message expired",
389
+ nextRetryAt: null,
390
+ }, now);
391
+ }
392
+ })();
393
+ return ids.map((id) => getMessage(id)).filter((message): message is Message => Boolean(message));
394
+ }
395
+
@@ -0,0 +1,249 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { randomUUID } from "node:crypto";
3
+ import { isRecord, stringValue, isMechanicalMessageKind } from "agent-relay-sdk";
4
+ import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
5
+ import { parseJson } from "../utils";
6
+ import { isLiveIsolatedWorkspace } from "../workspace-phase";
7
+ import {
8
+ CONTRACT_REQUIREMENTS,
9
+ contractCompatibility,
10
+ parseRuntimeCapabilities,
11
+ parseRuntimeContracts,
12
+ parseRuntimePackage,
13
+ type RuntimeContracts,
14
+ } from "../contracts";
15
+ import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
16
+ import { matchAgents } from "../agent-ref";
17
+ import { ValidationError, getDb } from "./connection.ts";
18
+ import { rowToChatHistoryImport, rowToChatHistoryImportEntry, rowToInboxDraft, rowToInboxThreadState } from "./mappers.ts";
19
+ import { getMessage } from "./messages.ts";
20
+ import type {
21
+ AgentCard,
22
+ ActivityEvent,
23
+ ActivityEventInput,
24
+ AgentKind,
25
+ AgentSessionGuard,
26
+ Artifact,
27
+ ArtifactBlob,
28
+ ArtifactKind,
29
+ ArtifactLink,
30
+ ArtifactSensitivity,
31
+ ArtifactVisibility,
32
+ AttachmentRef,
33
+ ChannelBinding,
34
+ ChannelBindingMode,
35
+ ChannelRouteTarget,
36
+ ChatHistoryImport,
37
+ ChatHistoryImportEntry,
38
+ ChannelSummary,
39
+ ChannelTargetHealth,
40
+ CreatePairInput,
41
+ HealthCheck,
42
+ HealthReport,
43
+ ManagedAgent,
44
+ ManagedSessionExitDiagnostics,
45
+ Message,
46
+ MessageDeliveryAttempt,
47
+ MessageDeliveryStatus,
48
+ Orchestrator,
49
+ OrchestratorHealth,
50
+ OrchestratorRuntimeInput,
51
+ OrchestratorStatus,
52
+ OrchestratorUpgradeState,
53
+ PairActionInput,
54
+ PairMessageInput,
55
+ PairSession,
56
+ PairStatus,
57
+ RegisterAgentInput,
58
+ ReplyObligation,
59
+ RegisterOrchestratorInput,
60
+ SendMessageInput,
61
+ PollQuery,
62
+ SpawnApprovalMode,
63
+ SpawnProvider,
64
+ Task,
65
+ TaskEvent,
66
+ TaskSeverity,
67
+ TaskStatus,
68
+ IntegrationEventInput,
69
+ IntegrationSummary,
70
+ IntegrationTaskStats,
71
+ InboxDraft,
72
+ InboxState,
73
+ InboxThreadState,
74
+ ContextSnapshot,
75
+ ContextState,
76
+ ProviderCapabilities,
77
+ TaskStatusInput,
78
+ WorkspaceMetadata,
79
+ WorkspaceRecord,
80
+ WorkspaceStatus,
81
+ } from "../types";
82
+
83
+ export function getInboxState(operatorId: string): InboxState {
84
+ const threads = (getDb().query(
85
+ "SELECT * FROM inbox_thread_state WHERE operator_id = ? ORDER BY updated_at DESC",
86
+ ).all(operatorId) as any[]).map(rowToInboxThreadState);
87
+ const drafts = (getDb().query(
88
+ "SELECT * FROM inbox_drafts WHERE operator_id = ? ORDER BY updated_at DESC",
89
+ ).all(operatorId) as any[]).map(rowToInboxDraft);
90
+ return { operatorId, threads, drafts };
91
+ }
92
+
93
+ export function setInboxThreadState(input: {
94
+ operatorId: string;
95
+ peerId: string;
96
+ readCursorMessageId?: number | null;
97
+ archivedAtMessageId?: number | null;
98
+ }): InboxThreadState {
99
+ const now = Date.now();
100
+ const current = getDb().query(
101
+ "SELECT * FROM inbox_thread_state WHERE operator_id = ? AND peer_id = ?",
102
+ ).get(input.operatorId, input.peerId) as any | undefined;
103
+
104
+ const readCursorMessageId = Object.prototype.hasOwnProperty.call(input, "readCursorMessageId")
105
+ ? input.readCursorMessageId ?? null
106
+ : current?.read_cursor_message_id ?? null;
107
+ const archivedAtMessageId = Object.prototype.hasOwnProperty.call(input, "archivedAtMessageId")
108
+ ? input.archivedAtMessageId ?? null
109
+ : current?.archived_at_message_id ?? null;
110
+
111
+ getDb().query(`
112
+ INSERT INTO inbox_thread_state (operator_id, peer_id, read_cursor_message_id, archived_at_message_id, updated_at)
113
+ VALUES (?, ?, ?, ?, ?)
114
+ ON CONFLICT(operator_id, peer_id) DO UPDATE SET
115
+ read_cursor_message_id = excluded.read_cursor_message_id,
116
+ archived_at_message_id = excluded.archived_at_message_id,
117
+ updated_at = excluded.updated_at
118
+ `).run(input.operatorId, input.peerId, readCursorMessageId, archivedAtMessageId, now);
119
+
120
+ return rowToInboxThreadState(getDb().query(
121
+ "SELECT * FROM inbox_thread_state WHERE operator_id = ? AND peer_id = ?",
122
+ ).get(input.operatorId, input.peerId));
123
+ }
124
+
125
+ export function setInboxDraft(input: {
126
+ operatorId: string;
127
+ peerId: string;
128
+ body: string;
129
+ subject?: string | null;
130
+ channel?: string | null;
131
+ }): InboxDraft {
132
+ const now = Date.now();
133
+ getDb().query(`
134
+ INSERT INTO inbox_drafts (operator_id, peer_id, body, subject, channel, updated_at)
135
+ VALUES (?, ?, ?, ?, ?, ?)
136
+ ON CONFLICT(operator_id, peer_id) DO UPDATE SET
137
+ body = excluded.body,
138
+ subject = excluded.subject,
139
+ channel = excluded.channel,
140
+ updated_at = excluded.updated_at
141
+ `).run(input.operatorId, input.peerId, input.body, input.subject ?? null, input.channel ?? null, now);
142
+
143
+ return rowToInboxDraft(getDb().query(
144
+ "SELECT * FROM inbox_drafts WHERE operator_id = ? AND peer_id = ?",
145
+ ).get(input.operatorId, input.peerId));
146
+ }
147
+
148
+ export function deleteInboxDraft(operatorId: string, peerId: string): boolean {
149
+ return getDb().query("DELETE FROM inbox_drafts WHERE operator_id = ? AND peer_id = ?").run(operatorId, peerId).changes > 0;
150
+ }
151
+
152
+ export function createChatHistoryImport(input: {
153
+ targetAgentId?: string;
154
+ targetSpawnRequestId?: string;
155
+ sourcePeerId: string;
156
+ sourceAgentId?: string;
157
+ sourceThreadId?: string;
158
+ sourceAgentLabel?: string;
159
+ importedBy?: string;
160
+ messageIds: number[];
161
+ }): ChatHistoryImport {
162
+ if (!input.targetAgentId && !input.targetSpawnRequestId) {
163
+ throw new ValidationError("targetAgentId or targetSpawnRequestId required");
164
+ }
165
+ if (!input.sourcePeerId.trim()) throw new ValidationError("sourcePeerId required");
166
+ const uniqueMessageIds = [...new Set(input.messageIds.map((id) => Number(id)).filter((id) => Number.isInteger(id) && id > 0))];
167
+ if (uniqueMessageIds.length === 0) throw new ValidationError("messageIds required");
168
+ if (uniqueMessageIds.length > 500) throw new ValidationError("messageIds max 500");
169
+
170
+ const messages = uniqueMessageIds.map((id) => getMessage(id));
171
+ if (messages.some((message) => !message)) throw new ValidationError("message not found");
172
+ const orderedMessages = (messages as Message[]).sort((a, b) => a.id - b.id);
173
+
174
+ const id = randomUUID();
175
+ const now = Date.now();
176
+ getDb().transaction(() => {
177
+ getDb().query(`
178
+ INSERT INTO chat_history_imports (
179
+ id, target_agent_id, target_spawn_request_id, source_peer_id, source_agent_id,
180
+ source_thread_id, source_agent_label, imported_by, imported_at
181
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
182
+ `).run(
183
+ id,
184
+ input.targetAgentId ?? null,
185
+ input.targetSpawnRequestId ?? null,
186
+ input.sourcePeerId,
187
+ input.sourceAgentId ?? null,
188
+ input.sourceThreadId ?? null,
189
+ input.sourceAgentLabel ?? null,
190
+ input.importedBy ?? "user",
191
+ now,
192
+ );
193
+
194
+ const insertEntry = getDb().query(`
195
+ INSERT INTO chat_history_import_entries (
196
+ import_id, position, original_message_id, original_from, original_to, original_created_at, message_snapshot
197
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
198
+ `);
199
+ orderedMessages.forEach((message, index) => {
200
+ insertEntry.run(
201
+ id,
202
+ index,
203
+ message.id,
204
+ message.from,
205
+ message.to,
206
+ Number(message.createdAt),
207
+ JSON.stringify(message),
208
+ );
209
+ });
210
+ })();
211
+
212
+ return getChatHistoryImport(id)!;
213
+ }
214
+
215
+ export function getChatHistoryImport(id: string): ChatHistoryImport | null {
216
+ const row = getDb().query("SELECT * FROM chat_history_imports WHERE id = ?").get(id) as any | undefined;
217
+ if (!row) return null;
218
+ const entries = (getDb().query(
219
+ "SELECT * FROM chat_history_import_entries WHERE import_id = ? ORDER BY position ASC",
220
+ ).all(id) as any[]).map(rowToChatHistoryImportEntry);
221
+ return rowToChatHistoryImport(row, entries);
222
+ }
223
+
224
+ export function listChatHistoryImports(input: {
225
+ targetAgentId?: string;
226
+ targetSpawnRequestId?: string;
227
+ limit?: number;
228
+ } = {}): ChatHistoryImport[] {
229
+ const conditions: string[] = [];
230
+ const params: any[] = [];
231
+ if (input.targetAgentId) {
232
+ conditions.push("target_agent_id = ?");
233
+ params.push(input.targetAgentId);
234
+ }
235
+ if (input.targetSpawnRequestId) {
236
+ conditions.push("target_spawn_request_id = ?");
237
+ params.push(input.targetSpawnRequestId);
238
+ }
239
+ const limit = Math.max(1, Math.min(input.limit ?? 100, 500));
240
+ const where = conditions.length ? `WHERE ${conditions.join(" OR ")}` : "";
241
+ const rows = getDb().query(`SELECT * FROM chat_history_imports ${where} ORDER BY imported_at ASC LIMIT ?`).all(...params, limit) as any[];
242
+ return rows.map((row) => {
243
+ const entries = (getDb().query(
244
+ "SELECT * FROM chat_history_import_entries WHERE import_id = ? ORDER BY position ASC",
245
+ ).all(row.id) as any[]).map(rowToChatHistoryImportEntry);
246
+ return rowToChatHistoryImport(row, entries);
247
+ });
248
+ }
249
+
@@ -0,0 +1,23 @@
1
+ // Barrel for the db/ package. src/db.ts re-exports this, preserving the
2
+ // historical `import { ... } from "./db"` surface while the implementation
3
+ // lives in per-domain modules. See docs/architecture.md (DB schema) and #298.
4
+ export * from "./connection.ts";
5
+ export * from "./schema.ts";
6
+ export * from "./migrations.ts";
7
+ export * from "./mappers.ts";
8
+ export * from "./channels.ts";
9
+ export * from "./agents.ts";
10
+ export * from "./agent-search.ts";
11
+ export * from "./tasks.ts";
12
+ export * from "./integrations.ts";
13
+ export * from "./pairs.ts";
14
+ export * from "./artifacts.ts";
15
+ export * from "./messages.ts";
16
+ export * from "./delivery.ts";
17
+ export * from "./message-reads.ts";
18
+ export * from "./inbox.ts";
19
+ export * from "./activity.ts";
20
+ export * from "./stats.ts";
21
+ export * from "./orchestrators.ts";
22
+ export * from "./workspaces.ts";
23
+ export * from "./merge-lease.ts";