@opengeni/db 1.0.1 → 3.1.0-canary.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 (265) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-LTG3S6ZY.js → chunk-EULAN2NP.js} +2 -2
  7. package/dist/{chunk-CUPM3FGB.js → chunk-NJVZPWXY.js} +3 -3
  8. package/dist/{chunk-4SR4HKKA.js → chunk-NMUBV7Z5.js} +19 -11
  9. package/dist/chunk-NMUBV7Z5.js.map +1 -0
  10. package/dist/chunk-OF5OBB76.js +4457 -0
  11. package/dist/chunk-OF5OBB76.js.map +1 -0
  12. package/dist/{chunk-6ZYKCRFO.js → chunk-PR2WMLOQ.js} +7580 -4417
  13. package/dist/chunk-PR2WMLOQ.js.map +1 -0
  14. package/dist/{chunk-ZWREYEWA.js → chunk-SCPUJS53.js} +41 -4
  15. package/dist/chunk-SCPUJS53.js.map +1 -0
  16. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  17. package/dist/chunk-SIBYBA2D.js.map +1 -0
  18. package/dist/chunk-TIKF3KTI.js +383 -0
  19. package/dist/chunk-TIKF3KTI.js.map +1 -0
  20. package/dist/chunk-ZZ5LJEFX.js +516 -0
  21. package/dist/chunk-ZZ5LJEFX.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +41 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +19 -0
  35. package/dist/editable-artifact-durable-export.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1595 -81
  43. package/dist/index.js +21571 -8126
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +22 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +41 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +153 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +1 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/package.json +10 -6
  189. package/src/attached-browser-devices.ts +233 -5
  190. package/src/automations.ts +911 -0
  191. package/src/browser-sessions.ts +10 -0
  192. package/src/capability-integrations.ts +175 -1
  193. package/src/child-lifecycle-notices.ts +241 -0
  194. package/src/codemode-operations.ts +1 -35
  195. package/src/company-brain-context-selection-schema.ts +100 -0
  196. package/src/company-brain-context-selection.ts +359 -0
  197. package/src/company-brain-governed-writes.ts +1170 -0
  198. package/src/company-brain.ts +309 -0
  199. package/src/company-profile-agent-admin.ts +225 -0
  200. package/src/company-profile-schema.ts +121 -1
  201. package/src/company-profile.ts +21 -0
  202. package/src/computer-sessions.ts +10 -1
  203. package/src/connection-authority.ts +290 -0
  204. package/src/connection-scopes.ts +20 -0
  205. package/src/connection-tenancy-backfill.ts +81 -0
  206. package/src/connection-token-resolver.ts +263 -31
  207. package/src/database.ts +75 -2
  208. package/src/editable-artifacts-schema.ts +10 -5
  209. package/src/editable-artifacts.ts +21 -11
  210. package/src/governed-learning-activation-schema.ts +121 -0
  211. package/src/governed-learning-activation.ts +588 -0
  212. package/src/governed-learning-evaluator-schema.ts +72 -0
  213. package/src/governed-learning-evaluator.ts +217 -0
  214. package/src/index.ts +11651 -1548
  215. package/src/insights.ts +107 -0
  216. package/src/integration-bindings.ts +88 -6
  217. package/src/integration-facets.ts +287 -84
  218. package/src/interaction-placement-loss.ts +234 -0
  219. package/src/managed-human-provisioning.ts +116 -0
  220. package/src/memory-domain.ts +27 -32
  221. package/src/migrate.ts +77 -1
  222. package/src/new-session-drafts.ts +229 -35
  223. package/src/organization-membership-backfill.ts +735 -0
  224. package/src/organization-membership-lifecycle.ts +985 -0
  225. package/src/pr-review.ts +890 -0
  226. package/src/preference-registry.ts +31 -1
  227. package/src/provision-roles.ts +877 -6
  228. package/src/runtime-posture-cli.ts +2 -0
  229. package/src/runtime-posture.ts +1291 -76
  230. package/src/schema.ts +3303 -428
  231. package/src/scoped-knowledge-schema.ts +16 -8
  232. package/src/scoped-knowledge.ts +1 -1
  233. package/src/session-background-commands.ts +759 -0
  234. package/src/session-control.ts +640 -74
  235. package/src/session-goal-pacing.ts +126 -0
  236. package/src/session-queue-commands.ts +437 -84
  237. package/src/session-realtime-context.ts +3 -3
  238. package/src/session-realtime-ledger.ts +22 -11
  239. package/src/session-realtime-mirror.ts +1 -1
  240. package/src/session-tenancy.ts +403 -49
  241. package/src/session-tool-call-settlement.ts +38 -29
  242. package/src/slack-routing-personal-workspace.ts +67 -0
  243. package/src/slack-routing.ts +469 -0
  244. package/src/slack-task-policy-schema.ts +5 -0
  245. package/src/slack-task-policy.ts +18 -0
  246. package/src/slack-user-link-access.ts +17 -1
  247. package/src/task-notes-schema.ts +74 -0
  248. package/src/task-notes.ts +124 -2
  249. package/src/turn-initiator.ts +8 -0
  250. package/src/user-resource-authority.ts +377 -0
  251. package/src/workspace-authority.ts +160 -0
  252. package/src/workspace-instruction-policies-schema.ts +53 -1
  253. package/src/workspace-instruction-policies.ts +528 -77
  254. package/src/workspace-learning-policy.ts +21 -5
  255. package/src/workspace-membership-permissions.ts +9 -0
  256. package/dist/chunk-4SR4HKKA.js.map +0 -1
  257. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  258. package/dist/chunk-NXAFLCQX.js +0 -2468
  259. package/dist/chunk-NXAFLCQX.js.map +0 -1
  260. package/dist/chunk-SK7YYHBI.js.map +0 -1
  261. package/dist/chunk-WD5ROSLL.js +0 -117
  262. package/dist/chunk-WD5ROSLL.js.map +0 -1
  263. package/dist/chunk-ZWREYEWA.js.map +0 -1
  264. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-EULAN2NP.js.map} +0 -0
  265. /package/dist/{chunk-CUPM3FGB.js.map → chunk-NJVZPWXY.js.map} +0 -0
