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.
- package/package.json +2 -2
- package/public/assets/{activity-DT1JGHnp.js → activity-B0_uE6Yh.js} +2 -2
- package/public/assets/{activity-DT1JGHnp.js.map → activity-B0_uE6Yh.js.map} +1 -1
- package/public/assets/{agent-profiles-CrMemMkZ.js → agent-profiles-Rwxrcf9F.js} +2 -2
- package/public/assets/{agent-profiles-CrMemMkZ.js.map → agent-profiles-Rwxrcf9F.js.map} +1 -1
- package/public/assets/{agents-Bl-rrgOy.js → agents-Dp1EXJc8.js} +2 -2
- package/public/assets/{agents-Bl-rrgOy.js.map → agents-Dp1EXJc8.js.map} +1 -1
- package/public/assets/{analytics-a663ak56.js → analytics-D5OT5ajj.js} +2 -2
- package/public/assets/{analytics-a663ak56.js.map → analytics-D5OT5ajj.js.map} +1 -1
- package/public/assets/automation-Dm6rXNxK.js +2 -0
- package/public/assets/{automation-CiaLThdO.js.map → automation-Dm6rXNxK.js.map} +1 -1
- package/public/assets/{branch-state-badge-D4ur3m3_.js → branch-state-badge-FX5Yww2s.js} +2 -2
- package/public/assets/{branch-state-badge-D4ur3m3_.js.map → branch-state-badge-FX5Yww2s.js.map} +1 -1
- package/public/assets/{channels-o9KLTHoK.js → channels--rdAiX17.js} +2 -2
- package/public/assets/{channels-o9KLTHoK.js.map → channels--rdAiX17.js.map} +1 -1
- package/public/assets/chat-JZAEDGfX.js +2 -0
- package/public/assets/chat-JZAEDGfX.js.map +1 -0
- package/public/assets/{connectors-CdC806mA.js → connectors-Bx4gzvNf.js} +2 -2
- package/public/assets/{connectors-CdC806mA.js.map → connectors-Bx4gzvNf.js.map} +1 -1
- package/public/assets/display-Bebqs1qu.js +3 -0
- package/public/assets/display-Bebqs1qu.js.map +1 -0
- package/public/assets/{formatted-body-impl-Ca74OAEH.js → formatted-body-impl-CVq4qHix.js} +2 -2
- package/public/assets/{formatted-body-impl-Ca74OAEH.js.map → formatted-body-impl-CVq4qHix.js.map} +1 -1
- package/public/assets/{index-C_33ymaw.js → index-BHRtR4q7.js} +8 -8
- package/public/assets/{index-C_33ymaw.js.map → index-BHRtR4q7.js.map} +1 -1
- package/public/assets/{insights-ClI68s39.js → insights-yJFgCa3o.js} +2 -2
- package/public/assets/{insights-ClI68s39.js.map → insights-yJFgCa3o.js.map} +1 -1
- package/public/assets/{integrations-1nxMizDY.js → integrations-k1HIONjo.js} +2 -2
- package/public/assets/{integrations-1nxMizDY.js.map → integrations-k1HIONjo.js.map} +1 -1
- package/public/assets/maintenance-CsoOFBXx.js +2 -0
- package/public/assets/{maintenance-DiFNzNPN.js.map → maintenance-CsoOFBXx.js.map} +1 -1
- package/public/assets/{managed-agents-Do3dKvfj.js → managed-agents-Q3HuVjGg.js} +2 -2
- package/public/assets/{managed-agents-Do3dKvfj.js.map → managed-agents-Q3HuVjGg.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-CLA0J255.js → markdown-preview-impl-CnsMjrnu.js} +2 -2
- package/public/assets/{markdown-preview-impl-CLA0J255.js.map → markdown-preview-impl-CnsMjrnu.js.map} +1 -1
- package/public/assets/{memory-IjwqFzBd.js → memory-D3-K5eJS.js} +2 -2
- package/public/assets/{memory-IjwqFzBd.js.map → memory-D3-K5eJS.js.map} +1 -1
- package/public/assets/{messages-DjvWqHyn.js → messages-B4lCP5rS.js} +2 -2
- package/public/assets/{messages-DjvWqHyn.js.map → messages-B4lCP5rS.js.map} +1 -1
- package/public/assets/{orchestrators-D2IqDxDT.js → orchestrators-CRoZtLeQ.js} +2 -2
- package/public/assets/{orchestrators-D2IqDxDT.js.map → orchestrators-CRoZtLeQ.js.map} +1 -1
- package/public/assets/{overview-DKC3TbAh.js → overview-CxCU2fOF.js} +2 -2
- package/public/assets/{overview-DKC3TbAh.js.map → overview-CxCU2fOF.js.map} +1 -1
- package/public/assets/pairs-unqjPlmq.js +2 -0
- package/public/assets/{pairs-WpKCPE1n.js.map → pairs-unqjPlmq.js.map} +1 -1
- package/public/assets/{security-BF7ZtPQe.js → security-B7HhSYNy.js} +2 -2
- package/public/assets/{security-BF7ZtPQe.js.map → security-B7HhSYNy.js.map} +1 -1
- package/public/assets/{settings-CQnjrTa-.js → settings-B9NDhsAb.js} +2 -2
- package/public/assets/{settings-CQnjrTa-.js.map → settings-B9NDhsAb.js.map} +1 -1
- package/public/assets/store-DiSzYHj9.js +9 -0
- package/public/assets/{store-C9VcSo05.js.map → store-DiSzYHj9.js.map} +1 -1
- package/public/assets/{tasks-CbN_GSSb.js → tasks-CIQolvNm.js} +2 -2
- package/public/assets/{tasks-CbN_GSSb.js.map → tasks-CIQolvNm.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BJRohThT.js → terminal-viewer-impl-DCifVqFR.js} +2 -2
- package/public/assets/{terminal-viewer-impl-BJRohThT.js.map → terminal-viewer-impl-DCifVqFR.js.map} +1 -1
- package/public/assets/{work-queue-C5xLBLmm.js → work-queue-Dr3c1V6O.js} +2 -2
- package/public/assets/{work-queue-C5xLBLmm.js.map → work-queue-Dr3c1V6O.js.map} +1 -1
- package/public/assets/{workspaces-D91H3wDX.js → workspaces-B1Jxop7h.js} +3 -3
- package/public/assets/{workspaces-D91H3wDX.js.map → workspaces-B1Jxop7h.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/src/adapter.ts +1 -1
- package/src/agent-lifecycle-events.ts +137 -0
- package/src/artifact-storage.ts +3 -5
- package/src/channel-target.ts +24 -0
- package/src/cli/_shared.ts +80 -0
- package/src/cli/agent-detect.ts +188 -0
- package/src/cli/agent-meta.ts +95 -0
- package/src/cli/context-probe.ts +88 -0
- package/src/cli/daemon.ts +111 -0
- package/src/cli/dev.ts +173 -0
- package/src/cli/index.ts +361 -0
- package/src/cli/introspect.ts +73 -0
- package/src/cli/memory.ts +37 -0
- package/src/cli/message.ts +201 -0
- package/src/cli/orchestrator.ts +227 -0
- package/src/cli/pair.ts +125 -0
- package/src/cli/provider.ts +209 -0
- package/src/cli/recipe.ts +110 -0
- package/src/cli/reply.ts +141 -0
- package/src/cli/setup.ts +57 -0
- package/src/cli/steward.ts +59 -0
- package/src/cli/token.ts +81 -0
- package/src/cli/upgrade.ts +193 -0
- package/src/cli/workspace.ts +215 -0
- package/src/cli.ts +4 -2718
- package/src/config-store.ts +10 -6
- package/src/db/activity.ts +194 -0
- package/src/db/agent-search.ts +174 -0
- package/src/db/agents.ts +551 -0
- package/src/db/artifacts.ts +342 -0
- package/src/db/channels.ts +576 -0
- package/src/db/connection.ts +71 -0
- package/src/db/delivery.ts +395 -0
- package/src/db/inbox.ts +249 -0
- package/src/db/index.ts +23 -0
- package/src/db/integrations.ts +339 -0
- package/src/db/mappers.ts +397 -0
- package/src/db/merge-lease.ts +160 -0
- package/src/db/message-reads.ts +304 -0
- package/src/db/messages.ts +434 -0
- package/src/db/migrations.ts +431 -0
- package/src/db/orchestrators.ts +358 -0
- package/src/db/pairs.ts +324 -0
- package/src/db/schema.ts +758 -0
- package/src/db/stats.ts +337 -0
- package/src/db/tasks.ts +407 -0
- package/src/db/workspaces.ts +440 -0
- package/src/db.ts +4 -5721
- package/src/maintenance.ts +4 -0
- package/src/mcp-errors.ts +7 -0
- package/src/mcp.ts +32 -34
- package/src/routes/agents-spawn.ts +9 -1
- package/src/routes/agents.ts +5 -0
- package/src/routes/commands.ts +15 -0
- package/src/routes/integrations.ts +6 -8
- package/src/spawn-targets.ts +159 -0
- package/src/utils.ts +16 -1
- package/public/assets/automation-CiaLThdO.js +0 -2
- package/public/assets/chat-5hvHZcAe.js +0 -2
- package/public/assets/chat-5hvHZcAe.js.map +0 -1
- package/public/assets/display-JI19Vc7L.js +0 -3
- package/public/assets/display-JI19Vc7L.js.map +0 -1
- package/public/assets/maintenance-DiFNzNPN.js +0 -2
- package/public/assets/pairs-WpKCPE1n.js +0 -2
- package/public/assets/store-C9VcSo05.js +0 -9
|
@@ -0,0 +1,304 @@
|
|
|
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 } from "./agents.ts";
|
|
18
|
+
import { deleteArtifactLinksForEntity } from "./artifacts.ts";
|
|
19
|
+
import { getDb, timedQuery } from "./connection.ts";
|
|
20
|
+
import { legacyChannelTargets } from "./delivery.ts";
|
|
21
|
+
import { MSG_SELECT, rowToMessage } from "./mappers.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 listRecentMessages(limit: number = 100, since?: number, channel?: string): Message[] {
|
|
86
|
+
const conditions: string[] = [];
|
|
87
|
+
const params: any[] = [];
|
|
88
|
+
|
|
89
|
+
if (since !== undefined) {
|
|
90
|
+
conditions.push("created_at > ?");
|
|
91
|
+
params.push(since);
|
|
92
|
+
}
|
|
93
|
+
if (channel) {
|
|
94
|
+
conditions.push("channel = ?");
|
|
95
|
+
params.push(channel);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
99
|
+
const sql = `${MSG_SELECT} ${where} ORDER BY m.created_at DESC LIMIT ?`;
|
|
100
|
+
params.push(limit);
|
|
101
|
+
|
|
102
|
+
const rows = timedQuery("listMessages", () => getDb().query(sql).all(...params) as any[]);
|
|
103
|
+
return rows.map(rowToMessage).reverse();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function pollMessages(query: PollQuery): Message[] {
|
|
107
|
+
const agent = getAgent(query.for);
|
|
108
|
+
const agentTags = agent?.tags ?? [];
|
|
109
|
+
const agentCaps = agent?.capabilities ?? [];
|
|
110
|
+
const agentLabel = agent?.label;
|
|
111
|
+
const agentLegacyChannelTargets = legacyChannelTargets(agent);
|
|
112
|
+
|
|
113
|
+
const conditions: string[] = [];
|
|
114
|
+
const params: any[] = [];
|
|
115
|
+
|
|
116
|
+
// Build target matching: direct + broadcast + tag + capability + label.
|
|
117
|
+
// Channel agents also accept legacy bare provider targets (for example
|
|
118
|
+
// "telegram") so older clients keep working after canonical IDs became
|
|
119
|
+
// provider:account ("telegram:default").
|
|
120
|
+
const targetClauses = ["to_target = ?", "resolved_to_agent = ?", "to_target = 'broadcast'"];
|
|
121
|
+
params.push(query.for, query.for);
|
|
122
|
+
|
|
123
|
+
for (const tag of agentTags) {
|
|
124
|
+
targetClauses.push("to_target = ?");
|
|
125
|
+
params.push(`tag:${tag}`);
|
|
126
|
+
}
|
|
127
|
+
for (const cap of agentCaps) {
|
|
128
|
+
targetClauses.push("to_target = ?");
|
|
129
|
+
params.push(`cap:${cap}`);
|
|
130
|
+
}
|
|
131
|
+
if (agentLabel) {
|
|
132
|
+
targetClauses.push("to_target = ?");
|
|
133
|
+
params.push(`label:${agentLabel}`);
|
|
134
|
+
}
|
|
135
|
+
for (const target of agentLegacyChannelTargets) {
|
|
136
|
+
targetClauses.push("to_target = ?");
|
|
137
|
+
params.push(target);
|
|
138
|
+
}
|
|
139
|
+
conditions.push(`(${targetClauses.join(" OR ")})`);
|
|
140
|
+
conditions.push("delivery_status != 'queued' AND delivery_status NOT IN ('failed', 'dead')");
|
|
141
|
+
|
|
142
|
+
// Hide active claims held by someone else, but let expired claims surface so
|
|
143
|
+
// another matching agent can recover stuck work.
|
|
144
|
+
conditions.push(`(claimable = 0 OR claimed_by IS NULL OR claimed_by = ? OR ((claim_expires_at IS NULL OR claim_expires_at <= ?) AND NOT EXISTS (SELECT 1 FROM tasks t WHERE t.message_id = m.id AND t.status IN ('done', 'failed', 'canceled'))))`);
|
|
145
|
+
params.push(query.for, Date.now());
|
|
146
|
+
|
|
147
|
+
if (query.sinceId !== undefined) {
|
|
148
|
+
conditions.push("id > ?");
|
|
149
|
+
params.push(query.sinceId);
|
|
150
|
+
} else if (query.since !== undefined) {
|
|
151
|
+
conditions.push("created_at > ?");
|
|
152
|
+
params.push(query.since);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (query.unread) {
|
|
156
|
+
conditions.push(
|
|
157
|
+
"NOT EXISTS (SELECT 1 FROM message_reads WHERE message_id = m.id AND agent_id = ?)"
|
|
158
|
+
);
|
|
159
|
+
params.push(query.for);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (query.channel) {
|
|
163
|
+
conditions.push("channel = ?");
|
|
164
|
+
params.push(query.channel);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const limit = query.limit ?? 50;
|
|
168
|
+
const sql = `${MSG_SELECT} WHERE ${conditions.join(" AND ")} ORDER BY m.created_at ASC LIMIT ?`;
|
|
169
|
+
params.push(limit);
|
|
170
|
+
|
|
171
|
+
const rows = timedQuery("pollMessages", () => getDb().query(sql).all(...params) as any[]);
|
|
172
|
+
return rows.map(rowToMessage);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function messageRequiresReply(message: Message): boolean {
|
|
176
|
+
// Server-owned notification flag (#283) wins over every kind/sender heuristic below: an
|
|
177
|
+
// explicit replyExpected:false is a fire-and-forget message that must never become an obligation.
|
|
178
|
+
if (message.replyExpected === false) return false;
|
|
179
|
+
if (isMechanicalMessageKind(message.kind)) return false;
|
|
180
|
+
if (message.from === "user") return true;
|
|
181
|
+
if (message.kind === "task" || message.kind === "channel.event") return true;
|
|
182
|
+
return Boolean(message.payload?.source);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function stringField(value: unknown): string | undefined {
|
|
186
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function recordField(value: unknown): Record<string, unknown> | undefined {
|
|
190
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function relayConversationId(message: Message): string | undefined {
|
|
194
|
+
const conversation = recordField(message.payload?.conversation);
|
|
195
|
+
const replyContext = recordField(message.payload?.replyContext);
|
|
196
|
+
return stringField(replyContext?.conversationId) ?? stringField(conversation?.id);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function isCoveredByLaterAgentResponse(message: Message, agentId: string): boolean {
|
|
200
|
+
// Order by id, not created_at: ids are monotonic insertion order, so this is
|
|
201
|
+
// robust when a reply lands in the same millisecond as the message it covers
|
|
202
|
+
// (created_at > … strictly would miss it, leaving the message wrongly pending).
|
|
203
|
+
const replies = (getDb().query(`
|
|
204
|
+
${MSG_SELECT}
|
|
205
|
+
WHERE m.from_agent = ?
|
|
206
|
+
AND m.id > ?
|
|
207
|
+
ORDER BY m.id ASC
|
|
208
|
+
LIMIT 200
|
|
209
|
+
`).all(agentId, message.id) as any[]).map(rowToMessage);
|
|
210
|
+
|
|
211
|
+
const conversationId = relayConversationId(message);
|
|
212
|
+
return replies.some((reply) => {
|
|
213
|
+
if (reply.replyTo === message.id) return true;
|
|
214
|
+
if (message.threadId !== undefined && reply.threadId === message.threadId) return true;
|
|
215
|
+
if (!message.channel || reply.channel !== message.channel || !conversationId) return false;
|
|
216
|
+
return relayConversationId(reply) === conversationId;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function replyObligationFromMessage(message: Message, agentId: string): ReplyObligation {
|
|
221
|
+
return {
|
|
222
|
+
messageId: message.id,
|
|
223
|
+
agentId,
|
|
224
|
+
from: message.from,
|
|
225
|
+
kind: message.kind,
|
|
226
|
+
...(message.subject ? { subject: message.subject } : {}),
|
|
227
|
+
...(message.channel ? { channel: message.channel } : {}),
|
|
228
|
+
bodyPreview: message.body.length > 240 ? `${message.body.slice(0, 240)}\n[truncated]` : message.body,
|
|
229
|
+
createdAt: message.createdAt,
|
|
230
|
+
replyCommand: `agent-relay /reply ${message.id} --stdin < .agent-relay/sessions/${agentId}/tmp/reply.md`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function listPendingReplyObligations(agentId: string, limit = 20): ReplyObligation[] {
|
|
235
|
+
const scanLimit = Math.max(limit * 5, 50);
|
|
236
|
+
const rows = timedQuery("listPendingReplyObligations", () => getDb().query(`
|
|
237
|
+
${MSG_SELECT}
|
|
238
|
+
WHERE EXISTS (
|
|
239
|
+
SELECT 1 FROM message_reads mr
|
|
240
|
+
WHERE mr.message_id = m.id AND mr.agent_id = ?
|
|
241
|
+
)
|
|
242
|
+
AND m.from_agent != ?
|
|
243
|
+
AND NOT EXISTS (
|
|
244
|
+
SELECT 1 FROM messages reply
|
|
245
|
+
WHERE reply.reply_to = m.id
|
|
246
|
+
AND reply.from_agent = ?
|
|
247
|
+
)
|
|
248
|
+
ORDER BY m.created_at ASC
|
|
249
|
+
LIMIT ?
|
|
250
|
+
`).all(agentId, agentId, agentId, scanLimit) as any[]);
|
|
251
|
+
return rows
|
|
252
|
+
.map(rowToMessage)
|
|
253
|
+
.filter(messageRequiresReply)
|
|
254
|
+
.filter((message) => !isCoveredByLaterAgentResponse(message, agentId))
|
|
255
|
+
.slice(0, limit)
|
|
256
|
+
.map((message) => replyObligationFromMessage(message, agentId));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function markRead(messageId: number, agentId: string): boolean {
|
|
260
|
+
const exists = getDb().query("SELECT 1 FROM messages WHERE id = ?").get(messageId);
|
|
261
|
+
if (!exists) return false;
|
|
262
|
+
getDb().query(
|
|
263
|
+
"INSERT OR IGNORE INTO message_reads (message_id, agent_id, read_at) VALUES (?, ?, ?)"
|
|
264
|
+
).run(messageId, agentId, Date.now());
|
|
265
|
+
getDb().query(`
|
|
266
|
+
UPDATE messages
|
|
267
|
+
SET delivery_status = 'delivered',
|
|
268
|
+
delivery_last_error = NULL,
|
|
269
|
+
delivery_next_retry_at = NULL,
|
|
270
|
+
delivery_poison_reason = NULL,
|
|
271
|
+
delivery_updated_at = ?
|
|
272
|
+
WHERE id = ? AND (to_target = ? OR resolved_to_agent = ? OR to_target = 'broadcast' OR to_target LIKE 'tag:%' OR to_target LIKE 'cap:%' OR to_target LIKE 'label:%')
|
|
273
|
+
`).run(Date.now(), messageId, agentId, agentId);
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
export function deleteMessage(id: number): boolean {
|
|
279
|
+
return getDb().transaction(() => {
|
|
280
|
+
// Break reply_to references from children so the FK doesn't block delete.
|
|
281
|
+
// Children keep their thread_id — the thread shows up minus this message.
|
|
282
|
+
getDb().query("UPDATE messages SET reply_to = NULL WHERE reply_to = ?").run(id);
|
|
283
|
+
deleteArtifactLinksForEntity("message", id);
|
|
284
|
+
return getDb().query("DELETE FROM messages WHERE id = ?").run(id).changes > 0;
|
|
285
|
+
})();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function getLatestMessageId(): number {
|
|
289
|
+
const row = getDb().query("SELECT MAX(id) as id FROM messages").get() as any;
|
|
290
|
+
return row?.id ?? 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function pruneOldMessages(maxAgeMs: number): number {
|
|
294
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
295
|
+
return getDb().transaction(() => {
|
|
296
|
+
getDb()
|
|
297
|
+
.query("UPDATE messages SET reply_to = NULL WHERE reply_to IN (SELECT id FROM messages WHERE created_at < ?)")
|
|
298
|
+
.run(cutoff);
|
|
299
|
+
return getDb()
|
|
300
|
+
.query("DELETE FROM messages WHERE created_at < ?")
|
|
301
|
+
.run(cutoff).changes;
|
|
302
|
+
})();
|
|
303
|
+
}
|
|
304
|
+
|