@relaycast/engine 6.3.1 → 7.0.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 (42) hide show
  1. package/dist/adapters/node/index.d.ts.map +1 -1
  2. package/dist/adapters/node/index.js +2 -0
  3. package/dist/adapters/node/index.js.map +1 -1
  4. package/dist/db/migrations/0033_dm_conversation_reservations.sql +114 -0
  5. package/dist/db/migrations/0034_node_load_reporting.sql +23 -0
  6. package/dist/db/schema.d.ts +134 -0
  7. package/dist/db/schema.d.ts.map +1 -1
  8. package/dist/db/schema.js +20 -1
  9. package/dist/db/schema.js.map +1 -1
  10. package/dist/engine/action.d.ts +1 -0
  11. package/dist/engine/action.d.ts.map +1 -1
  12. package/dist/engine/action.js +178 -28
  13. package/dist/engine/action.js.map +1 -1
  14. package/dist/engine/agent.d.ts +69 -1
  15. package/dist/engine/agent.d.ts.map +1 -1
  16. package/dist/engine/agent.js +186 -58
  17. package/dist/engine/agent.js.map +1 -1
  18. package/dist/engine/dm.d.ts +32 -0
  19. package/dist/engine/dm.d.ts.map +1 -1
  20. package/dist/engine/dm.js +146 -52
  21. package/dist/engine/dm.js.map +1 -1
  22. package/dist/engine/node.d.ts +10 -6
  23. package/dist/engine/node.d.ts.map +1 -1
  24. package/dist/engine/node.js +50 -5
  25. package/dist/engine/node.js.map +1 -1
  26. package/dist/engine/nodeProvider.d.ts +2 -1
  27. package/dist/engine/nodeProvider.d.ts.map +1 -1
  28. package/dist/engine/nodeProvider.js +30 -6
  29. package/dist/engine/nodeProvider.js.map +1 -1
  30. package/dist/engine/placement.d.ts.map +1 -1
  31. package/dist/engine/placement.js +15 -4
  32. package/dist/engine/placement.js.map +1 -1
  33. package/dist/engine/presence.d.ts.map +1 -1
  34. package/dist/engine/presence.js +5 -2
  35. package/dist/engine/presence.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/routes/agent.js +4 -1
  41. package/dist/routes/agent.js.map +1 -1
  42. package/package.json +3 -3
@@ -1,10 +1,92 @@
1
- import { eq, and, lt, inArray } from 'drizzle-orm';
2
- import { agents, channels, channelMembers, actions, deliveries, nodes } from '../db/schema.js';
1
+ import { eq, and, gt, lt, ne, inArray } from 'drizzle-orm';
2
+ import { agents, agentNodeBindings, channels, channelMembers, actions, deliveries, nodes } from '../db/schema.js';
3
3
  import { randomHex, sha256Hex } from '../lib/crypto.js';
4
4
  import { generateId } from './snowflake.js';
5
5
  import { codedError } from '../lib/httpError.js';
