@opengeni/db 0.22.2 → 0.23.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 (48) hide show
  1. package/dist/{chunk-CYGFLLMN.js → chunk-3UCHDMKG.js} +656 -282
  2. package/dist/chunk-3UCHDMKG.js.map +1 -0
  3. package/dist/{chunk-BNGEN5QZ.js → chunk-L6ADMZHE.js} +24 -2
  4. package/dist/chunk-L6ADMZHE.js.map +1 -0
  5. package/dist/index.d.ts +39 -3
  6. package/dist/index.js +7009 -3882
  7. package/dist/index.js.map +1 -1
  8. package/dist/provision-roles.js +1 -1
  9. package/dist/runtime-posture.d.ts +3 -3
  10. package/dist/schema.d.ts +1416 -92
  11. package/dist/schema.js +11 -1
  12. package/dist/session-control.d.ts +7 -1
  13. package/dist/session-queue-commands.d.ts +8 -0
  14. package/dist/session-realtime-context.d.ts +56 -0
  15. package/dist/session-realtime-ledger.d.ts +188 -0
  16. package/dist/session-realtime-mirror.d.ts +30 -0
  17. package/dist/session-realtime-state.d.ts +2 -0
  18. package/dist/session-realtime-terminal.d.ts +40 -0
  19. package/dist/session-realtime.d.ts +59 -0
  20. package/dist/workspace-instruction-policies-schema.d.ts +239 -0
  21. package/dist/workspace-instruction-policies.d.ts +44 -0
  22. package/drizzle/0156_slack_reaction_trigger.sql +49 -0
  23. package/drizzle/0157_session_policy_role_snapshots.sql +1146 -0
  24. package/drizzle/0158_session_realtime_mode.sql +88 -0
  25. package/drizzle/0159_session_realtime_ledger.sql +198 -0
  26. package/drizzle/0160_session_realtime_delegation_terminal.sql +38 -0
  27. package/drizzle/0161_session_realtime_context_projection.sql +82 -0
  28. package/drizzle/0162_session_realtime_connection_promotion.sql +53 -0
  29. package/drizzle/0163_session_realtime_delegation_progress.sql +35 -0
  30. package/drizzle/0164_session_realtime_models.sql +28 -0
  31. package/package.json +4 -4
  32. package/src/index.ts +614 -78
  33. package/src/preference-registry.ts +11 -6
  34. package/src/provision-roles.ts +12 -0
  35. package/src/runtime-posture.ts +10 -0
  36. package/src/schema.ts +400 -43
  37. package/src/session-control.ts +596 -21
  38. package/src/session-queue-commands.ts +76 -7
  39. package/src/session-realtime-context.ts +393 -0
  40. package/src/session-realtime-ledger.ts +1790 -0
  41. package/src/session-realtime-mirror.ts +160 -0
  42. package/src/session-realtime-state.ts +25 -0
  43. package/src/session-realtime-terminal.ts +306 -0
  44. package/src/session-realtime.ts +611 -0
  45. package/src/workspace-instruction-policies-schema.ts +41 -0
  46. package/src/workspace-instruction-policies.ts +131 -2
  47. package/dist/chunk-BNGEN5QZ.js.map +0 -1
  48. package/dist/chunk-CYGFLLMN.js.map +0 -1
