@opengeni/db 5.0.1 → 6.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 (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -0,0 +1,348 @@
1
+ import { createHash } from "node:crypto";
2
+ import { and, asc, eq, sql, type SQL } from "drizzle-orm";
3
+ import { z } from "zod";
4
+ import type {
5
+ SessionMessageSearchRequest,
6
+ SessionMessageSearchMatch,
7
+ SessionMessageSearchResponse,
8
+ } from "@opengeni/contracts";
9
+ import type { Database } from "./database";
10
+ import * as schema from "./schema";
11
+ import { readSessionMessageSliceInScope } from "./session-event-slices";
12
+ import { fromPostgresLosslessJson, LOSSLESS_JSON_STRING_PREFIX } from "./lossless-json";
13
+
14
+ const Position = z
15
+ .object({
16
+ sessionId: z.string().uuid(),
17
+ sequence: z.number().int().min(1).max(2_147_483_647),
18
+ offset: z.number().int().min(0).max(2_147_483_647),
19
+ utf16Offset: z.number().int().min(0).max(4_294_967_294),
20
+ minimumMatchOffset: z.number().int().min(0).max(4_294_967_294),
21
+ matched: z.boolean(),
22
+ done: z.boolean(),
23
+ skipSession: z.boolean().optional(),
24
+ })
25
+ .strict();
26
+ const Cursor = z
27
+ .object({
28
+ version: z.literal(1),
29
+ binding: z.string().regex(/^[a-f0-9]{64}$/),
30
+ position: Position,
31
+ scanned: z.number().int().nonnegative().safe(),
32
+ matched: z.number().int().nonnegative().safe(),
33
+ occurrences: z.number().int().nonnegative().safe(),
34
+ })
35
+ .strict();
36
+ type Position = z.infer<typeof Position>;
37
+
38
+ export class SessionMessageSearchCursorError extends Error {
39
+ constructor() {
40
+ super("Invalid session message search cursor or changed search scope");
41
+ }
42
+ }
43
+
44
+ /** ECMAScript Unicode simple case folding, not locale-sensitive lowercasing.
45
+ * Escaping every regexp metacharacter makes %, _, backslash, etc. literal too.
46
+ * RegExp indices refer to original UTF-16 text (no lowercased-offset drift).
47
+ */
48
+ export function sessionMessageLiteralPattern(query: string): RegExp {
49
+ return new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "iu");
50
+ }
51
+
52
+ export function sessionMessageSearchSnippet(text: string, start: number, length: number) {
53
+ let left = Math.max(0, start - 120);
54
+ let right = Math.min(text.length, start + length + 120);
55
+ if (left > 0 && /[\uDC00-\uDFFF]/.test(text[left]!)) left--;
56
+ if (right < text.length && /[\uDC00-\uDFFF]/.test(text[right]!)) right++;
57
+ return {
58
+ text: text.slice(left, right),
59
+ matchStart: start - left,
60
+ matchEnd: start + length - left,
61
+ };
62
+ }
63
+
64
+ /** Internal executor: caller holds subject RLS, session-tenancy and membership
65
+ * fences and supplies the ordinary session list filters. Never call as auth.
66
+ * Work/transfer is bounded by 32 scalar windows per request. No history cutoff:
67
+ * an unfinished scan ALWAYS returns an advancing cursor, even with zero hits.
68
+ */
69
+ export async function scanSessionMessages(
70
+ db: Database,
71
+ workspaceId: string,
72
+ request: SessionMessageSearchRequest,
73
+ filters: SQL[],
74
+ authorityIdentity: unknown,
75
+ signal?: AbortSignal,
76
+ ): Promise<SessionMessageSearchResponse> {
77
+ signal?.throwIfAborted();
78
+ const started = performance.now();
79
+ const binding = createHash("sha256")
80
+ .update(
81
+ JSON.stringify([
82
+ workspaceId,
83
+ authorityIdentity,
84
+ request.query,
85
+ request.sessionId ?? null,
86
+ request.archiveStatus ?? "active",
87
+ ...(request.groupBy ? [{ groupBy: request.groupBy }] : []),
88
+ ]),
89
+ )
90
+ .digest("hex");
91
+ let position: Position | null = null;
92
+ let scanned = 0;
93
+ let matched = 0;
94
+ let occurrences = 0;
95
+ if (request.cursor) {
96
+ try {
97
+ const cursor = Cursor.parse(
98
+ JSON.parse(Buffer.from(request.cursor, "base64url").toString("utf8")),
99
+ );
100
+ if (
101
+ cursor.binding !== binding ||
102
+ cursor.matched > cursor.scanned ||
103
+ cursor.matched > cursor.occurrences ||
104
+ (cursor.position.skipSession &&
105
+ (request.groupBy !== "session" || !cursor.position.done || !cursor.position.matched))
106
+ )
107
+ throw new Error();
108
+ position = cursor.position;
109
+ scanned = cursor.scanned;
110
+ matched = cursor.matched;
111
+ occurrences = cursor.occurrences;
112
+ } catch {
113
+ throw new SessionMessageSearchCursorError();
114
+ }
115
+ }
116
+ const pattern = new RegExp(sessionMessageLiteralPattern(request.query), "giu");
117
+ const matches: SessionMessageSearchMatch[] = [];
118
+ let exhausted = false;
119
+ const e = schema.sessionEvents;
120
+ // One bounded batch for ordinary complete messages. Large scalar continuations
121
+ // alone use the existing lossless slice reader. There is no per-message SQL
122
+ // round trip for the common small-message/no-hit path.
123
+ {
124
+ const boundary = position
125
+ ? position.skipSession
126
+ ? sql`${e.sessionId} > ${position.sessionId}::uuid`
127
+ : sql`(${e.sessionId} > ${position.sessionId}::uuid or (${e.sessionId} = ${position.sessionId}::uuid and ${position.done ? sql`${e.sequence} > ${position.sequence}` : sql`${e.sequence} >= ${position.sequence}`}))`
128
+ : sql`true`;
129
+ // Correlate the exact durable message identity, not turn status or latest
130
+ // model context. The latest full completion for a provider message wins,
131
+ // so an earlier shorter completion cannot hide later retained text. Same-
132
+ // text distinct provider messages remain distinct; the id-less final
133
+ // settlement copy is suppressed against its earlier complete text.
134
+ // Suppression requires the competing copy to retain searchable string text
135
+ // itself: a later text-less completion (failed/empty retry sharing the
136
+ // provider message id) must never hide the earlier complete text.
137
+ const identities = await db
138
+ .select({
139
+ sessionId: e.sessionId,
140
+ eventId: e.id,
141
+ sequence: e.sequence,
142
+ turnId: e.turnId,
143
+ type: sql<"user.message" | "agent.message.completed">`${e.type}`,
144
+ sessionTitle: sql<
145
+ string | null
146
+ >`case when octet_length(${schema.sessions.title}) <= 2048 then ${schema.sessions.title} else null end`,
147
+ messageId: sql<unknown>`case when octet_length((${e.payload}->'messageId')::text) <= 2048 then ${e.payload}->'messageId' else null end`,
148
+ codec: e.payloadCodecVersion,
149
+ smallText: sql<unknown>`case when octet_length(${e.payload}->>'text') <= 8192 then ${e.payload}->'text' else null end`,
150
+ })
151
+ .from(e)
152
+ .innerJoin(
153
+ schema.sessions,
154
+ and(eq(schema.sessions.workspaceId, e.workspaceId), eq(schema.sessions.id, e.sessionId)),
155
+ )
156
+ .where(
157
+ and(
158
+ eq(e.workspaceId, workspaceId),
159
+ ...filters,
160
+ boundary,
161
+ request.sessionId ? eq(e.sessionId, request.sessionId) : sql`true`,
162
+ sql`${e.type} in ('user.message', 'agent.message.completed')`,
163
+ sql`(${e.turnAssociation} is null or ${e.turnAssociation} = 'current')`,
164
+ sql`${e.duplicateOfEventId} is null`,
165
+ sql`jsonb_typeof(${e.payload}->'text') = 'string'`,
166
+ sql`(${e.type} <> 'user.message' or not exists (
167
+ select 1 from ${schema.sessionTurns} unclaimed
168
+ where unclaimed.workspace_id = ${workspaceId} and unclaimed.session_id = ${e.sessionId}
169
+ and unclaimed.trigger_event_id = ${e.id} and unclaimed.source in ('user', 'api')
170
+ and unclaimed.started_at is null))`,
171
+ sql`(${e.type} <> 'agent.message.completed' or not exists (
172
+ select 1 from ${schema.sessionEvents} other_message
173
+ where other_message.workspace_id = ${workspaceId} and other_message.session_id = ${e.sessionId}
174
+ and other_message.turn_id = ${e.turnId}
175
+ and other_message.type = 'agent.message.completed' and other_message.duplicate_of_event_id is null
176
+ and (other_message.turn_association is null or other_message.turn_association = 'current')
177
+ and jsonb_typeof(other_message.payload->'text') = 'string'
178
+ and ((jsonb_typeof(${e.payload}->'messageId') = 'string'
179
+ and other_message.sequence > ${e.sequence}
180
+ and other_message.payload->'messageId' = ${e.payload}->'messageId'
181
+ and (other_message.payload_codec_version is not distinct from ${e.payloadCodecVersion}
182
+ or left(${e.payload}->>'messageId', ${LOSSLESS_JSON_STRING_PREFIX.length}) <> ${LOSSLESS_JSON_STRING_PREFIX}))
183
+ or (coalesce(${e.payload}->>'messageId', '') = ''
184
+ and other_message.sequence < ${e.sequence}
185
+ and other_message.payload->'text' = ${e.payload}->'text'
186
+ and (other_message.payload_codec_version is not distinct from ${e.payloadCodecVersion}
187
+ or left(${e.payload}->>'text', ${LOSSLESS_JSON_STRING_PREFIX.length}) <> ${LOSSLESS_JSON_STRING_PREFIX})))))`,
188
+ ),
189
+ )
190
+ .orderBy(asc(e.sessionId), asc(e.sequence))
191
+ .limit(33);
192
+ let identityIndex = 0;
193
+ for (let window = 0; window < 32 && matches.length < (request.limit ?? 20); window++) {
194
+ signal?.throwIfAborted();
195
+ if (window > 0 && performance.now() - started >= 1_500) break;
196
+ const identity = identities[identityIndex];
197
+ if (!identity) {
198
+ // Grouping can drain a full candidate batch in fewer than 32 windows.
199
+ // A full batch is not proof that later sessions do not exist.
200
+ exhausted = identities.length < 33;
201
+ break;
202
+ }
203
+ const continuing =
204
+ position &&
205
+ !position.done &&
206
+ position.sessionId === identity.sessionId &&
207
+ position.sequence === identity.sequence;
208
+ if (!continuing) {
209
+ scanned++;
210
+ position = {
211
+ sessionId: identity.sessionId,
212
+ sequence: identity.sequence,
213
+ offset: 0,
214
+ utf16Offset: 0,
215
+ minimumMatchOffset: 0,
216
+ matched: false,
217
+ done: false,
218
+ };
219
+ }
220
+ const offset = position!.offset;
221
+ const utf16Offset = position!.utf16Offset;
222
+ const smallText = fromPostgresLosslessJson(identity.smallText, identity.codec);
223
+ // Amortize full-source detoasting/codec validation over adjacent windows,
224
+ // not a larger request budget. A four-window read consumes four of the
225
+ // original 32 slots, even if a match/short source ends it early.
226
+ const scalarWindows = typeof smallText === "string" ? 1 : Math.min(4, 32 - window);
227
+ const slice =
228
+ typeof smallText === "string"
229
+ ? {
230
+ offset: 0,
231
+ total: smallText.length,
232
+ unit: "utf16" as const,
233
+ text: smallText,
234
+ omitted: false,
235
+ }
236
+ : await readSessionMessageSliceInScope(
237
+ db,
238
+ workspaceId,
239
+ identity.sessionId,
240
+ {
241
+ id: identity.eventId,
242
+ sequence: identity.sequence,
243
+ type: identity.type,
244
+ turnId: identity.turnId,
245
+ turnAssociation: null,
246
+ duplicateOfEventId: null,
247
+ },
248
+ offset,
249
+ scalarWindows,
250
+ );
251
+ window += scalarWindows - 1;
252
+ if (!slice || slice.omitted) {
253
+ // Deletion/visibility changes never turn into a fabricated message.
254
+ position = { ...position!, done: true };
255
+ identityIndex++;
256
+ continue;
257
+ }
258
+ pattern.lastIndex = Math.max(0, position!.minimumMatchOffset - utf16Offset);
259
+ let found: RegExpExecArray | null;
260
+ const messageId = fromPostgresLosslessJson(identity.messageId, identity.codec);
261
+ while (matches.length < (request.limit ?? 20) && (found = pattern.exec(slice.text))) {
262
+ matches.push({
263
+ sessionId: identity.sessionId,
264
+ sessionTitle: identity.sessionTitle,
265
+ eventId: identity.eventId,
266
+ sequence: identity.sequence,
267
+ turnId: identity.turnId,
268
+ role: identity.type === "user.message" ? "user" : "assistant",
269
+ messageId: typeof messageId === "string" ? messageId : null,
270
+ messageMatchOffset: utf16Offset + found.index,
271
+ snippet: sessionMessageSearchSnippet(slice.text, found.index, found[0].length),
272
+ });
273
+ if (!position!.matched) matched++;
274
+ occurrences++;
275
+ position = {
276
+ ...position!,
277
+ matched: true,
278
+ minimumMatchOffset: utf16Offset + found.index + found[0].length,
279
+ };
280
+ if (request.groupBy === "session") {
281
+ position = { ...position, done: true, skipSession: true };
282
+ break;
283
+ }
284
+ }
285
+ if (position!.skipSession) {
286
+ // Skip both buffered messages and all unbuffered history of this
287
+ // matching session. The next cursor carries a strict session boundary,
288
+ // so a prolific message cannot flood the workspace picker with pages.
289
+ do {
290
+ identityIndex++;
291
+ } while (identities[identityIndex]?.sessionId === identity.sessionId);
292
+ continue;
293
+ }
294
+ const units = slice.unit === "utf16" ? slice.text.length : Array.from(slice.text).length;
295
+ if (matches.length >= (request.limit ?? 20)) {
296
+ if (
297
+ offset + units >= slice.total &&
298
+ position!.minimumMatchOffset >= utf16Offset + slice.text.length
299
+ ) {
300
+ position = { ...position!, done: true };
301
+ identityIndex++;
302
+ }
303
+ break;
304
+ }
305
+ if (offset + units >= slice.total) {
306
+ position = { ...position!, done: true };
307
+ identityIndex++;
308
+ continue;
309
+ }
310
+ // Overlap enough complete scalars for every literal crossing a window.
311
+ // Retain additional preceding snippet context; resume offset is absolute
312
+ // UTF-16 so supplementary characters and lossless code units cannot drift.
313
+ const overlap = request.query.length * 2 + 122;
314
+ let advance = Math.max(1, units - overlap);
315
+ let prefix =
316
+ slice.unit === "utf16"
317
+ ? slice.text.slice(0, advance)
318
+ : Array.from(slice.text).slice(0, advance).join("");
319
+ if (slice.unit === "utf16" && /[\uD800-\uDBFF]/.test(prefix.at(-1)!)) {
320
+ advance--;
321
+ prefix = prefix.slice(0, -1);
322
+ }
323
+ position = {
324
+ ...position!,
325
+ offset: offset + advance,
326
+ utf16Offset: utf16Offset + prefix.length,
327
+ minimumMatchOffset: Math.max(position!.minimumMatchOffset, utf16Offset + prefix.length),
328
+ done: false,
329
+ };
330
+ }
331
+ if (identityIndex >= identities.length && identities.length < 33) exhausted = true;
332
+ }
333
+ signal?.throwIfAborted();
334
+ return {
335
+ matches,
336
+ hasMore: !exhausted,
337
+ countIsExact: exhausted,
338
+ scannedMessages: scanned,
339
+ matchedMessageCount: matched,
340
+ matchedOccurrenceCount: occurrences,
341
+ nextCursor:
342
+ exhausted || !position
343
+ ? null
344
+ : Buffer.from(
345
+ JSON.stringify({ version: 1, binding, position, scanned, matched, occurrences }),
346
+ ).toString("base64url"),
347
+ };
348
+ }
@@ -1,4 +1,6 @@
1
+ import { acceptSessionFileAttachments } from "./session-file-attachments";
1
2
  import { withLatestStartedSessionPolicy } from "./session-execution-policy";