6
- import { directNodeIdForAgent, ensureDirectNodeForAgent } from './node.js';
7
- const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
6
+ import { directNodeIdForAgent } from './node.js';
7
+ import { runAtomicWrites } from '../ports/database.js';
8
+ /** How long an authenticated agent can be silent before it is no longer present. */
9
+ export const AGENT_LIVENESS_TTL_MS = 5 * 60 * 1000;
10
+ /**
11
+ * How long an agent must be silent before a DIFFERENT node may reclaim its
12
+ * name and overwrite its `token_hash` (`registerAgentViaNode`).
13
+ *
14
+ * Deliberately NOT `AGENT_LIVENESS_TTL_MS`, and deliberately not the `status`
15
+ * column. Presence and identity are different questions: "should the roster
16
+ * show this agent as here" is cheap to get wrong and self-corrects on the next
17
+ * heartbeat, whereas "may another node take this name and be issued a working
18
+ * token for it" is a credential handover that cannot be undone. They must not
19
+ * share a threshold, and they must not share a field that a roster read can
20
+ * write — gating identity on `status` meant a stale-agent sweep, triggered by
21
+ * an `agent list`, silently made records claimable.
22
+ *
23
+ * 24h chosen against measured production data (relaycast-cloud, 2026-08-07):
24
+ * of the 1,578 records whose reclaim eligibility this governs, silence was
25
+ * <5m: 5, 5m-24h: 9, 1d-7d: 1, >7d: 1,568. So 99.4% have been silent over a
26
+ * week and a 24h grace costs essentially nothing steady-state (1,569 eligible
27
+ * vs 1,578) while protecting the ~14 identities a human would still call live.
28
+ *
29
+ * Lower this only with fresh measurements — shortening it converts live agents
30
+ * into reclaimable ones.
31
+ */
32
+ export const AGENT_RECLAIM_GRACE_MS = 24 * 60 * 60 * 1000;
33
+ /**
34
+ * Status of a row whose name has been released back to the workspace
35
+ * (relaycast#309). The row is retained so the four RESTRICT foreign keys to
36
+ * `agents.id` stay valid and the agent's history keeps its author; only the
37
+ * name is freed. A released row is not a roster member and never serves as a
38
+ * delivery target.
39
+ */
40
+ export const RELEASED_AGENT_STATUS = 'released';
41
+ /**
42
+ * Marker separating a released agent's original name from its tombstone
43
+ * suffix. Reserved: registration rejects it, which is what makes
44
+ * {@link releasedAgentName} collision-free (see {@link assertRegistrableAgentName}).
45
+ */
46
+ export const RELEASED_NAME_MARKER = '#released-';
47
+ /**
48
+ * Tombstone name for a released agent.
49
+ *
50
+ * Keyed on the agent id, not a timestamp: the release runs inside an atomic
51
+ * batch where a `UNIQUE(workspace_id, name)` violation aborts the WHOLE unit
52
+ * rather than failing just this statement — which is the exact defect the
53
+ * tombstone exists to avoid. A timestamped name can collide; an id-keyed one
54
+ * cannot, because the id is already unique per workspace.
55
+ *
56
+ * That argument only holds while no ordinary agent can occupy this namespace.
57
+ * Agent names are otherwise arbitrary strings, so without the registration
58
+ * guard a caller could pre-register `<name>#released-<victimId>` and make the
59
+ * victim's release abort. Hence the reserved marker.
60
+ */
61
+ export function releasedAgentName(name, agentId) {
62
+ return `${name}${RELEASED_NAME_MARKER}${agentId}`;
63
+ }
64
+ /**
65
+ * Reject names that would land in the reserved tombstone namespace.
66
+ *
67
+ * Called on every registration path. Without it the release path is not
68
+ * atomic-safe: see {@link releasedAgentName}.
69
+ */
70
+ export function assertRegistrableAgentName(name) {
71
+ if (name.includes(RELEASED_NAME_MARKER)) {
72
+ throw codedError(`Agent name "${name}" is reserved: "${RELEASED_NAME_MARKER}" marks a released agent`, 'invalid_agent_name', 400);
73
+ }
74
+ }
75
+ /**
76
+ * Resolve the public presence status from server-observed activity. Persisted
77
+ * `active` is only the last reported state; it is not proof of current life.
78
+ */
79
+ export function effectiveAgentStatus(agent, now = Date.now()) {
80
+ // A client-supplied/faulty clock must not create a negative age. The
81
+ // workspace sweep durably clamps future timestamps to its server clock so
82
+ // they subsequently expire normally after one TTL window.
83
+ const observedAt = Math.min(agent.lastSeen.getTime(), now);
84
+ if ((agent.status === 'active' || agent.status === 'online')
85
+ && now - observedAt > AGENT_LIVENESS_TTL_MS) {
86
+ return 'offline';
87
+ }
88
+ return agent.status === 'online' ? 'active' : agent.status;
89
+ }
8
90
  /** Detect unique constraint violations across D1, SQLite, and drizzle error shapes. */