@@ -0,0 +1,160 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { and, desc, eq, gt, sql } from "drizzle-orm";
4
+
5
+ import { sanitizeEventPayload } from "./event-payload-sanitizer";
6
+ import type { Database } from "./index";
7
+ import * as schema from "./schema";
8
+
9
+ const SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES = 131_072;
10
+ const SESSION_REALTIME_MIRROR_MAX_PAYLOAD_BYTES = 131_072;
11
+ const TRUNCATION_MARKER = "\n…realtime context truncated…";
12
+
13
+ export type SessionRealtimeMirrorChannel = "speakable" | "commentary" | null;
14
+
15
+ export type MirrorSessionRealtimeContextInput = {
16
+ accountId: string;
17
+ workspaceId: string;
18
+ sessionId: string;
19
+ sourceKind: "human_input" | "assistant_progress" | "assistant_terminal";
20
+ sourceId: string;
21
+ text: string;
22
+ channel: SessionRealtimeMirrorChannel;
23
+ turnId?: string | null;
24
+ payload?: Record<string, unknown> | undefined;
25
+ now?: Date | undefined;
26
+ };
27
+
28
+ export type MirrorSessionRealtimeContextResult = {
29
+ entry: typeof schema.sessionRealtimeEntries.$inferSelect;
30
+ replay: boolean;
31
+ } | null;
32
+
33
+ function deterministicUuid(seed: string): string {
34
+ const bytes = createHash("sha256").update(seed, "utf8").digest().subarray(0, 16);
35
+ bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x50;
36
+ bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
37
+ const hex = bytes.toString("hex");
38
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
39
+ }
40
+
41
+ function takeUtf8Head(value: string, maximumBytes: number): string {
42
+ const bytes = Buffer.from(value, "utf8");
43
+ if (bytes.length <= maximumBytes) return value;
44
+ let end = maximumBytes;
45
+ while (end > 0 && (bytes[end]! & 0xc0) === 0x80) end -= 1;
46
+ return bytes.subarray(0, end).toString("utf8");
47
+ }
48
+
49
+ function boundedText(value: string): string {
50
+ if (Buffer.byteLength(value, "utf8") <= SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES) return value;
51
+ const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
52
+ return `${takeUtf8Head(value, SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES - markerBytes)}${TRUNCATION_MARKER}`;
53
+ }
54
+
55
+ function boundedPayload(value: Record<string, unknown>): Record<string, unknown> {
56
+ const payload = sanitizeEventPayload(value);
57
+ if (
58
+ Buffer.byteLength(JSON.stringify(payload), "utf8") > SESSION_REALTIME_MIRROR_MAX_PAYLOAD_BYTES
59
+ ) {
60
+ throw new Error("Realtime mirror payload exceeds the durable ledger limit");
61
+ }
62
+ return payload;
63
+ }
64
+
65
+ /**
66
+ * Append one canonical same-session fact to the active realtime conversation.
67
+ * The caller already owns the session event-write lock. Locking the active mode
68
+ * serializes sequence allocation with lifecycle end and browser ledger sync.
69
+ */
70
+ export async function mirrorSessionRealtimeContextInTransaction(
71
+ db: Database,
72
+ input: MirrorSessionRealtimeContextInput,
73
+ ): Promise<MirrorSessionRealtimeContextResult> {
74
+ const now = input.now ?? new Date();
75
+ const modes = await db
76
+ .select()
77
+ .from(schema.sessionRealtimeModes)
78
+ .where(
79
+ and(
80
+ eq(schema.sessionRealtimeModes.accountId, input.accountId),
81
+ eq(schema.sessionRealtimeModes.workspaceId, input.workspaceId),
82
+ eq(schema.sessionRealtimeModes.sessionId, input.sessionId),
83
+ eq(schema.sessionRealtimeModes.state, "active"),
84
+ gt(schema.sessionRealtimeModes.leaseExpiresAt, now),
85
+ ),
86
+ )
87
+ .orderBy(desc(schema.sessionRealtimeModes.startedAt))
88
+ .for("update")
89
+ .limit(2);
90
+ if (modes.length === 0) return null;
91
+ if (modes.length !== 1) {
92
+ throw new Error(`Session ${input.sessionId} has multiple active realtime modes`);
93
+ }
94
+ const mode = modes[0]!;
95
+ const operationId = deterministicUuid(
96
+ `opengeni:session-realtime-mirror:${mode.id}:${input.sourceKind}:${input.sourceId}`,
97
+ );
98
+ const [existing] = await db
99
+ .select()
100
+ .from(schema.sessionRealtimeEntries)
101
+ .where(
102
+ and(
103
+ eq(schema.sessionRealtimeEntries.realtimeId, mode.id),
104
+ eq(schema.sessionRealtimeEntries.operationId, operationId),
105
+ ),
106
+ )
107
+ .limit(1);
108
+ if (existing) return { entry: existing, replay: true };
109
+
110
+ const [sequenceRow] = await db
111
+ .select({ next: sql<number>`coalesce(max(${schema.sessionRealtimeEntries.sequence}), 0) + 1` })
112
+ .from(schema.sessionRealtimeEntries)
113
+ .where(eq(schema.sessionRealtimeEntries.realtimeId, mode.id));
114
+ const payload = boundedPayload({
115
+ ...(input.payload ?? {}),
116
+ source: input.sourceKind,
117
+ sourceId: input.sourceId,
118
+ channel: input.channel,
119
+ ...(input.turnId ? { sourceTurnId: input.turnId } : {}),
120
+ });
121
+ const [entry] = await db
122
+ .insert(schema.sessionRealtimeEntries)
123
+ .values({
124
+ accountId: input.accountId,
125
+ workspaceId: input.workspaceId,
126
+ sessionId: input.sessionId,
127
+ realtimeId: mode.id,
128
+ operationId,
129
+ connectionEpoch: mode.connectionEpoch,
130
+ sequence: Number(sequenceRow?.next ?? 1),
131
+ direction: "provider_out",
132
+ kind: "session_update",
133
+ text: boundedText(input.text),
134
+ payload,
135
+ createdAt: now,
136
+ updatedAt: now,
137
+ })
138
+ .returning();
139
+ if (!entry) throw new Error("Failed to append realtime session context");
140
+ return { entry, replay: false };
141
+ }
142
+
143
+ export function renderRealtimeHumanInputContext(input: {
144
+ delivery: "send" | "steer";
145
+ routing: "accepted_for_execution" | "queued_for_execution" | "accepted_for_steering";
146
+ text: string;
147
+ }): string {
148
+ return [
149
+ "<session_user_message>",
150
+ ` <status>${input.routing}</status>`,
151
+ ` <delivery>${input.delivery}</delivery>`,
152
+ ` <text>${escapeXmlText(input.text)}</text>`,
153
+ " <instruction>Already routed by OpenGeni; do not delegate this message again.</instruction>",
154
+ "</session_user_message>",
155
+ ].join("\n");
156
+ }
157
+
158
+ function escapeXmlText(value: string): string {
159
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
160
+ }
@@ -0,0 +1,25 @@
1
+ import { and, eq, gt } from "drizzle-orm";
2
+
3
+ import type { Database } from "./index";
4
+ import * as schema from "./schema";
5
+
6
+ export async function sessionRealtimeIsActiveInTransaction(
7
+ db: Database,
8
+ workspaceId: string,
9
+ sessionId: string,
10
+ now = new Date(),
11
+ ): Promise<boolean> {
12
+ const [row] = await db
13
+ .select({ id: schema.sessionRealtimeModes.id })
14
+ .from(schema.sessionRealtimeModes)
15
+ .where(
16
+ and(
17
+ eq(schema.sessionRealtimeModes.workspaceId, workspaceId),
18
+ eq(schema.sessionRealtimeModes.sessionId, sessionId),
19
+ eq(schema.sessionRealtimeModes.state, "active"),
20
+ gt(schema.sessionRealtimeModes.leaseExpiresAt, now),
21
+ ),
22
+ )
23
+ .limit(1);
24
+ return Boolean(row);
25
+ }
@@ -0,0 +1,306 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { and, eq, inArray, sql } from "drizzle-orm";
4
+
5
+ import { sanitizeEventPayload } from "./event-payload-sanitizer";
6
+ import type { Database } from "./index";
7
+ import { mirrorSessionRealtimeContextInTransaction } from "./session-realtime-mirror";
8
+ import * as schema from "./schema";
9
+
10
+ const MAX_TEXT_BYTES = 131_072;
11
+ const MAX_PAYLOAD_BYTES = 131_072;
12
+
13
+ export type ProjectSessionRealtimeDelegationTerminalInput = {
14
+ accountId: string;
15
+ workspaceId: string;
16
+ sessionId: string;
17
+ turnId: string;
18
+ turnStatus: "completed" | "failed" | "cancelled" | "superseded";
19
+ terminalEvent: {
20
+ type: "turn.completed" | "turn.failed" | "turn.cancelled" | "turn.superseded";
21
+ payload: Record<string, unknown>;
22
+ };
23
+ now?: Date;
24
+ };
25
+
26
+ export type SessionRealtimeTerminalEntry = {
27
+ id: string;
28
+ realtimeId: string;
29
+ operationId: string;
30
+ connectionEpoch: number;
31
+ sequence: number;
32
+ direction: "provider_in" | "provider_out";
33
+ kind:
34
+ | "user_transcript"
35
+ | "assistant_transcript"
36
+ | "delegation_call"
37
+ | "delegation_progress"
38
+ | "delegation_result"
39
+ | "interruption"
40
+ | "session_update"
41
+ | "error";
42
+ role: "user" | "assistant" | null;
43
+ providerEventId: string | null;
44
+ delegationItemId: string | null;
45
+ sourceUpdateId: string | null;
46
+ historyItemId: string | null;
47
+ turnId: string | null;
48
+ text: string | null;
49
+ payload: Record<string, unknown>;
50
+ clientAckedAt: string | null;
51
+ providerAckedAt: string | null;
52
+ createdAt: string;
53
+ updatedAt: string;
54
+ };
55
+
56
+ export type ProjectSessionRealtimeDelegationTerminalResult = {
57
+ entry: SessionRealtimeTerminalEntry;
58
+ replay: boolean;
59
+ } | null;
60
+
61
+ function deterministicUuid(seed: string): string {
62
+ const bytes = createHash("sha256").update(seed, "utf8").digest().subarray(0, 16);
63
+ bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x50;
64
+ bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
65
+ const hex = bytes.toString("hex");
66
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
67
+ }
68
+
69
+ function deterministicTerminalOperationId(turnId: string): string {
70
+ return deterministicUuid(`opengeni:session-realtime-delegation-terminal:${turnId}`);
71
+ }
72
+
73
+ function boundedPayload(input: Record<string, unknown> | undefined): Record<string, unknown> {
74
+ const payload = sanitizeEventPayload(input ?? {});
75
+ if (Buffer.byteLength(JSON.stringify(payload), "utf8") > MAX_PAYLOAD_BYTES) {
76
+ throw new Error("Realtime payload exceeds the durable ledger limit");
77
+ }
78
+ return payload;
79
+ }
80
+
81
+ function assertBoundedText(value: string, label: string): void {
82
+ if (Buffer.byteLength(value, "utf8") > MAX_TEXT_BYTES) {
83
+ throw new Error(`${label} exceeds the durable realtime ledger limit`);
84
+ }
85
+ }
86
+
87
+ function terminalProjection(input: ProjectSessionRealtimeDelegationTerminalInput): {
88
+ kind: "delegation_result" | "error";
89
+ text: string;
90
+ payload: Record<string, unknown>;
91
+ } {
92
+ const expectedType = `turn.${input.turnStatus}`;
93
+ if (input.terminalEvent.type !== expectedType) {
94
+ throw new Error(
95
+ `Realtime delegation terminal event ${input.terminalEvent.type} does not match ${input.turnStatus}`,
96
+ );
97
+ }
98
+ const terminal = boundedPayload(input.terminalEvent.payload);
99
+ if (input.turnStatus === "completed") {
100
+ const output =
101
+ typeof terminal.output === "string" ? terminal.output : "Delegated turn completed.";
102
+ assertBoundedText(output, "Delegation result");
103
+ return {
104
+ kind: "delegation_result",
105
+ text: output,
106
+ payload: boundedPayload({
107
+ status: input.turnStatus,
108
+ turnId: input.turnId,
109
+ terminalEventType: input.terminalEvent.type,
110
+ terminal,
111
+ }),
112
+ };
113
+ }
114
+ const code =
115
+ typeof terminal.code === "string" && terminal.code.length > 0
116
+ ? terminal.code
117
+ : `delegation_turn_${input.turnStatus}`;
118
+ const message =
119
+ typeof terminal.error === "string" && terminal.error.length > 0
120
+ ? terminal.error
121
+ : `Delegated turn ${input.turnStatus}.`;
122
+ assertBoundedText(message, "Delegation error");
123
+ return {
124
+ kind: "error",
125
+ text: message,
126
+ payload: boundedPayload({
127
+ code,
128
+ status: input.turnStatus,
129
+ turnId: input.turnId,
130
+ terminalEventType: input.terminalEvent.type,
131
+ terminal,
132
+ }),
133
+ };
134
+ }
135
+
136
+ function mapEntry(
137
+ row: typeof schema.sessionRealtimeEntries.$inferSelect,
138
+ ): SessionRealtimeTerminalEntry {
139
+ return {
140
+ id: row.id,
141
+ realtimeId: row.realtimeId,
142
+ operationId: row.operationId,
143
+ connectionEpoch: row.connectionEpoch,
144
+ sequence: row.sequence,
145
+ direction: row.direction as "provider_in" | "provider_out",
146
+ kind: row.kind as SessionRealtimeTerminalEntry["kind"],
147
+ role: row.role as "user" | "assistant" | null,
148
+ providerEventId: row.providerEventId,
149
+ delegationItemId: row.delegationItemId,
150
+ sourceUpdateId: row.sourceUpdateId,
151
+ historyItemId: row.historyItemId,
152
+ turnId: row.turnId,
153
+ text: row.text,
154
+ payload: row.payload,
155
+ clientAckedAt: row.clientAckedAt?.toISOString() ?? null,
156
+ providerAckedAt: row.providerAckedAt?.toISOString() ?? null,
157
+ createdAt: row.createdAt.toISOString(),
158
+ updatedAt: row.updatedAt.toISOString(),
159
+ };
160
+ }
161
+
162
+ /** One terminal projection for every realtime-delegated durable turn. */
163
+ export async function projectSessionRealtimeDelegationTerminalInTransaction(
164
+ db: Database,
165
+ input: ProjectSessionRealtimeDelegationTerminalInput,
166
+ ): Promise<ProjectSessionRealtimeDelegationTerminalResult> {
167
+ // Steer is a continuing session interaction, not a failure to announce.
168
+ // Realtime receives the accepted direction and subsequent agent stream from
169
+ // the canonical session mirror, so emitting a synthetic terminal is wrong.
170
+ if (input.turnStatus === "superseded") return null;
171
+
172
+ const calls = await db
173
+ .select()
174
+ .from(schema.sessionRealtimeEntries)
175
+ .where(
176
+ and(
177
+ eq(schema.sessionRealtimeEntries.accountId, input.accountId),
178
+ eq(schema.sessionRealtimeEntries.workspaceId, input.workspaceId),
179
+ eq(schema.sessionRealtimeEntries.sessionId, input.sessionId),
180
+ eq(schema.sessionRealtimeEntries.turnId, input.turnId),
181
+ eq(schema.sessionRealtimeEntries.direction, "provider_in"),
182
+ eq(schema.sessionRealtimeEntries.kind, "delegation_call"),
183
+ ),
184
+ )
185
+ .for("update")
186
+ .limit(2);
187
+ if (calls.length > 1) {
188
+ throw new Error(`Delegation turn ${input.turnId} has multiple accepted realtime calls`);
189
+ }
190
+ const projected = terminalProjection(input);
191
+ const now = input.now ?? new Date();
192
+ const call = calls[0] ?? null;
193
+ const [mode] = call
194
+ ? await db
195
+ .select({
196
+ id: schema.sessionRealtimeModes.id,
197
+ connectionEpoch: schema.sessionRealtimeModes.connectionEpoch,
198
+ state: schema.sessionRealtimeModes.state,
199
+ leaseExpiresAt: schema.sessionRealtimeModes.leaseExpiresAt,
200
+ })
201
+ .from(schema.sessionRealtimeModes)
202
+ .where(
203
+ and(
204
+ eq(schema.sessionRealtimeModes.id, call.realtimeId),
205
+ eq(schema.sessionRealtimeModes.accountId, input.accountId),
206
+ eq(schema.sessionRealtimeModes.workspaceId, input.workspaceId),
207
+ eq(schema.sessionRealtimeModes.sessionId, input.sessionId),
208
+ ),
209
+ )
210
+ .limit(1)
211
+ : [];
212
+ if (call && !mode) {
213
+ throw new Error(`Delegation turn ${input.turnId} lost its realtime mode ownership`);
214
+ }
215
+ const routesToActiveDelegation =
216
+ call !== null && mode?.state === "active" && mode.leaseExpiresAt > now;
217
+ if (!routesToActiveDelegation) {
218
+ const mirrored = await mirrorSessionRealtimeContextInTransaction(db, {
219
+ accountId: input.accountId,
220
+ workspaceId: input.workspaceId,
221
+ sessionId: input.sessionId,
222
+ sourceKind: "assistant_terminal",
223
+ sourceId: `${input.turnId}:${input.terminalEvent.type}`,
224
+ turnId: input.turnId,
225
+ channel: "speakable",
226
+ text: projected.text,
227
+ payload: {
228
+ route: "session_context",
229
+ status: input.turnStatus,
230
+ terminalEventType: input.terminalEvent.type,
231
+ terminal: boundedPayload(input.terminalEvent.payload),
232
+ ...(call ? { priorRealtimeId: call.realtimeId } : {}),
233
+ },
234
+ now,
235
+ });
236
+ return mirrored ? { entry: mapEntry(mirrored.entry), replay: mirrored.replay } : null;
237
+ }
238
+ if (!call || !mode) {
239
+ throw new Error(`Delegation turn ${input.turnId} lost its active realtime route`);
240
+ }
241
+ if (!call.delegationItemId) {
242
+ throw new Error(`Delegation turn ${input.turnId} has no provider item identity`);
243
+ }
244
+ const operationId = deterministicTerminalOperationId(input.turnId);
245
+ const [existing] = await db
246
+ .select()
247
+ .from(schema.sessionRealtimeEntries)
248
+ .where(
249
+ and(
250
+ eq(schema.sessionRealtimeEntries.workspaceId, input.workspaceId),
251
+ eq(schema.sessionRealtimeEntries.sessionId, input.sessionId),
252
+ eq(schema.sessionRealtimeEntries.turnId, input.turnId),
253
+ eq(schema.sessionRealtimeEntries.direction, "provider_out"),
254
+ inArray(schema.sessionRealtimeEntries.kind, ["delegation_result", "error"]),
255
+ ),
256
+ )
257
+ .limit(1);
258
+ if (existing) {
259
+ if (
260
+ existing.accountId !== input.accountId ||
261
+ existing.realtimeId !== call.realtimeId ||
262
+ existing.operationId !== operationId ||
263
+ existing.kind !== projected.kind ||
264
+ existing.delegationItemId !== call.delegationItemId
265
+ ) {
266
+ throw new Error(`Delegation turn ${input.turnId} has conflicting terminal projection`);
267
+ }
268
+ return { entry: mapEntry(existing), replay: true };
269
+ }
270
+
271
+ const [sequenceRow] = await db
272
+ .select({
273
+ next: sql<number>`coalesce(max(${schema.sessionRealtimeEntries.sequence}), 0) + 1`,
274
+ })
275
+ .from(schema.sessionRealtimeEntries)
276
+ .where(eq(schema.sessionRealtimeEntries.realtimeId, call.realtimeId));
277
+ const payload = boundedPayload({
278
+ ...projected.payload,
279
+ route: "delegation_context",
280
+ channel: "speakable",
281
+ callOperationId: call.operationId,
282
+ callLedgerEntryId: call.id,
283
+ });
284
+ const [entry] = await db
285
+ .insert(schema.sessionRealtimeEntries)
286
+ .values({
287
+ accountId: input.accountId,
288
+ workspaceId: input.workspaceId,
289
+ sessionId: input.sessionId,
290
+ realtimeId: call.realtimeId,
291
+ operationId,
292
+ connectionEpoch: mode.connectionEpoch,
293
+ sequence: Number(sequenceRow?.next ?? 1),
294
+ direction: "provider_out",
295
+ kind: projected.kind,
296
+ delegationItemId: call.delegationItemId,
297
+ turnId: input.turnId,
298
+ text: projected.text,
299
+ payload,
300
+ createdAt: now,
301
+ updatedAt: now,
302
+ })
303
+ .returning();
304
+ if (!entry) throw new Error("Failed to project realtime delegation terminal result");
305
+ return { entry: mapEntry(entry), replay: false };
306
+ }