3
+ import { parseAcceptedMcpAccountBindings } from "./mcp-account-bindings";
2
4
  import {
3
5
  WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1,
4
6
  XaiProviderAccountAuthoritySnapshotV1,
@@ -14,6 +16,7 @@ import {
14
16
  stableJson,
15
17
  turnExecutionPolicyAuditMetadata,
16
18
  type McpPersonalConnectionDelegation,
19
+ type McpConnectionAccountBinding,
17
20
  type DraftTimelineAnnotation,
18
21
  type LatencyMode,
19
22
  type ReasoningEffort,
@@ -31,7 +34,12 @@ import {
31
34
  type PersonalResourceAttachmentIntent,
32
35
  } from "@opengeni/contracts";
33
36
  import { and, asc, desc, eq, inArray, sql } from "drizzle-orm";
34
- import { setSubjectRlsContext, type Database, type SessionActivityDatabase } from "./database";
37
+ import {
38
+ withSessionRlsActorContext,
39
+ setSubjectRlsContext,
40
+ type Database,
41
+ type SessionActivityDatabase,
42
+ } from "./database";
35
43
  import {
36
44
  fromPostgresLosslessJson,
37
45
  fromPostgresLosslessText,
@@ -347,14 +355,15 @@ async function personalConnectionDelegationsForAgentActor(
347
355
  db: Database,
348
356
  workspaceId: string,
349
357
  actor: Extract<SessionCommandActor, { type: "agent_attempt" }>,
350
- targetSessionId: string,
351
358
  ): Promise<{
352
359
  delegations: McpPersonalConnectionDelegation[];
360
+ mcpAccountBindings: McpConnectionAccountBinding[] | null;
353
361
  connectionAuthoritySubjectId: string | null;
354
362
  }> {
355
363
  const [row] = await db
356
364
  .select({
357
365
  delegations: schema.sessionTurns.personalConnectionDelegations,
366
+ mcpAccountBindings: schema.sessionTurns.mcpAccountBindings,
358
367
  initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
359
368
  })
360
369
  .from(schema.sessionTurns)
@@ -377,22 +386,11 @@ async function personalConnectionDelegationsForAgentActor(
377
386
  `Agent authority turn has malformed personal MCP delegation state: ${actor.sessionId}/${actor.turnId}`,
378
387
  );
379
388
  }
380
- // Agent messages and Agent Steer create new accepted work. A once grant is
381
- // already bound to the caller turn, while a session grant may be projected
382
- // only into another turn of that exact session. Always grants can cross the
383
- // direct parent/child boundary subject to the live admission fences.
384
- const delegations = parsed.data
385
- .filter((delegation) => {
386
- const authority = delegation.userDelegation;
387
- if (!authority) return true;
388
- if (authority.mode === "once") return false;
389
- if (authority.mode === "session") return authority.sessionId === targetSessionId;
390
- return true;
391
- })
392
- .map((delegation) => ({ ...delegation }));
393
- const activated = delegations.filter((delegation) => delegation.userDelegation);
394
- if (activated.length === 0) {
395
- return { delegations, connectionAuthoritySubjectId: null };
389
+ // New agent work inherits the exact originating turn's sender and accounts.
390
+ const delegations = parsed.data.map((delegation) => ({ ...delegation }));
391
+ const mcpAccountBindings = parseAcceptedMcpAccountBindings(row.mcpAccountBindings);
392
+ if (delegations.length === 0) {
393
+ return { delegations, mcpAccountBindings, connectionAuthoritySubjectId: null };
396
394
  }
397
395
  const connectionAuthoritySubjectId = row.initiatingHumanSubjectId;
398
396
  if (!connectionAuthoritySubjectId) {
@@ -400,14 +398,14 @@ async function personalConnectionDelegationsForAgentActor(
400
398
  `Agent authority turn lost its causal human: ${actor.sessionId}/${actor.turnId}`,
401
399
  );
402
400
  }
403
- for (const delegation of activated) {
401
+ for (const delegation of delegations) {
404
402
  if (delegation.ownerSubjectId !== connectionAuthoritySubjectId) {
405
403
  throw new SessionControlInvariantError(
406
404
  `Agent authority turn causal human does not own retained connection authority: ${actor.sessionId}/${actor.turnId}`,
407
405
  );
408
406
  }
409
407
  }
410
- return { delegations, connectionAuthoritySubjectId };
408
+ return { delegations, mcpAccountBindings, connectionAuthoritySubjectId };
411
409
  }
412
410
 
413
411
  async function xaiAuthorityForAgentActor(
@@ -1759,6 +1757,7 @@ export async function submitHumanPromptInTransaction(
1759
1757
  /** Record the admitted run's durable usage fact in this transaction. */
1760
1758
  recordAgentRunUsage?: boolean;
1761
1759
  personalConnectionDelegations?: McpPersonalConnectionDelegation[];
1760
+ mcpAccountBindings?: McpConnectionAccountBinding[] | null;
1762
1761
  personalResourceAttachment?: PersonalResourceAttachmentIntent;
1763
1762
  mcpCredentialUpdates?: Array<{
1764
1763
  id: string;
@@ -1769,11 +1768,7 @@ export async function submitHumanPromptInTransaction(
1769
1768
  beforeFreshPromptCommit?: (tx: Database) => Promise<void>;
1770
1769
  /** Backend-only authority capture, atomic with the newly accepted turn. */
1771
1770
  captureTurnAuthority?: (tx: Database, turnId: string) => Promise<void>;
1772
- selectedHostMcpDelegations?: Array<{
1773
- serverId: string;
1774
- delegationId: string;
1775
- generation: number;
1776
- }>;
1771
+
1777
1772
  /** Request-scoped callers bound the control prefix wait; lifecycle callers omit it. */
1778
1773
  controlLockTimeoutMs?: number;
1779
1774
  },
@@ -1829,10 +1824,9 @@ export async function submitHumanPromptInTransaction(
1829
1824
  mirrorToRealtime: input.mirrorToRealtime ?? true,
1830
1825
  mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
1831
1826
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
1827
+ mcpAccountBindings: input.mcpAccountBindings ?? null,
1832
1828
  personalResourceAttachment: input.personalResourceAttachment ?? null,
1833
- ...(input.selectedHostMcpDelegations?.length
1834
- ? { selectedHostMcpDelegations: input.selectedHostMcpDelegations }
1835
- : {}),
1829
+
1836
1830
  ...(input.actor.type === "service"
1837
1831
  ? {
1838
1832
  serviceInitiator: {
@@ -1923,6 +1917,7 @@ export async function submitHumanPromptInTransaction(
1923
1917
  !isQueueEditSubmission &&
1924
1918
  before.state === "active" &&
1925
1919
  session.status === "requires_action" &&
1920
+ !session.admissionBlock &&
1926
1921
  session.activeTurnId !== null
1927
1922
  ? "steer"
1928
1923
  : input.delivery;
@@ -2082,9 +2077,7 @@ export async function submitHumanPromptInTransaction(
2082
2077
  : (frozenInitiator.initiatingHumanSubjectId ??
2083
2078
  (frozenInitiator.initiator.kind === "subject" ? frozenInitiator.initiator.subjectId : null));
2084
2079
  if (
2085
- (input.personalConnectionDelegations ?? []).some(
2086
- (delegation) => delegation.userDelegation !== undefined,
2087
- ) ||
2080
+ (input.personalConnectionDelegations ?? []).length > 0 ||
2088
2081
  input.personalResourceAttachment !== undefined
2089
2082
  ) {
2090
2083
  if (!acceptedInitiatingHumanSubjectId) {
@@ -2194,6 +2187,9 @@ export async function submitHumanPromptInTransaction(
2194
2187
  personalConnectionDelegations: editedSourceTurn
2195
2188
  ? editedSourceTurn.personalConnectionDelegations
2196
2189
  : (input.personalConnectionDelegations ?? []),
2190
+ mcpAccountBindings: editedSourceTurn
2191
+ ? editedSourceTurn.mcpAccountBindings
2192
+ : parseAcceptedMcpAccountBindings(input.mcpAccountBindings),
2197
2193
  xaiProviderAccountAuthoritySnapshot,
2198
2194
  createdAt: now,
2199
2195
  updatedAt: now,
@@ -2398,7 +2394,9 @@ export async function submitHumanPromptInTransaction(
2398
2394
  );
2399
2395
  const noCurrentAfter =
2400
2396
  effectiveDelivery === "steer" ? liveCurrentTurnId === null : !session.activeTurnId;
2401
- const nextStatus = noCurrentAfter ? "queued" : session.status;
2397
+ const nextStatus = noCurrentAfter
2398
+ ? "queued"
2399
+ : (session.admissionBlock?.previousStatus ?? session.status);
2402
2400
  if (nextStatus !== session.status) {
2403
2401
  eventValues.push({
2404
2402
  accountId: input.accountId,
@@ -2414,6 +2412,24 @@ export async function submitHumanPromptInTransaction(
2414
2412
  .insert(schema.sessionEvents)
2415
2413
  .values(withLosslessContentWriteVersion(eventValues, "payload", "payloadCodecVersion"))
2416
2414
  .returning();
2415
+ // Shared acceptance boundary covers ordinary Send/Steer and realtime. Only the
2416
+ // verified human command can share original uploads; inherited agent identity
2417
+ // is deliberately insufficient. Events must exist before grant provenance is checked.
2418
+ if (input.actor.type === "human" && input.actor.subjectId === input.subjectId) {
2419
+ await withSessionRlsActorContext(
2420
+ { subjectId: input.subjectId, privateFileOwnerSubjectId: input.subjectId },
2421
+ () =>
2422
+ acceptSessionFileAttachments(db, {
2423
+ accountId: input.accountId,
2424
+ workspaceId: input.workspaceId,
2425
+ sessionId: input.sessionId,
2426
+ turnId: turn.id,
2427
+ subjectId: input.subjectId,
2428
+ resources: [...ResourceRef.array().parse(session.resources), ...input.resources],
2429
+ }),
2430
+ );
2431
+ }
2432
+
2417
2433
  if (input.actor.type === "human" && input.mirrorToRealtime !== false) {
2418
2434
  await mirrorSessionRealtimeContextInTransaction(db, {
2419
2435
  accountId: input.accountId,
@@ -2445,6 +2461,7 @@ export async function submitHumanPromptInTransaction(
2445
2461
  tools: session.tools,
2446
2462
  activeTurnId: effectiveDelivery === "steer" ? liveCurrentTurnId : session.activeTurnId,
2447
2463
  status: nextStatus,
2464
+ admissionBlock: null,
2448
2465
  queueVersion,
2449
2466
  queueHeadPosition: 0,
2450
2467
  queueTailPosition: ordered.length,
@@ -2647,7 +2664,6 @@ export async function sendAgentMessageInTransaction(
2647
2664
  db,
2648
2665
  input.workspaceId,
2649
2666
  input.actor,
2650
- input.targetSessionId,
2651
2667
  );
2652
2668
  const personalConnectionDelegations = inheritedConnectionAuthority.delegations;
2653
2669
  const xaiAuthority = await xaiAuthorityForAgentActor(db, input.workspaceId, input.actor);
@@ -2701,6 +2717,7 @@ export async function sendAgentMessageInTransaction(
2701
2717
  ...(xaiAuthority.subjectId ? { xaiAuthoritySubjectId: xaiAuthority.subjectId } : {}),
2702
2718
  },
2703
2719
  personalConnectionDelegations,
2720
+ mcpAccountBindings: inheritedConnectionAuthority.mcpAccountBindings,
2704
2721
  xaiProviderAccountAuthoritySnapshot: xaiAuthority.snapshot,
2705
2722
  state: "pending",
2706
2723
  },
@@ -2760,7 +2777,11 @@ export async function sendAgentMessageInTransaction(
2760
2777
  }
2761
2778
  const eventIds = insertedEvents.map((event) => event.id);
2762
2779
  const workflowId = session.temporalWorkflowId ?? `session-${session.id}`;
2763
- const runnable = !realtimeActive && session.activeTurnId === null && effective.state === "active";
2780
+ const runnable =
2781
+ !session.admissionBlock &&
2782
+ !realtimeActive &&
2783
+ session.activeTurnId === null &&
2784
+ effective.state === "active";
2764
2785
  const wake = runnable
2765
2786
  ? await registerInternalUpdateWakeInTransaction(db, {
2766
2787
  accountId: input.accountId,
@@ -2897,7 +2918,6 @@ export async function steerAgentSessionInTransaction(
2897
2918
  db,
2898
2919
  input.workspaceId,
2899
2920
  input.actor,
2900
- input.targetSessionId,
2901
2921
  );
2902
2922
  const personalConnectionDelegations = inheritedConnectionAuthority.delegations;
2903
2923
  const xaiAuthority = await xaiAuthorityForAgentActor(db, input.workspaceId, input.actor);
@@ -2986,6 +3006,7 @@ export async function steerAgentSessionInTransaction(
2986
3006
  ...(xaiAuthority.subjectId ? { xaiAuthoritySubjectId: xaiAuthority.subjectId } : {}),
2987
3007
  },
2988
3008
  personalConnectionDelegations,
3009
+ mcpAccountBindings: inheritedConnectionAuthority.mcpAccountBindings,
2989
3010
  xaiProviderAccountAuthoritySnapshot: xaiAuthority.snapshot,
2990
3011
  state: "pending",
2991
3012
  },
@@ -3095,7 +3116,10 @@ export async function steerAgentSessionInTransaction(
3095
3116
  .update(schema.sessions)
3096
3117
  .set({
3097
3118
  activeTurnId: supersession.liveCurrentTurnId,
3098
- status: supersession.liveCurrentTurnId ? session.status : "queued",
3119
+ status: supersession.liveCurrentTurnId
3120
+ ? (session.admissionBlock?.previousStatus ?? session.status)
3121
+ : "queued",
3122
+ admissionBlock: null,
3099
3123
  lastSequence: sequence,
3100
3124
  updatedAt: now,
3101
3125
  })