9
91
  function isUniqueConstraintError(err) {
10
92
  if (!err || typeof err !== 'object')
@@ -25,31 +107,82 @@ function isUniqueConstraintError(err) {
25
107
  return false;
26
108
  }
27
109
  export async function registerAgent(db, workspaceId, data) {
110
+ assertRegistrableAgentName(data.name);
28
111
  const agentId = generateId();
29
112
  const token = `at_live_${randomHex(16)}`;
30
113
  const tokenHash = await sha256Hex(token);
31
- // Use INSERT directly and let the unique index (workspace_id, name) enforce
32
- // uniqueness. Avoids TOCTOU race between SELECT check and INSERT that causes
33
- // false "already exists" errors on D1 read replicas after delete+re-register.
114
+ const directNodeId = directNodeIdForAgent(agentId);
115
+ const directNodeTokenHash = await sha256Hex(`implicit_direct:${workspaceId}:${agentId}:${randomHex(16)}`);
116
+ const now = new Date();
117
+ const [generalChannel] = await db
118
+ .select()
119
+ .from(channels)
120
+ .where(and(eq(channels.workspaceId, workspaceId), eq(channels.name, 'general')));
121
+ // Register the identity, implicit direct node, active binding, and default
122
+ // membership as one atomic unit. A failed direct-node write must not leave a
123
+ // live but undispatchable roster row behind.
34
124
  let agent;
35
125
  try {
36
- [agent] = await db
37
- .insert(agents)
38
- .values({
39
- id: agentId,
40
- workspaceId,
41
- name: data.name,
42
- handle: `@${data.name}`,
43
- type: data.type || 'agent',
44
- // Set status explicitly: the column DEFAULT is the deprecated 'online'
45
- // on databases migrated before 0013, and SQLite can't ALTER a default.
46
- status: 'active',
47
- tokenHash,
48
- persona: data.persona ?? null,
49
- metadata: data.metadata ?? {},
50
- capabilities: data.capabilities ?? null,
51
- })
52
- .returning();
126
+ const results = await runAtomicWrites(db, (writeDb) => {
127
+ const writes = [writeDb.insert(nodes).values({
128
+ id: directNodeId,
129
+ workspaceId,
130
+ name: `direct-${agentId}`,
131
+ tokenHash: directNodeTokenHash,
132
+ kind: 'ws',
133
+ role: 'direct',
134
+ deliveryAdapter: 'ws.node.v1',
135
+ deliveryConfig: { implicit: true, agent_id: agentId, agent_name: data.name },
136
+ capabilities: [],
137
+ maxAgents: 1,
138
+ activeAgents: 1,
139
+ tags: ['implicit', 'direct'],
140
+ version: 'implicit',
141
+ status: 'offline',
142
+ handlersLive: false,
143
+ load: 0,
144
+ lastHeartbeatAt: null,
145
+ createdAt: now,
146
+ }), writeDb
147
+ .insert(agents)
148
+ .values({
149
+ id: agentId,
150
+ workspaceId,
151
+ name: data.name,
152
+ handle: `@${data.name}`,
153
+ type: data.type || 'agent',
154
+ // Set status explicitly: the column DEFAULT is the deprecated 'online'
155
+ // on databases migrated before 0013, and SQLite can't ALTER a default.
156
+ status: 'active',
157
+ tokenHash,
158
+ persona: data.persona ?? null,
159
+ metadata: data.metadata ?? {},
160
+ capabilities: data.capabilities ?? null,
161
+ locationType: 'via_node',
162
+ locationNodeId: directNodeId,
163
+ })
164
+ .returning()];
165
+ if (generalChannel) {
166
+ writes.push(writeDb.insert(channelMembers).values({
167
+ channelId: generalChannel.id,
168
+ agentId,
169
+ role: 'member',
170
+ }));
171
+ }
172
+ writes.push(writeDb.insert(agentNodeBindings).values({
173
+ id: `anb_${generateId()}`,
174
+ workspaceId,
175
+ agentId,
176
+ nodeId: directNodeId,
177
+ status: 'active',
178
+ sessionRef: null,
179
+ priority: 0,
180
+ createdAt: now,
181
+ updatedAt: now,
182
+ }));
183
+ return writes;
184
+ });
185
+ [agent] = results[1];
53
186
  }
54
187
  catch (insertErr) {
55
188
  // Unique constraint violation on (workspace_id, name) → agent already exists
@@ -60,19 +193,6 @@ export async function registerAgent(db, workspaceId, data) {
60
193
  }
61
194
  throw insertErr;
62
195
  }
63
- // Auto-join #general
64
- const [generalChannel] = await db
65
- .select()
66
- .from(channels)
67
- .where(and(eq(channels.workspaceId, workspaceId), eq(channels.name, 'general')));
68
- if (generalChannel) {
69
- await db.insert(channelMembers).values({
70
- channelId: generalChannel.id,
71
- agentId,
72
- role: 'member',
73
- });
74
- }
75
- await ensureDirectNodeForAgent(db, workspaceId, agent);
76
196
  return {
77
197
  id: agentId,
78
198
  // Return the workspace id so a client that joined by workspace key (and
@@ -88,33 +208,33 @@ export async function registerAgent(db, workspaceId, data) {
88
208
  };
89
209
  }
90
210
  export async function listAgents(db, workspaceId, status) {
91
- let rows;
92
- if (status && status !== 'all') {
93
- rows = await db
94
- .select()
95
- .from(agents)
96
- .where(and(eq(agents.workspaceId, workspaceId), eq(agents.status, status)));
97
- }
98
- else {
99
- rows = await db
100
- .select()
101
- .from(agents)
102
- .where(eq(agents.workspaceId, workspaceId));
103
- }
211
+ // Keep the durable state aligned as a cleanup side effect, while still
212
+ // deriving below so correctness never depends on a cron/sweep having run.
213
+ await sweepStaleAgents(db, workspaceId);
214
+ const rows = await db
215
+ .select()
216
+ .from(agents)
217
+ // Released rows are tombstones retained only to keep history attributable;
218
+ // they are not roster members, so `agent list` must not fill with them.
219
+ .where(and(eq(agents.workspaceId, workspaceId), ne(agents.status, RELEASED_AGENT_STATUS)));
220
+ const requestedStatus = status === 'online' ? 'active' : status;
104
221
  return rows.map((a) => ({
105
222
  id: a.id,
106
223
  name: a.name,
107
224
  handle: `@${a.name}`,
108
225
  type: a.type,
109
- status: a.status,
226
+ status: effectiveAgentStatus(a),
110
227
  persona: a.persona,
111
228
  capabilities: a.capabilities ?? null,
112
229
  created_at: a.createdAt.toISOString(),
113
230
  last_seen: a.lastSeen.toISOString(),
114
231
  metadata: a.metadata,
115
- }));
232
+ })).filter((agent) => !requestedStatus || requestedStatus === 'all' || agent.status === requestedStatus);
116
233
  }