@@ -1,17 +1,44 @@
1
1
  import type {
2
2
  NewSessionDraftOptions,
3
+ NewSessionSelectionHistory,
3
4
  LatencyMode,
4
5
  ReasoningEffort,
5
6
  RepositoryResourceRef,
6
7
  ResourceRef,
7
8
  ToolRef,
8
9
  } from "@opengeni/contracts";
9
- import { and, eq } from "drizzle-orm";
10
+ import { stableJson } from "@opengeni/contracts";
11
+ import { and, eq, sql } from "drizzle-orm";
10
12
  import type { Database } from "./database";
11
13
  import * as schema from "./schema";
14
+ import {
15
+ accountIdInRlsScope,
16
+ subjectHasLiveWorkspaceAuthorityInScope,
17
+ } from "./workspace-authority";
12
18
 
13
19
  export type NewSessionDraftRow = typeof schema.newSessionDrafts.$inferSelect;
14
20
 
21
+ async function lockNewSessionDraftIdentity(
22
+ db: Database,
23
+ workspaceId: string,
24
+ subjectId: string,
25
+ ): Promise<void> {
26
+ await db.execute(
27
+ sql`select pg_advisory_xact_lock(hashtextextended(${`new-session-draft:${workspaceId}:${subjectId}`}, 0))`,
28
+ );
29
+ }
30
+
31
+ export type NewSessionDraftSnapshot = {
32
+ text: string;
33
+ resources: ResourceRef[];
34
+ tools: ToolRef[];
35
+ toolsProvided: boolean;
36
+ model: string;
37
+ reasoningEffort: ReasoningEffort;
38
+ latencyMode: LatencyMode;
39
+ options: NewSessionDraftOptions;
40
+ };
41
+
15
42
  export class NewSessionDraftConflictError extends Error {
16
43
  readonly name = "NewSessionDraftConflictError";
17
44
 
@@ -31,13 +58,54 @@ export class NewSessionDraftAccessError extends Error {
31
58
  type StoredNewSessionDraftOptions = NewSessionDraftOptions & {
32
59
  /** JSONB-only compatibility marker; deliberately not part of public options. */
33
60
  toolsProvided?: boolean;
61
+ /** Successful-create preference state, separate from transient draft edits. */
62
+ selectionHistory?: NewSessionSelectionHistory;
34
63
  };
35
64
 
65
+ const REMEMBERED_WORKING_DIR_MAX_LENGTH = 4096;
66
+
36
67
  function storedOptions(
37
68
  options: NewSessionDraftOptions,
38
69
  toolsProvided: boolean,
70
+ selectionHistory: NewSessionSelectionHistory = { projects: [] },
39
71
  ): StoredNewSessionDraftOptions {
40
- return { ...options, toolsProvided };
72
+ return { ...options, toolsProvided, selectionHistory };
73
+ }
74
+
75
+ export function newSessionSelectionHistory(row: NewSessionDraftRow): NewSessionSelectionHistory {
76
+ const raw = (row.sessionOptions as StoredNewSessionDraftOptions).selectionHistory;
77
+ if (!raw || !Array.isArray(raw.projects)) return { projects: [] };
78
+ return {
79
+ projects: raw.projects.slice(0, 50).flatMap((project) => {
80
+ if (!project || (project.channelId !== null && typeof project.channelId !== "string")) {
81
+ return [];
82
+ }
83
+ return [
84
+ {
85
+ channelId: project.channelId,
86
+ targetSandboxId:
87
+ typeof project.targetSandboxId === "string" ? project.targetSandboxId : null,
88
+ machines: Array.isArray(project.machines)
89
+ ? project.machines.slice(0, 20).flatMap((machine) =>
90
+ machine && typeof machine.sandboxId === "string"
91
+ ? [
92
+ {
93
+ sandboxId: machine.sandboxId,
94
+ workingDir:
95
+ typeof machine.workingDir === "string" &&
96
+ machine.workingDir.length > 0 &&
97
+ machine.workingDir.length <= REMEMBERED_WORKING_DIR_MAX_LENGTH
98
+ ? machine.workingDir
99
+ : null,
100
+ },
101
+ ]
102
+ : [],
103
+ )
104
+ : [],
105
+ },
106
+ ];
107
+ }),
108
+ };
41
109
  }
42
110
 
43
111
  export function newSessionDraftToolsProvided(row: NewSessionDraftRow): boolean {
@@ -53,6 +121,7 @@ export function newSessionDraftToolsProvided(row: NewSessionDraftRow): boolean {
53
121
  export function publicNewSessionDraftOptions(row: NewSessionDraftRow): NewSessionDraftOptions {
54
122
  const options = { ...(row.sessionOptions as StoredNewSessionDraftOptions) };
55
123
  delete options.toolsProvided;
124
+ delete options.selectionHistory;
56
125
  return options;
57
126
  }
58
127
 
@@ -87,10 +156,16 @@ export async function saveNewSessionDraftInTransaction(
87
156
  toolsProvided: boolean;
88
157
  model: string;
89
158
  reasoningEffort: ReasoningEffort;
90
- latencyMode?: LatencyMode;
159
+ latencyMode: LatencyMode;
91
160
  options: NewSessionDraftOptions;
92
161
  /** API-key and delegated service subjects have no workspace-membership row. */
93
162
  requireWorkspaceMembership?: boolean;
163
+ /**
164
+ * May this caller use the owner-only managed personal-workspace exception?
165
+ * See `PersonalWorkspaceOwnerException` in `@opengeni/db`. Absent means the
166
+ * historical bare-membership fence, unchanged.
167
+ */
168
+ personalWorkspaceOwnerException?: boolean;
94
169
  },
95
170
  ): Promise<NewSessionDraftRow> {
96
171
  if (input.requireWorkspaceMembership !== false) {
@@ -109,9 +184,36 @@ export async function saveNewSessionDraftInTransaction(
109
184
  )
110
185
  .for("key share")
111
186
  .limit(1);
112
- if (!membership) throw new NewSessionDraftAccessError();
187
+ // The owner of a managed personal workspace never has a membership row there
188
+ // (migration 0219 raises on one), so the bare probe above would 403 the
189
+ // composer inside the one workspace they always belong to. Ask the canonical
190
+ // resolver instead of re-deriving that rule — it runs on this same
191
+ // transaction handle, and there is no membership row for removeWorkspaceMember()
192
+ // to delete in a personal workspace, so nothing is lost by taking no
193
+ // `FOR KEY SHARE` on this path.
194
+ if (
195
+ !membership &&
196
+ !(
197
+ input.personalWorkspaceOwnerException === true &&
198
+ (await subjectHasLiveWorkspaceAuthorityInScope(db, {
199
+ // Scope-derived, NOT `input.accountId`: the applied RLS GUC is the
200
+ // tenant this transaction actually runs under, so an authority
201
+ // decision never reads a caller-supplied account. Matches
202
+ // listSessionsForSubject and setSessionPin.
203
+ accountId: await accountIdInRlsScope(db),
204
+ workspaceId: input.workspaceId,
205
+ subjectId: input.subjectId,
206
+ }))
207
+ )
208
+ ) {
209
+ throw new NewSessionDraftAccessError();
210
+ }
113
211
  }
114
- const current = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
212
+ await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
213
+ const current = await getNewSessionDraftInTransaction(db, {
214
+ ...input,
215
+ lock: true,
216
+ });
115
217
  const currentRevision = current?.revision ?? 0;
116
218
  if (currentRevision !== input.expectedRevision) {
117
219
  throw new NewSessionDraftConflictError(currentRevision);
@@ -128,10 +230,14 @@ export async function saveNewSessionDraftInTransaction(
128
230
  tools: input.tools,
129
231
  model: input.model,
130
232
  reasoningEffort: input.reasoningEffort,
131
- latencyMode: input.latencyMode ?? "standard",
233
+ latencyMode: input.latencyMode,
132
234
  // Keep the explicit/omitted policy in the existing JSONB extension point;
133
235
  // adding a column here would turn a client preference into a migration.
134
- sessionOptions: storedOptions(input.options, input.toolsProvided),
236
+ sessionOptions: storedOptions(
237
+ input.options,
238
+ input.toolsProvided,
239
+ current ? newSessionSelectionHistory(current) : { projects: [] },
240
+ ),
135
241
  updatedAt: new Date(),
136
242
  };
137
243
  if (current) {
@@ -155,7 +261,10 @@ export async function saveNewSessionDraftInTransaction(
155
261
  })
156
262
  .returning();
157
263
  if (inserted) return inserted;
158
- const raced = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
264
+ const raced = await getNewSessionDraftInTransaction(db, {
265
+ ...input,
266
+ lock: true,
267
+ });
159
268
  throw new NewSessionDraftConflictError(raced?.revision ?? 0);
160
269
  }
161
270
 
@@ -177,38 +286,94 @@ function safeRepositoryResource(resource: RepositoryResourceRef): RepositoryReso
177
286
  };
178
287
  }
179
288
 
180
- function safeWorkingDir(value: unknown, targetSandboxId: string | undefined): string | undefined {
181
- if (!targetSandboxId || typeof value !== "string") return undefined;
182
- const trimmed = value.trim();
183
- // Only a workspace-root-relative path is safe to remember. Absolute host
184
- // paths and traversal would leak or unexpectedly target a different machine
185
- // location after the user changes machines.
186
- if (
187
- !trimmed ||
188
- trimmed === "." ||
189
- trimmed === ".." ||
190
- trimmed.includes("\u0000") ||
191
- trimmed.startsWith("/") ||
192
- trimmed.startsWith("\\") ||
193
- /^[A-Za-z]:[\\/]/.test(trimmed) ||
194
- trimmed.split(/[\\/]+/).some((part) => part === "..")
195
- ) {
196
- return undefined;
289
+ export type AcceptedNewSessionSelection = {
290
+ channelId: string | null;
291
+ targetSandboxId: string | null;
292
+ workingDir: string | null;
293
+ };
294
+
295
+ export function rememberNewSessionSelection(
296
+ history: NewSessionSelectionHistory,
297
+ selection: AcceptedNewSessionSelection,
298
+ ): NewSessionSelectionHistory {
299
+ const existingProject = history.projects.find(
300
+ (project) => project.channelId === selection.channelId,
301
+ );
302
+ let machines = existingProject?.machines ?? [];
303
+ if (selection.targetSandboxId) {
304
+ machines = [
305
+ {
306
+ sandboxId: selection.targetSandboxId,
307
+ workingDir:
308
+ selection.workingDir && selection.workingDir.length <= REMEMBERED_WORKING_DIR_MAX_LENGTH
309
+ ? selection.workingDir
310
+ : null,
311
+ },
312
+ ...machines.filter((machine) => machine.sandboxId !== selection.targetSandboxId),
313
+ ].slice(0, 20);
197
314
  }
198
- return trimmed;
315
+ return {
316
+ projects: [
317
+ {
318
+ channelId: selection.channelId,
319
+ targetSandboxId: selection.targetSandboxId,
320
+ machines,
321
+ },
322
+ ...history.projects.filter((project) => project.channelId !== selection.channelId),
323
+ ].slice(0, 50),
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Record a successful create without consuming the editable draft. Realtime
329
+ * session creation uses this path because it must preserve the actor's pending
330
+ * text/files for a later ordinary session. The hidden preference update shares
331
+ * the draft identity lock with saves, and deliberately leaves its OCC revision
332
+ * unchanged because no browser-editable field changed.
333
+ */
334
+ export async function rememberNewSessionSelectionInTransaction(
335
+ db: Database,
336
+ input: {
337
+ workspaceId: string;
338
+ subjectId: string;
339
+ acceptedSelection: AcceptedNewSessionSelection;
340
+ },
341
+ ): Promise<boolean> {
342
+ await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
343
+ const current = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
344
+ if (!current) return false;
345
+ const [updated] = await db
346
+ .update(schema.newSessionDrafts)
347
+ .set({
348
+ sessionOptions: storedOptions(
349
+ publicNewSessionDraftOptions(current),
350
+ newSessionDraftToolsProvided(current),
351
+ rememberNewSessionSelection(newSessionSelectionHistory(current), input.acceptedSelection),
352
+ ),
353
+ })
354
+ .where(eq(schema.newSessionDrafts.id, current.id))
355
+ .returning({ id: schema.newSessionDrafts.id });
356
+ return Boolean(updated);
199
357
  }
200
358
 
201
359
  /**
202
360
  * Replace one exact accepted draft with the next-create safe seed. The row is
203
- * locked before the revision check so a concurrent save either commits first
204
- * and wins (this returns false), or observes the incremented seed revision and
205
- * reports a typed OCC conflict. A missing row is an idempotent no-op.
361
+ * identity-serialized before the revision check so even the absent-row
362
+ * revision-zero case cannot race a first save. Exact create callers receive a
363
+ * typed conflict; legacy revision-only callers retain the old false/no-op
364
+ * result. A missing row is an idempotent no-op.
206
365
  */
207
366
  export async function seedNewSessionDraftInTransaction(
208
367
  db: Database,
209
- input: { workspaceId: string; subjectId: string; expectedRevision: number },
368
+ input: {
369
+ workspaceId: string;
370
+ subjectId: string;
371
+ expectedRevision: number;
372
+ expectedSnapshot?: NewSessionDraftSnapshot;
373
+ acceptedSelection?: AcceptedNewSessionSelection;
374
+ },
210
375
  ): Promise<boolean> {
211
- if (input.expectedRevision === 0) return false;
376
+ await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
212
377
  const [current] = await db
213
378
  .select()
214
379
  .from(schema.newSessionDrafts)
@@ -220,7 +385,27 @@ export async function seedNewSessionDraftInTransaction(
220
385
  )
221
386
  .for("update")
222
387
  .limit(1);
223
- if (!current || current.revision !== input.expectedRevision) return false;
388
+ if (!current || current.revision !== input.expectedRevision) {
389
+ if (input.expectedSnapshot) {
390
+ throw new NewSessionDraftConflictError(current?.revision ?? 0);
391
+ }
392
+ return false;
393
+ }
394
+ if (
395
+ input.expectedSnapshot &&
396
+ stableJson({
397
+ text: current.text,
398
+ resources: current.resources,
399
+ tools: newSessionDraftToolsProvided(current) ? current.tools : [],
400
+ toolsProvided: newSessionDraftToolsProvided(current),
401
+ model: current.model,
402
+ reasoningEffort: current.reasoningEffort,
403
+ latencyMode: current.latencyMode,
404
+ options: publicNewSessionDraftOptions(current),
405
+ }) !== stableJson(input.expectedSnapshot)
406
+ ) {
407
+ throw new NewSessionDraftConflictError(current.revision);
408
+ }
224
409
 
225
410
  const options = current.sessionOptions as StoredNewSessionDraftOptions;
226
411
  const targetSandboxId =
@@ -228,8 +413,8 @@ export async function seedNewSessionDraftInTransaction(
228
413
  const safeOptions: NewSessionDraftOptions = {
229
414
  ...(options.sandboxBackend ? { sandboxBackend: options.sandboxBackend } : {}),
230
415
  ...(targetSandboxId ? { targetSandboxId } : {}),
231
- ...(safeWorkingDir(options.workingDir, targetSandboxId)
232
- ? { workingDir: safeWorkingDir(options.workingDir, targetSandboxId) }
416
+ ...(targetSandboxId && typeof options.workingDir === "string"
417
+ ? { workingDir: options.workingDir }
233
418
  : {}),
234
419
  ...(options.variableSetId ? { variableSetId: options.variableSetId } : {}),
235
420
  ...(options.rigId ? { rigId: options.rigId } : {}),
@@ -252,7 +437,16 @@ export async function seedNewSessionDraftInTransaction(
252
437
  model: current.model,
253
438
  reasoningEffort: current.reasoningEffort,
254
439
  latencyMode: current.latencyMode,
255
- sessionOptions: storedOptions(safeOptions, newSessionDraftToolsProvided(current)),
440
+ sessionOptions: storedOptions(
441
+ safeOptions,
442
+ newSessionDraftToolsProvided(current),
443
+ input.acceptedSelection
444
+ ? rememberNewSessionSelection(
445
+ newSessionSelectionHistory(current),
446
+ input.acceptedSelection,
447
+ )
448
+ : newSessionSelectionHistory(current),
449
+ ),
256
450
  updatedAt: new Date(),
257
451
  })
258
452
  .where(