117
234
  export async function getAgentByName(db, workspaceId, name) {
235
+ // Match roster reads: detail consumers should observe both derived and
236
+ // durable presence consistently within this workspace.
237
+ await sweepStaleAgents(db, workspaceId);
118
238
  const [agent] = await db
119
239
  .select()
120
240
  .from(agents)
@@ -174,7 +294,7 @@ export async function getAgentByName(db, workspaceId, name) {
174
294
  name: agent.name,
175
295
  handle: agent.handle ?? `@${agent.name}`,
176
296
  type: agent.type,
177
- status: agent.status,
297
+ status: effectiveAgentStatus(agent),
178
298
  persona: agent.persona,
179
299
  capabilities: agent.capabilities ?? null,
180
300
  created_at: agent.createdAt.toISOString(),
@@ -227,7 +347,7 @@ export async function updateAgent(db, workspaceId, name, updates) {
227
347
  name: updated.name,
228
348
  handle: `@${updated.name}`,
229
349
  type: updated.type,
230
- status: updated.status,
350
+ status: effectiveAgentStatus(updated),
231
351
  persona: updated.persona,
232
352
  capabilities: updated.capabilities ?? null,
233
353
  created_at: updated.createdAt.toISOString(),
@@ -252,14 +372,22 @@ export async function touchLastSeen(db, agentId) {
252
372
  .set({ lastSeen: new Date(), status: 'active' })
253
373
  .where(eq(agents.id, agentId));
254
374
  }
255
- export async function sweepStaleAgents(db) {
256
- const cutoff = new Date(Date.now() - STALE_THRESHOLD_MS);
375
+ export async function sweepStaleAgents(db, workspaceId) {
376
+ const now = new Date();
377
+ const cutoff = new Date(now.getTime() - AGENT_LIVENESS_TTL_MS);
378
+ const future = and(inArray(agents.status, ['active', 'online']), gt(agents.lastSeen, now), ...(workspaceId ? [eq(agents.workspaceId, workspaceId)] : []));
379
+ const normalized = await db
380
+ .update(agents)
381
+ .set({ lastSeen: now })
382
+ .where(future)
383
+ .returning({ id: agents.id });
384
+ const stale = and(inArray(agents.status, ['active', 'online']), lt(agents.lastSeen, cutoff), ...(workspaceId ? [eq(agents.workspaceId, workspaceId)] : []));
257
385
  const result = await db
258
386
  .update(agents)
259
387
  .set({ status: 'offline' })
260
388
  // Sweep both 'active' and legacy 'online' agents during the transition period
261
- .where(and(inArray(agents.status, ['active', 'online']), lt(agents.lastSeen, cutoff)))
389
+ .where(stale)
262
390
  .returning({ id: agents.id });
263
- return result.length;
391
+ return normalized.length + result.length;
264
392
  }
265
393
  //# sourceMappingURL=agent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/engine/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/engine/agent.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,38 @@ interface SendDmOptions {
7
7
  skipA2aIntercept?: boolean;
8
8
  mailbox?: MailboxConfig;
9
9
  }
10
+ /**
11
+ * Recognize a unique-constraint violation on `dm_conversation_reservations`
12
+ * across every driver this engine actually runs on.
13
+ *
14
+ * THIS ENGINE HAS ALREADY REGRESSED THIS EXACT BUG CLASS ONCE. `engine/agent.ts`
15
+ * gained `isUniqueConstraintError` in PR #193 after clean 409 handling silently
16
+ * became an uncaught 500 against D1, because detection only matched
17
+ * better-sqlite3's error shape. `engine/observerToken.ts` documents the same
18
+ * trap. A first version of this handler matched `.code` and `.message` on the
19
+ * top-level error only — which passes against better-sqlite3 in tests and would
20
+ * have reproduced that regression in the hosted engine, where the driver differs
21
+ * from the one the test suite exercises.
22
+ *
23
+ * Self-hosted runs on better-sqlite3: `.code` is `SQLITE_CONSTRAINT_UNIQUE` and
24
+ * `.message` reads `UNIQUE constraint failed: dm_conversation_reservations...`.
25
+ * The hosted engine runs Cloudflare D1 via `drizzle-orm/d1`, which prefixes
26
+ * `D1_ERROR: ` and may re-wrap the driver error under `.cause` rather than
27
+ * surfacing it at the top level. So the chain has to be walked.
28
+ *
29
+ * Two conditions must BOTH hold somewhere in the chain: the failure is a unique
30
+ * violation, and it names this table. The table check is what stops an unrelated
31
+ * constraint failure on this insert — a FK or NOT NULL on `workspace_id` — from
32
+ * being laundered into a tidy 409 that says something untrue about participant
33
+ * pairs. They are tracked independently across the walk because a wrapper may
34
+ * carry the code while only the wrapped cause carries the message.
35
+ *
36
+ * The walk is iterative and records every object visited in a `WeakSet`,
37
+ * breaking on any revisit rather than only a direct self-reference: a multi-step
38
+ * cycle (`A -> B -> A`) would otherwise blow the stack and turn the check meant
39
+ * to prevent a 500 into one itself. Same reasoning as `isObserverTokenNameConflict`.
40
+ */
41
+ export declare function isPairReservationConflict(err: unknown): boolean;
10
42
  export declare function sendDm(db: Db, workspaceId: string, fromAgentId: string, data: {
11
43
  to: string;
12
44
  text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/engine/dm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAmB5C,OAAO,EAAqD,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE3G,OAAO,EAAiD,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAErG,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAEnC,UAAU,aAAa;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAmID,wBAAsB,MAAM,CAC1B,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EACnF,OAAO,GAAE,aAAkB;;;;;;;;;;;;;;;;;;;GAgJ5B;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;kBA8Db,MAAM;oBAAc,MAAM;;;;;;;;;;KAkChG;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,OAAO,CAAC,SAAS,EAAE,CAAC,CAwEtB"}
1
+ {"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/engine/dm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAoB5C,OAAO,EAAqD,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE3G,OAAO,EAAiD,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAErG,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAEnC,UAAU,aAAa;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CA6B/D;AAkLD,wBAAsB,MAAM,CAC1B,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EACnF,OAAO,GAAE,aAAkB;;;;;;;;;;;;;;;;;;;GAgJ5B;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;kBA8Db,MAAM;oBAAc,MAAM;;;;;;;;;;KAkChG;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,OAAO,CAAC,SAAS,EAAE,CAAC,CAwEtB"}
package/dist/engine/dm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { eq, and, sql, lt, gt, isNull, inArray } from 'drizzle-orm';
2
- import { messages, channels, agents, dmConversations, dmParticipants, messageAttachments, } from '../db/schema.js';
2
+ import { messages, channels, agents, dmConversations, dmConversationReservations, dmParticipants, messageAttachments, } from '../db/schema.js';
3
3
  import { sha256Hex } from '../lib/crypto.js';
4
4
  import { runAtomicWrites } from '../ports/database.js';
5
5
  import { generateId } from './snowflake.js';
@@ -9,62 +9,156 @@ import { buildDirectDeliveryWrite, fetchDirectDeliveryOutcomes, } from './delive
9
9
  import { DEFAULT_MAILBOX_DEPTH_CAP, DEFAULT_MAILBOX_TTL_MS } from './mailboxConfig.js';
10
10
  import { codedError } from '../lib/httpError.js';
11
11
  import { fetchAttachmentsBatch, resolveSendAttachments } from './attachments.js';
12
- async function getDmPairKey(workspaceId, agentA, agentB) {
12
+ /**
13
+ * Derivation only: this digest does not resolve or claim a conversation.
14
+ * Callers must atomically reserve the derived id before using it.
15
+ */
16
+ async function deriveDmPairKey(workspaceId, agentA, agentB) {
13
17
  const [first, second] = [agentA, agentB].sort();
14
18
  return (await sha256Hex(`${workspaceId}:${first}:${second}`)).slice(0, 24);
15
19
  }
16
- async function resolveConversation(db, workspaceId, fromAgentId, toAgentId) {
17
- const existing = await db.all(sql `
18
- SELECT dc.id, dc.channel_id
19
- FROM dm_conversations dc
20
- JOIN dm_participants p1
21
- ON p1.conversation_id = dc.id
22
- AND p1.agent_id = ${fromAgentId}
23
- AND p1.left_at IS NULL
24
- JOIN dm_participants p2
25
- ON p2.conversation_id = dc.id
26
- AND p2.agent_id = ${toAgentId}
27
- AND p2.left_at IS NULL
28
- WHERE dc.workspace_id = ${workspaceId}
29
- AND dc.dm_type = '1:1'
30
- LIMIT 1
31
- `);
32
- let conversationId = existing[0]?.id;
33
- if (!conversationId) {
34
- const pairKey = await getDmPairKey(workspaceId, fromAgentId, toAgentId);
35
- const deterministicConversationId = `dm_${pairKey}`;
36
- const deterministicChannelId = `dmch_${pairKey}`;
37
- await db.insert(channels).values({
38
- id: deterministicChannelId,
39
- workspaceId,
40
- name: `dm-${pairKey}`,
41
- channelType: 1,
42
- }).onConflictDoNothing();
43
- await db.insert(dmConversations).values({
44
- id: deterministicConversationId,
20
+ /**
21
+ * Recognize a unique-constraint violation on `dm_conversation_reservations`
22
+ * across every driver this engine actually runs on.
23
+ *
24
+ * THIS ENGINE HAS ALREADY REGRESSED THIS EXACT BUG CLASS ONCE. `engine/agent.ts`
25
+ * gained `isUniqueConstraintError` in PR #193 after clean 409 handling silently
26
+ * became an uncaught 500 against D1, because detection only matched
27
+ * better-sqlite3's error shape. `engine/observerToken.ts` documents the same
28
+ * trap. A first version of this handler matched `.code` and `.message` on the
29
+ * top-level error only — which passes against better-sqlite3 in tests and would
30
+ * have reproduced that regression in the hosted engine, where the driver differs
31
+ * from the one the test suite exercises.
32
+ *
33
+ * Self-hosted runs on better-sqlite3: `.code` is `SQLITE_CONSTRAINT_UNIQUE` and
34
+ * `.message` reads `UNIQUE constraint failed: dm_conversation_reservations...`.
35
+ * The hosted engine runs Cloudflare D1 via `drizzle-orm/d1`, which prefixes
36
+ * `D1_ERROR: ` and may re-wrap the driver error under `.cause` rather than
37
+ * surfacing it at the top level. So the chain has to be walked.
38
+ *
39
+ * Two conditions must BOTH hold somewhere in the chain: the failure is a unique
40
+ * violation, and it names this table. The table check is what stops an unrelated
41
+ * constraint failure on this insert — a FK or NOT NULL on `workspace_id` — from
42
+ * being laundered into a tidy 409 that says something untrue about participant
43
+ * pairs. They are tracked independently across the walk because a wrapper may
44
+ * carry the code while only the wrapped cause carries the message.
45
+ *
46
+ * The walk is iterative and records every object visited in a `WeakSet`,
47
+ * breaking on any revisit rather than only a direct self-reference: a multi-step
48
+ * cycle (`A -> B -> A`) would otherwise blow the stack and turn the check meant
49
+ * to prevent a 500 into one itself. Same reasoning as `isObserverTokenNameConflict`.
50
+ */
51
+ export function isPairReservationConflict(err) {
52
+ const visited = new WeakSet();
53
+ let current = err;
54
+ let sawUniqueViolation = false;
55
+ let namesReservationTable = false;
56
+ while (current && typeof current === 'object') {
57
+ if (visited.has(current))
58
+ break;
59
+ visited.add(current);
60
+ const candidate = current;
61
+ const message = candidate.message ?? '';
62
+ const lowerMessage = message.toLowerCase();
63
+ if (candidate.code === 'SQLITE_CONSTRAINT_UNIQUE'
64
+ || lowerMessage.includes('unique constraint failed')
65
+ || (candidate.code === 'SQLITE_CONSTRAINT' && lowerMessage.includes('unique'))) {
66
+ sawUniqueViolation = true;
67
+ }
68
+ if (lowerMessage.includes('dm_conversation_reservations')) {
69
+ namesReservationTable = true;
70
+ }
71
+ current = candidate.cause;
72
+ }
73
+ return sawUniqueViolation && namesReservationTable;
74
+ }
75
+ /**
76
+ * Atomically resolve or reserve a deterministic 1:1 DM id for one exact tuple.
77
+ *
78
+ * The primary-key conflict and conditional no-op update are one SQL statement.
79
+ * An identical tuple returns the existing reservation; a digest collision makes
80
+ * the conflict predicate false, returns no row, and fails closed.
81
+ *
82
+ * TWO DISTINCT COLLISIONS, and both must fail closed with the same coded 409:
83
+ *
84
+ * 1. Same conversation_id, different pair. Caught by the PRIMARY KEY conflict
85
+ * target: the conditional update predicate is false, no row is returned.
86
+ *
87
+ * 2. Same pair, different conversation_id. This violates the pair_unique
88
+ * index, which is NOT the conflict target - SQLite only accepts one - so
89
+ * the statement raises SQLITE_CONSTRAINT_UNIQUE. Raised in review of PR
90
+ * #303 and reachable in practice: migration 0033 backfills whatever `dc.id`
91
+ * a legacy 1:1 already had, without requiring it to equal the current
92
+ * derivation, so the pair can be reserved under an id the next send will
93
+ * not re-derive. Left unhandled that surfaced as a 500.
94
+ *
95
+ * Failing closed is not sufficient on its own. It has to fail closed with the
96
+ * documented code, or a caller cannot tell a refused collision from an engine
97
+ * fault - which is the same distinction the rest of this seam exists to make.
98
+ */
99
+ async function resolveOrReserveConversation(db, conversationId, workspaceId, sortedPair) {
100
+ const [participantOneId, participantTwoId] = sortedPair;
101
+ let reservation;
102
+ try {
103
+ [reservation] = await db
104
+ .insert(dmConversationReservations)
105
+ .values({
106
+ conversationId,
45
107
  workspaceId,
46
- channelId: deterministicChannelId,
47
- dmType: '1:1',
48
- }).onConflictDoNothing();
49
- // Clear `left_at` rather than no-op on conflict. Reaching this branch for an
50
- // id that already exists means the lookup above missed, and the only way it
51
- // can miss for an existing 1:1 is a participant marked departed. Leaving the
52
- // marker set would resolve the conversation while its roster disagreed —
53
- // see invariant DM-1 in __tests__/dm.test.ts.
54
- const rejoin = {
55
- target: [dmParticipants.conversationId, dmParticipants.agentId],
56
- set: { leftAt: null },
57
- };
58
- await db.insert(dmParticipants).values({
59
- conversationId: deterministicConversationId,
60
- agentId: fromAgentId,
61
- }).onConflictDoUpdate(rejoin);
62
- await db.insert(dmParticipants).values({
63
- conversationId: deterministicConversationId,
64
- agentId: toAgentId,
65
- }).onConflictDoUpdate(rejoin);
66
- conversationId = deterministicConversationId;
108
+ participantOneId,
109
+ participantTwoId,
110
+ })
111
+ .onConflictDoUpdate({
112
+ target: dmConversationReservations.conversationId,
113
+ set: { conversationId: sql `excluded.conversation_id` },
114
+ setWhere: and(eq(dmConversationReservations.workspaceId, workspaceId), eq(dmConversationReservations.participantOneId, participantOneId), eq(dmConversationReservations.participantTwoId, participantTwoId)),
115
+ })
116
+ .returning({ conversationId: dmConversationReservations.conversationId });
67
117
  }
118
+ catch (err) {
119
+ // Collision (2) above.
120
+ if (!isPairReservationConflict(err))
121
+ throw err;
122
+ throw codedError('DM participant pair is already reserved under a different conversation identifier', 'dm_conversation_id_collision', 409);
123
+ }
124
+ if (!reservation) {
125
+ throw codedError('DM conversation identifier is already reserved for a different participant pair', 'dm_conversation_id_collision', 409);
126
+ }
127
+ }
128
+ async function resolveConversation(db, workspaceId, fromAgentId, toAgentId) {
129
+ const sortedPair = [fromAgentId, toAgentId].sort();
130
+ const pairKey = await deriveDmPairKey(workspaceId, sortedPair[0], sortedPair[1]);
131
+ const conversationId = `dm_${pairKey}`;
132
+ const channelId = `dmch_${pairKey}`;
133
+ // This is the mandatory resolution seam. It must happen before any metadata
134
+ // creation so exactly one tuple can win a digest collision.
135
+ await resolveOrReserveConversation(db, conversationId, workspaceId, sortedPair);
136
+ await db.insert(channels).values({
137
+ id: channelId,
138
+ workspaceId,
139
+ name: `dm-${pairKey}`,
140
+ channelType: 1,
141
+ }).onConflictDoNothing();
142
+ await db.insert(dmConversations).values({
143
+ id: conversationId,
144
+ workspaceId,
145
+ channelId,
146
+ dmType: '1:1',
147
+ }).onConflictDoNothing();
148
+ // A deterministic 1:1 is a durable relationship. Re-resolution restores a
149
+ // stale departure marker instead of letting roster state disagree with it.
150
+ const rejoin = {
151
+ target: [dmParticipants.conversationId, dmParticipants.agentId],
152
+ set: { leftAt: null },
153
+ };
154
+ await db.insert(dmParticipants).values({
155
+ conversationId,
156
+ agentId: fromAgentId,
157
+ }).onConflictDoUpdate(rejoin);
158
+ await db.insert(dmParticipants).values({
159
+ conversationId,
160
+ agentId: toAgentId,
161
+ }).onConflictDoUpdate(rejoin);
68
162
  const [conv] = await db
69
163
  .select({ id: dmConversations.id, channelId: dmConversations.channelId })
70
164
  .from(dmConversations)