@opengeni/core 3.0.1 → 3.1.2

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 (257) hide show
  1. package/README.md +4 -4
  2. package/dist/access/index.d.ts +131 -7
  3. package/dist/access/service-initiator.d.ts +3 -0
  4. package/dist/application/archived-session-imports.d.ts +6 -0
  5. package/dist/application/composer-submit.d.ts +1 -1
  6. package/dist/application/connect-operation.d.ts +6 -0
  7. package/dist/application/external-workspace-members.d.ts +13 -1
  8. package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
  9. package/dist/application/modal-native-original-configuration.d.ts +79 -0
  10. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  11. package/dist/application/new-session-drafts.d.ts +29 -2
  12. package/dist/application/organization-privacy.d.ts +57 -0
  13. package/dist/application/prepared-mcp-connection.d.ts +31 -0
  14. package/dist/application/sandbox-recovery.d.ts +58 -0
  15. package/dist/application/session-commands.d.ts +3 -0
  16. package/dist/application/session-tenancy.d.ts +13 -4
  17. package/dist/billing/agent-run-admission.d.ts +21 -0
  18. package/dist/billing/limits.d.ts +27 -1
  19. package/dist/billing/model-call-admission.d.ts +34 -0
  20. package/dist/billing/model-usage-settlement.d.ts +112 -0
  21. package/dist/canonical-human-identities.js +1 -1
  22. package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
  23. package/dist/chunk-7T2SZ7BI.js.map +1 -0
  24. package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
  25. package/dist/chunk-OMJFIIWK.js.map +1 -0
  26. package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
  27. package/dist/chunk-QI2M2B2K.js.map +1 -0
  28. package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
  29. package/dist/chunk-TTWTXQB2.js.map +1 -0
  30. package/dist/codex-model-availability.d.ts +55 -0
  31. package/dist/default-session-model.d.ts +192 -0
  32. package/dist/dependencies.d.ts +41 -7
  33. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  34. package/dist/domain/agent-config-resolution.d.ts +49 -0
  35. package/dist/domain/automations.d.ts +8 -1
  36. package/dist/domain/capabilities.d.ts +112 -8
  37. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  38. package/dist/domain/connector-tool-permissions.d.ts +62 -0
  39. package/dist/domain/direct-model-provider.d.ts +3 -0
  40. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  41. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  42. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  43. package/dist/domain/editable-artifacts/service.d.ts +3 -1
  44. package/dist/domain/editable-artifacts/types.d.ts +1 -1
  45. package/dist/domain/encryption-key.d.ts +12 -0
  46. package/dist/domain/insights-timing.d.ts +16 -0
  47. package/dist/domain/insights-usage.d.ts +27 -0
  48. package/dist/domain/insights.d.ts +21 -3
  49. package/dist/domain/knowledge-files.d.ts +1 -1
  50. package/dist/domain/knowledge-preparation.d.ts +10 -0
  51. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  52. package/dist/domain/knowledge-search.d.ts +15 -76
  53. package/dist/domain/mcp-account-bindings.d.ts +17 -0
  54. package/dist/domain/mcp-account-routes.d.ts +21 -0
  55. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  56. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  57. package/dist/domain/personal-connection-delegations.d.ts +73 -12
  58. package/dist/domain/pr-review.d.ts +121 -6
  59. package/dist/domain/realtime-voice-billing.d.ts +103 -0
  60. package/dist/domain/resources.d.ts +17 -1
  61. package/dist/domain/scheduled-task-access.d.ts +265 -0
  62. package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
  63. package/dist/domain/scheduled-task-audience.d.ts +70 -0
  64. package/dist/domain/scheduled-tasks.d.ts +61 -18
  65. package/dist/domain/session-connection-accounts.d.ts +53 -0
  66. package/dist/domain/session-file-access.d.ts +4 -0
  67. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  68. package/dist/domain/session-start-timing.d.ts +7 -0
  69. package/dist/domain/session-tool-policy.d.ts +102 -4
  70. package/dist/domain/sessions.d.ts +179 -15
  71. package/dist/domain/site-audience.d.ts +52 -0
  72. package/dist/domain/skills.d.ts +29 -1
  73. package/dist/domain/slack-bot.d.ts +46 -1
  74. package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
  75. package/dist/domain/vaults/index.d.ts +7 -0
  76. package/dist/domain/vaults/mapping.d.ts +25 -0
  77. package/dist/domain/vaults/provider-registry.d.ts +72 -0
  78. package/dist/domain/vaults/provider-sync.d.ts +176 -0
  79. package/dist/domain/vaults/provider-writes.d.ts +57 -0
  80. package/dist/domain/vaults/secret-resolver.d.ts +34 -0
  81. package/dist/domain/vaults/types.d.ts +24 -0
  82. package/dist/domain/vaults/vault-service.d.ts +35 -0
  83. package/dist/domain/voice-input-billing.d.ts +77 -0
  84. package/dist/domain/web-search-billing.d.ts +51 -0
  85. package/dist/domain/workspace-credits.d.ts +7 -0
  86. package/dist/editable-artifact-live/application.d.ts +2 -1
  87. package/dist/editable-artifact-live/server.d.ts +5 -1
  88. package/dist/editable-artifact-live/ticket.d.ts +2 -1
  89. package/dist/editable-artifact-live/types.d.ts +9 -0
  90. package/dist/editable-artifact-live.js +2 -2
  91. package/dist/editable-artifacts.js +10 -2
  92. package/dist/goal-admission.d.ts +627 -0
  93. package/dist/host-session-usage-limit.d.ts +15 -0
  94. package/dist/index.d.ts +43 -8
  95. package/dist/index.js +22682 -11743
  96. package/dist/index.js.map +1 -1
  97. package/dist/managed-session.d.ts +110 -7
  98. package/dist/model-call.d.ts +58 -0
  99. package/dist/model-catalog.d.ts +29 -3
  100. package/dist/model-provider-credentials.d.ts +43 -0
  101. package/dist/product-usage-metrics.d.ts +30 -0
  102. package/dist/rigs/index.d.ts +1 -13
  103. package/dist/sandbox/fleet.d.ts +58 -4
  104. package/dist/sandbox/routing.d.ts +42 -2
  105. package/dist/sandbox/runtime-settings.d.ts +27 -17
  106. package/dist/session-authorization.d.ts +10 -0
  107. package/dist/session-usage-limit-metrics.d.ts +60 -0
  108. package/dist/session-usage-limits.d.ts +89 -0
  109. package/dist/site-agent-clamp.d.ts +71 -0
  110. package/dist/site-session-origin.d.ts +39 -2
  111. package/dist/transcription.d.ts +120 -3
  112. package/dist/turn-surface.d.ts +20 -0
  113. package/dist/user-presence.d.ts +32 -0
  114. package/dist/workflow-wake-contract.d.ts +13 -1
  115. package/package.json +15 -22
  116. package/src/access/index.ts +961 -38
  117. package/src/access/service-initiator.ts +50 -0
  118. package/src/application/api-integration-servers.ts +9 -0
  119. package/src/application/archived-session-imports.ts +161 -0
  120. package/src/application/composer-submit.ts +3 -2
  121. package/src/application/connect-authority.ts +3 -3
  122. package/src/application/connect-operation.ts +37 -3
  123. package/src/application/external-continuation.ts +20 -3
  124. package/src/application/external-link-work-admission.ts +3 -0
  125. package/src/application/external-workspace-members.ts +51 -3
  126. package/src/application/modal-native-live-declaration-join.ts +196 -0
  127. package/src/application/modal-native-original-configuration.ts +261 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +172 -55
  130. package/src/application/organization-privacy.ts +161 -0
  131. package/src/application/prepared-mcp-connection.ts +133 -0
  132. package/src/application/sandbox-recovery.ts +78 -0
  133. package/src/application/session-commands.ts +73 -1
  134. package/src/application/session-mcp-credential-rotation.ts +61 -18
  135. package/src/application/session-tenancy.ts +262 -44
  136. package/src/application/user-resource-grants.ts +1 -29
  137. package/src/billing/agent-run-admission.ts +117 -0
  138. package/src/billing/limits.ts +138 -14
  139. package/src/billing/model-call-admission.ts +118 -0
  140. package/src/billing/model-usage-settlement.ts +472 -0
  141. package/src/codex-model-availability.ts +264 -0
  142. package/src/default-session-model.ts +692 -0
  143. package/src/dependencies.ts +48 -6
  144. package/src/developer-setup-credential-ceiling.ts +60 -0
  145. package/src/domain/agent-config-resolution.ts +148 -0
  146. package/src/domain/automations.ts +76 -2
  147. package/src/domain/capabilities.ts +830 -181
  148. package/src/domain/connection-account-selection-error.ts +19 -0
  149. package/src/domain/connector-tool-permissions.ts +572 -0
  150. package/src/domain/direct-model-provider.ts +62 -0
  151. package/src/domain/editable-artifacts/agent-application.ts +44 -6
  152. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  153. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  154. package/src/domain/editable-artifacts/ports.ts +11 -0
  155. package/src/domain/editable-artifacts/service.ts +20 -0
  156. package/src/domain/editable-artifacts/types.ts +1 -1
  157. package/src/domain/encryption-key.ts +26 -0
  158. package/src/domain/file-owner.ts +16 -1
  159. package/src/domain/github-action-policies.ts +1 -1
  160. package/src/domain/github-skill-source.ts +3 -1
  161. package/src/domain/insights-timing.ts +95 -0
  162. package/src/domain/insights-usage.ts +67 -0
  163. package/src/domain/insights.ts +167 -43
  164. package/src/domain/knowledge-files.ts +10 -3
  165. package/src/domain/knowledge-messages.ts +1 -0
  166. package/src/domain/knowledge-preparation.test.ts +276 -0
  167. package/src/domain/knowledge-preparation.ts +147 -0
  168. package/src/domain/knowledge-search.test.ts +617 -5
  169. package/src/domain/knowledge-search.ts +240 -23
  170. package/src/domain/mcp-account-bindings.ts +169 -0
  171. package/src/domain/mcp-account-routes.ts +152 -0
  172. package/src/domain/native-mcp-connection-admission.ts +14 -0
  173. package/src/domain/organization-integration-catalog.ts +88 -0
  174. package/src/domain/personal-connection-delegations.ts +399 -257
  175. package/src/domain/pr-review.ts +24 -8
  176. package/src/domain/realtime-voice-billing.ts +425 -0
  177. package/src/domain/resources.ts +50 -4
  178. package/src/domain/scheduled-task-access.ts +1089 -0
  179. package/src/domain/scheduled-task-account-audit.ts +90 -0
  180. package/src/domain/scheduled-task-audience.ts +249 -0
  181. package/src/domain/scheduled-tasks.ts +1313 -374
  182. package/src/domain/session-connection-accounts.ts +239 -0
  183. package/src/domain/session-file-access.ts +47 -0
  184. package/src/domain/session-native-connection-replacement.ts +64 -0
  185. package/src/domain/session-start-timing.ts +60 -0
  186. package/src/domain/session-tool-policy.ts +489 -14
  187. package/src/domain/sessions.ts +2254 -717
  188. package/src/domain/site-audience.ts +130 -0
  189. package/src/domain/skills.ts +9 -0
  190. package/src/domain/slack-bot.ts +141 -4
  191. package/src/domain/slack-publication-secret-safety.ts +1 -1
  192. package/src/domain/vaults/index.ts +19 -0
  193. package/src/domain/vaults/mapping.ts +190 -0
  194. package/src/domain/vaults/provider-registry.ts +201 -0
  195. package/src/domain/vaults/provider-sync.ts +531 -0
  196. package/src/domain/vaults/provider-writes.ts +288 -0
  197. package/src/domain/vaults/secret-resolver.ts +464 -0
  198. package/src/domain/vaults/types.ts +26 -0
  199. package/src/domain/vaults/vault-service.ts +1316 -0
  200. package/src/domain/voice-input-billing.ts +330 -0
  201. package/src/domain/web-search-billing.ts +169 -0
  202. package/src/domain/workspace-credits.ts +13 -0
  203. package/src/editable-artifact-live/application.ts +8 -1
  204. package/src/editable-artifact-live/server.ts +154 -6
  205. package/src/editable-artifact-live/ticket.ts +86 -1
  206. package/src/editable-artifact-live/types.ts +10 -0
  207. package/src/goal-admission.ts +220 -0
  208. package/src/host-session-usage-limit.ts +25 -0
  209. package/src/index.ts +75 -11
  210. package/src/managed-session.ts +137 -6
  211. package/src/model-call.ts +79 -0
  212. package/src/model-catalog.ts +345 -91
  213. package/src/model-provider-credentials.ts +394 -0
  214. package/src/product-usage-metrics.ts +101 -0
  215. package/src/rigs/index.ts +23 -111
  216. package/src/sandbox/fleet.ts +253 -28
  217. package/src/sandbox/routing.ts +204 -43
  218. package/src/sandbox/runtime-settings.ts +159 -85
  219. package/src/session-authorization.ts +172 -23
  220. package/src/session-usage-limit-metrics.ts +95 -0
  221. package/src/session-usage-limits.ts +415 -0
  222. package/src/site-agent-clamp.ts +226 -0
  223. package/src/site-session-origin.ts +84 -5
  224. package/src/transcription.ts +123 -4
  225. package/src/turn-surface.ts +47 -0
  226. package/src/user-presence.ts +134 -0
  227. package/src/workflow-wake-contract.ts +14 -1
  228. package/dist/application/host-mcp-owner.d.ts +0 -6
  229. package/dist/application/host-mcp-resolvers.d.ts +0 -13
  230. package/dist/chunk-5WZYFL45.js +0 -266
  231. package/dist/chunk-5WZYFL45.js.map +0 -1
  232. package/dist/chunk-H7X52RI2.js.map +0 -1
  233. package/dist/chunk-OF65T3PM.js.map +0 -1
  234. package/dist/chunk-VCZFFEPB.js.map +0 -1
  235. package/dist/chunk-ZYCNLYGH.js.map +0 -1
  236. package/dist/domain/connection-authority.d.ts +0 -97
  237. package/dist/domain/connection-authority.js +0 -169
  238. package/dist/domain/connection-authority.js.map +0 -1
  239. package/dist/domain/environments.d.ts +0 -33
  240. package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
  241. package/dist/domain/host-mcp-task-admission.d.ts +0 -18
  242. package/dist/domain/packs.d.ts +0 -59
  243. package/dist/domain/product-integration-pack.d.ts +0 -44
  244. package/dist/domain/product-integration-skill.gen.d.ts +0 -5
  245. package/dist/remote-mcp-credentials.d.ts +0 -23
  246. package/dist/remote-mcp-credentials.js +0 -11
  247. package/dist/remote-mcp-credentials.js.map +0 -1
  248. package/src/application/host-mcp-owner.ts +0 -41
  249. package/src/application/host-mcp-resolvers.ts +0 -84
  250. package/src/domain/connection-authority.ts +0 -329
  251. package/src/domain/environments.ts +0 -120
  252. package/src/domain/host-mcp-authority-source-admission.ts +0 -25
  253. package/src/domain/host-mcp-task-admission.ts +0 -121
  254. package/src/domain/packs.ts +0 -699
  255. package/src/domain/product-integration-pack.ts +0 -42
  256. package/src/domain/product-integration-skill.gen.ts +0 -52
  257. package/src/remote-mcp-credentials.ts +0 -361
@@ -1,31 +1,44 @@
1
1
  import {
2
2
  ForkSessionResponse,
3
+ SessionVisibilityChangePreview,
3
4
  SessionTenancyCreateCapabilities,
4
5
  UpdateSessionVisibilityResponse,
5
6
  sessionVisibilityFromPublic,
6
7
  sessionVisibilityToPublic,
7
8
  type ForkSessionRequest,
8
9
  type SessionAuthorizationSurface,
10
+ type SessionVisibility,
9
11
  type UpdateSessionVisibilityRequest,
10
12
  type VariableSet,
11
13
  } from "@opengeni/contracts";
12
14
  import {
15
+ assertSessionIsNotImported,
13
16
  forkSessionContent,
17
+ getSessionForSubject,
14
18
  getRig,
15
19
  getPrivateSessionCreatePolicy,
16
20
  getSessionEventForSubject,
21
+ inspectSessionVisibilityChange,
22
+ EMPTY_SESSION_VISIBILITY_WORK,
17
23
  isRetryableDatabaseTransportFailure,
18
24
  nestedPostgresSqlState,
19
25
  replayAppliedSessionFork,
20
- sessionTenancyProductActivated,
26
+ sessionHasLiveAttempts,
21
27
  SessionTenancyAccessError,
22
28
  SessionTenancyInvalidRequestError,
23
- SessionTenancyNotActivatedError,
29
+ SessionVisibilityWorkConflictError,
30
+ stopSessionWorkForVisibilityChange,
24
31
  transitionSessionVisibility,
25
32
  type Database,
26
33
  type ForkSessionContentResult,
34
+ type TransitionSessionVisibilityResult,
27
35
  } from "@opengeni/db";
28
36
  import { publishDurableSessionEvents, type EventBus } from "@opengeni/events";
37
+ import {
38
+ privacyDeploymentLimits,
39
+ requireDeploymentAllowsOnlyMe,
40
+ resolveOrganizationPrivacy,
41
+ } from "./organization-privacy";
29
42
  import {
30
43
  requirePermission,
31
44
  hasVerifiedOwningUserAuthorization,
@@ -33,7 +46,7 @@ import {
33
46
  } from "../access";
34
47
  import { requireSessionAuthorization } from "../session-authorization";
35
48
  import type { AppDependencies } from "../dependencies";
36
- import { validateVariableSetAttachment } from "../domain/environments";
49
+ import { variableSetsRemovedError } from "../domain/encryption-key";
37
50
  import { externalContinuationCommitAuthorizer } from "./external-continuation";
38
51
 
39
52
  type SessionTenancyDependencies = Pick<
@@ -41,6 +54,18 @@ type SessionTenancyDependencies = Pick<
41
54
  "db" | "bus" | "sessionAuthorization" | "settings"
42
55
  >;
43
56
 
57
+ type SessionVisibilityDependencies = SessionTenancyDependencies & {
58
+ workflowClient: Pick<AppDependencies["workflowClient"], "wakeSessionWorkflow">;
59
+ };
60
+
61
+ /**
62
+ * How long one "make private anyway" request waits for a worker to settle the
63
+ * response it is executing. Past it the request answers `stopping_active_work`
64
+ * and the same idempotency key finishes the change on retry.
65
+ */
66
+ const STOP_WORK_SETTLE_TIMEOUT_MS = 15_000;
67
+ const STOP_WORK_POLL_INTERVAL_MS = 400;
68
+
44
69
  export class SessionTenancyManagedHumanRequiredError extends Error {
45
70
  readonly name = "SessionTenancyManagedHumanRequiredError";
46
71
  constructor() {
@@ -82,7 +107,7 @@ export function requireVerifiedOwningUser(
82
107
  }
83
108
 
84
109
  export async function getManagedHumanSessionCreateCapabilities(
85
- deps: Pick<SessionTenancyDependencies, "db">,
110
+ deps: Pick<SessionTenancyDependencies, "db" | "settings">,
86
111
  authorization: AccessGrantAuthorization,
87
112
  workspaceId: string,
88
113
  ): Promise<SessionTenancyCreateCapabilities> {
@@ -99,16 +124,22 @@ export async function getManagedHumanSessionCreateCapabilities(
99
124
  }
100
125
  throw error;
101
126
  }
102
- // Mirror the database create fence exactly: a managed human's own Personal
103
- // workspace needs only the operator readiness receipt, while a shared
104
- // organization workspace additionally needs the owner/admin product setting.
127
+ // Mirror the database create fence exactly: every organization is
128
+ // session-tenancy activated (migration 0611), so a member's own Personal
129
+ // workspace is always private-capable, while a shared organization workspace
130
+ // additionally needs the owner/admin Only-me product setting.
105
131
  let activated = false;
132
+ let sharedWorkspace = false;
106
133
  try {
107
134
  const policy = await getPrivateSessionCreatePolicy(deps.db, {
108
135
  workspaceId,
109
136
  actorSubjectId: authorization.grant.subjectId,
110
137
  });
111
- activated = policy.personalWorkspace ? policy.platformAvailable : policy.organizationEnabled;
138
+ sharedWorkspace = !policy.personalWorkspace;
139
+ // A deployment that turns Only me off narrows every shared workspace.
140
+ activated = policy.personalWorkspace
141
+ ? policy.platformAvailable
142
+ : policy.organizationEnabled && privacyDeploymentLimits(deps.settings).onlyMeAllowed;
112
143
  } catch (error) {
113
144
  // A canonical managed human without an active organization membership or
114
145
  // stated workspace authority (legacy managed access) is simply not
@@ -121,15 +152,22 @@ export async function getManagedHumanSessionCreateCapabilities(
121
152
  throw error;
122
153
  }
123
154
  }
155
+ // The organization's default audience applies to shared workspaces where
156
+ // Only me is available; a Personal workspace has nobody else to share with.
157
+ const defaultVisibility =
158
+ activated && sharedWorkspace
159
+ ? (await resolveOrganizationPrivacy(deps, authorization.grant.accountId)).defaultVisibility
160
+ : "workspace";
124
161
  return SessionTenancyCreateCapabilities.parse({
125
162
  activated,
126
163
  canCreatePrivate: activated,
127
164
  reason: activated ? "available" : "not_activated",
165
+ defaultVisibility,
128
166
  });
129
167
  }
130
168
 
131
169
  export async function requireManagedHumanPrivateSessionCreate(
132
- deps: Pick<SessionTenancyDependencies, "db">,
170
+ deps: Pick<SessionTenancyDependencies, "db" | "settings">,
133
171
  authorization: AccessGrantAuthorization,
134
172
  workspaceId: string,
135
173
  ): Promise<void> {
@@ -138,6 +176,10 @@ export async function requireManagedHumanPrivateSessionCreate(
138
176
  // fence, after keyed replay resolution), so a committed keyed success still
139
177
  // replays after the setting is disabled while a fresh key fails closed.
140
178
  await requireSessionTenancyMutationGate(deps, authorization, workspaceId, ["sessions:create"]);
179
+ await requireDeploymentAllowsOnlyMe(deps, {
180
+ workspaceId,
181
+ actorSubjectId: authorization.grant.subjectId,
182
+ });
141
183
  }
142
184
 
143
185
  async function requireSessionTenancyMutationGate(
@@ -151,9 +193,6 @@ async function requireSessionTenancyMutationGate(
151
193
  // missing, shared, or another owner's private session.
152
194
  requireVerifiedOwningUser(authorization, workspaceId);
153
195
  for (const permission of permissions) requirePermission(authorization.grant, permission);
154
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
155
- throw new SessionTenancyNotActivatedError();
156
- }
157
196
  }
158
197
 
159
198
  async function publishExactCommittedEvent(
@@ -244,7 +283,7 @@ async function projectAndPublishSessionFork(
244
283
  }
245
284
 
246
285
  export async function updateManagedHumanSessionVisibility(
247
- deps: SessionTenancyDependencies,
286
+ deps: SessionVisibilityDependencies,
248
287
  authorization: AccessGrantAuthorization,
249
288
  workspaceId: string,
250
289
  sessionId: string,
@@ -258,19 +297,57 @@ export async function updateManagedHumanSessionVisibility(
258
297
  operation: "session.visibility.write",
259
298
  surface: authorizationSurface,
260
299
  });
300
+ if (request.visibility === "private") {
301
+ await requireDeploymentAllowsOnlyMe(deps, {
302
+ workspaceId,
303
+ actorSubjectId: authorization.grant.subjectId,
304
+ });
305
+ }
261
306
 
262
- const result = await runTenancyMutation(
263
- async () =>
264
- await transitionSessionVisibility(deps.db, {
265
- workspaceId,
266
- sessionId,
267
- actorSubjectId: authorization.grant.subjectId,
268
- targetVisibility: sessionVisibilityFromPublic(request.visibility),
269
- expectedAuthorityEpoch: request.expectedAuthorityEpoch,
270
- operationKey: request.idempotencyKey,
271
- ...(beforeCommit ? { beforeCommit } : {}),
272
- }),
273
- );
307
+ const actorSubjectId = authorization.grant.subjectId;
308
+ const transition = async () =>
309
+ await runTenancyMutation(
310
+ async () =>
311
+ await transitionSessionVisibility(deps.db, {
312
+ workspaceId,
313
+ sessionId,
314
+ actorSubjectId,
315
+ targetVisibility: sessionVisibilityFromPublic(request.visibility),
316
+ expectedAuthorityEpoch: request.expectedAuthorityEpoch,
317
+ operationKey: request.idempotencyKey,
318
+ ...(request.stopActiveWork === true ? { stopActiveWork: true } : {}),
319
+ ...(beforeCommit ? { beforeCommit } : {}),
320
+ }),
321
+ );
322
+ let result: TransitionSessionVisibilityResult;
323
+ try {
324
+ result = await transition();
325
+ } catch (error) {
326
+ if (
327
+ !(error instanceof SessionVisibilityWorkConflictError) ||
328
+ request.stopActiveWork !== true ||
329
+ error.liveAttemptCount === 0
330
+ ) {
331
+ throw error;
332
+ }
333
+ // A worker is executing a response. Stopping it is asynchronous: durably
334
+ // interrupt it (and cancel everything queued or parked) first, let the
335
+ // worker cancel the turn, then make the same transition.
336
+ await stopLiveSessionWorkForVisibilityChange(deps, {
337
+ workspaceId,
338
+ sessionId,
339
+ actorSubjectId,
340
+ expectedAuthorityEpoch: request.expectedAuthorityEpoch,
341
+ operationKey: `${request.idempotencyKey}:stop-work`,
342
+ });
343
+ result = await transitionAfterLiveWorkSettles(deps, {
344
+ workspaceId,
345
+ sessionId,
346
+ actorSubjectId,
347
+ transition,
348
+ conflict: error,
349
+ });
350
+ }
274
351
  const response = UpdateSessionVisibilityResponse.parse({
275
352
  operationId: result.operationId,
276
353
  eventId: result.eventId,
@@ -281,16 +358,160 @@ export async function updateManagedHumanSessionVisibility(
281
358
  replay: result.replay,
282
359
  revokedGrantCount: result.revokedGrantCount,
283
360
  });
361
+ await publishCommittedEventIds(deps, {
362
+ workspaceId,
363
+ sessionId,
364
+ subjectId: actorSubjectId,
365
+ eventIds: result.settledEventIds,
366
+ });
284
367
  await publishExactCommittedEvent(deps, {
285
368
  workspaceId,
286
369
  sessionId,
287
- subjectId: authorization.grant.subjectId,
370
+ subjectId: actorSubjectId,
288
371
  eventId: response.eventId,
289
372
  eventSequence: response.eventSequence,
290
373
  });
374
+ await wakeSessionWorkflowAfterVisibilityWork(deps, result.workflowWake);
291
375
  return response;
292
376
  }
293
377
 
378
+ /**
379
+ * Owner-only preview for the audience dialog: what a change to `visibility`
380
+ * would stop, and what would still refuse it. Same gates as the change itself,
381
+ * so a non-owner learns nothing a refused change would not tell them.
382
+ */
383
+ export async function previewManagedHumanSessionVisibilityChange(
384
+ deps: SessionTenancyDependencies,
385
+ authorization: AccessGrantAuthorization,
386
+ workspaceId: string,
387
+ sessionId: string,
388
+ targetVisibility: SessionVisibility,
389
+ authorizationSurface: SessionAuthorizationSurface = "core",
390
+ ): Promise<SessionVisibilityChangePreview> {
391
+ await requireSessionTenancyMutationGate(deps, authorization, workspaceId, ["sessions:control"]);
392
+ await requireSessionAuthorization(deps, authorization.grant, {
393
+ sessionId,
394
+ operation: "session.visibility.write",
395
+ surface: authorizationSurface,
396
+ });
397
+ const preview = await inspectSessionVisibilityChange(deps.db, {
398
+ workspaceId,
399
+ sessionId,
400
+ actorSubjectId: authorization.grant.subjectId,
401
+ });
402
+ const current = sessionVisibilityToPublic(preview.visibility);
403
+ // Only a change to Only me stops work or drops other people's access;
404
+ // sharing a private chat with the workspace never does either.
405
+ const changesToPrivate = targetVisibility === "private" && current !== "private";
406
+ return SessionVisibilityChangePreview.parse({
407
+ visibility: current,
408
+ targetVisibility,
409
+ authorityEpoch: preview.authorityEpoch,
410
+ work: changesToPrivate ? preview.work : EMPTY_SESSION_VISIBILITY_WORK,
411
+ // Kept for older clients; nothing refuses the change any more.
412
+ blocker: null,
413
+ });
414
+ }
415
+
416
+ async function stopLiveSessionWorkForVisibilityChange(
417
+ deps: SessionVisibilityDependencies,
418
+ input: Parameters<typeof stopSessionWorkForVisibilityChange>[1],
419
+ ): Promise<void> {
420
+ const stopped = await runTenancyMutation(
421
+ async () => await stopSessionWorkForVisibilityChange(deps.db, input),
422
+ );
423
+ await publishCommittedEventIds(deps, {
424
+ workspaceId: input.workspaceId,
425
+ sessionId: input.sessionId,
426
+ subjectId: input.actorSubjectId,
427
+ eventIds: stopped.eventIds,
428
+ });
429
+ await wakeSessionWorkflowAfterVisibilityWork(deps, stopped.workflowWake);
430
+ }
431
+
432
+ async function transitionAfterLiveWorkSettles(
433
+ deps: SessionVisibilityDependencies,
434
+ input: {
435
+ workspaceId: string;
436
+ sessionId: string;
437
+ actorSubjectId: string;
438
+ transition: () => Promise<TransitionSessionVisibilityResult>;
439
+ conflict: SessionVisibilityWorkConflictError;
440
+ },
441
+ ): Promise<TransitionSessionVisibilityResult> {
442
+ const deadline = Date.now() + STOP_WORK_SETTLE_TIMEOUT_MS;
443
+ for (;;) {
444
+ const live = await sessionHasLiveAttempts(deps.db, input);
445
+ if (!live) {
446
+ try {
447
+ return await input.transition();
448
+ } catch (error) {
449
+ // A worker claimed a new attempt between the read and the transition.
450
+ if (!(error instanceof SessionVisibilityWorkConflictError) || error.liveAttemptCount === 0)
451
+ throw error;
452
+ }
453
+ }
454
+ if (Date.now() >= deadline) {
455
+ throw new SessionVisibilityWorkConflictError(
456
+ "stopping_active_work",
457
+ input.conflict.work,
458
+ input.conflict.liveAttemptCount,
459
+ );
460
+ }
461
+ await new Promise((resolve) => setTimeout(resolve, STOP_WORK_POLL_INTERVAL_MS));
462
+ }
463
+ }
464
+
465
+ async function publishCommittedEventIds(
466
+ deps: { db: Database; bus: EventBus },
467
+ input: { workspaceId: string; sessionId: string; subjectId: string; eventIds: string[] },
468
+ ): Promise<void> {
469
+ for (const eventId of input.eventIds) {
470
+ try {
471
+ const event = await getSessionEventForSubject(
472
+ deps.db,
473
+ input.workspaceId,
474
+ input.subjectId,
475
+ eventId,
476
+ );
477
+ if (!event || event.sessionId !== input.sessionId) continue;
478
+ await publishDurableSessionEvents(deps.bus, input.workspaceId, input.sessionId, [event]);
479
+ } catch {
480
+ // Committed; live fanout is repairable from the durable stream.
481
+ console.warn("[session-tenancy] settled event live publication was deferred to replay", {
482
+ errorClass: "SessionTenancyEventPublishOperationError",
483
+ errorCode: "session_tenancy_event_publish_deferred",
484
+ origin: "core",
485
+ sessionId: input.sessionId,
486
+ });
487
+ }
488
+ }
489
+ }
490
+
491
+ async function wakeSessionWorkflowAfterVisibilityWork(
492
+ deps: SessionVisibilityDependencies,
493
+ wake: TransitionSessionVisibilityResult["workflowWake"],
494
+ ): Promise<void> {
495
+ if (!wake) return;
496
+ try {
497
+ await deps.workflowClient.wakeSessionWorkflow({
498
+ accountId: wake.accountId,
499
+ workspaceId: wake.workspaceId,
500
+ sessionId: wake.sessionId,
501
+ workflowId: wake.temporalWorkflowId,
502
+ wakeRevision: wake.wakeRevision,
503
+ interruptionRequested: true,
504
+ });
505
+ } catch {
506
+ // The wake revision committed with the change; the durable outbox retries.
507
+ console.warn("[session-tenancy] immediate workflow wake failed; durable outbox will retry", {
508
+ errorClass: "WorkflowWakeOperationError",
509
+ errorCode: "session_tenancy_wake_failed",
510
+ origin: "core",
511
+ });
512
+ }
513
+ }
514
+
294
515
  export async function forkManagedHumanSession(
295
516
  deps: SessionTenancyDependencies,
296
517
  authorization: AccessGrantAuthorization,
@@ -303,6 +524,12 @@ export async function forkManagedHumanSession(
303
524
  "sessions:read",
304
525
  "sessions:create",
305
526
  ]);
527
+ if (request.visibility === "private") {
528
+ await requireDeploymentAllowsOnlyMe(deps, {
529
+ workspaceId,
530
+ actorSubjectId: authorization.grant.subjectId,
531
+ });
532
+ }
306
533
  const beforeCommit = externalContinuationCommitAuthorizer(authorization);
307
534
  const forkInput = {
308
535
  sourceWorkspaceId: workspaceId,
@@ -335,17 +562,9 @@ export async function forkManagedHumanSession(
335
562
  const resolveRuntimeConfiguration = async () => {
336
563
  if (runtimeConfigurationPromise) return await runtimeConfigurationPromise;
337
564
  runtimeConfigurationPromise = (async () => {
565
+ // Variable Sets were replaced by vaults (RM-1).
566
+ if (request.variableSetIds?.length) throw variableSetsRemovedError("variableSetIds");
338
567
  const variableSets: VariableSet[] = [];
339
- for (const variableSetId of request.variableSetIds ?? []) {
340
- variableSets.push(
341
- await validateVariableSetAttachment(
342
- { settings: deps.settings, db: deps.db },
343
- authorization.grant,
344
- workspaceId,
345
- variableSetId,
346
- ),
347
- );
348
- }
349
568
  let rigVersionId: string | null = null;
350
569
  if (request.rigId) {
351
570
  requirePermission(authorization.grant, "rigs:use");
@@ -353,14 +572,6 @@ export async function forkManagedHumanSession(
353
572
  if (!rig?.activeVersion) {
354
573
  throw new SessionTenancyInvalidRequestError();
355
574
  }
356
- for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
357
- await validateVariableSetAttachment(
358
- { settings: deps.settings, db: deps.db },
359
- authorization.grant,
360
- workspaceId,
361
- defaultVariableSetId,
362
- );
363
- }
364
575
  rigVersionId = rig.activeVersion.id;
365
576
  }
366
577
  return {
@@ -422,6 +633,13 @@ export async function forkManagedHumanSession(
422
633
 
423
634
  // Validate all requested runtime resources before committing the independent
424
635
  // fork so ordinary authorization failures cannot leave an unusable copy.
636
+ const source = await getSessionForSubject(
637
+ deps.db,
638
+ workspaceId,
639
+ sourceSessionId,
640
+ authorization.grant.subjectId,
641
+ );
642
+ if (source) assertSessionIsNotImported(source);
425
643
  const runtimeConfiguration = runtimeSetupRequested ? await resolveRuntimeConfiguration() : null;
426
644
  const result = await runTenancyMutation(
427
645
  async () =>
@@ -6,11 +6,8 @@ import type {
6
6
  } from "@opengeni/contracts";
7
7
  import {
8
8
  issueSelfUserResourceGrant,
9
- issueSelfLocalConnectionUseGrant,
10
9
  listSelfUserResourceAuthorities,
11
10
  revokeSelfUserResourceGrant,
12
- sessionTenancyProductActivated,
13
- SessionTenancyNotActivatedError,
14
11
  } from "@opengeni/db";
15
12
  import { requirePermission, type AccessGrantAuthorization } from "../access";
16
13
  import type { AppDependencies } from "../dependencies";
@@ -47,9 +44,6 @@ async function requireOwnerProductGate(
47
44
  // Target-free gates must run before an optional target-session lookup or host
48
45
  // callback so rejected principals cannot use grant management as an oracle.
49
46
  requireOwnerAuthority(authorization, workspaceId, permissions);
50
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
51
- throw new SessionTenancyNotActivatedError();
52
- }
53
47
  }
54
48
 
55
49
  function requireOwnerAuthority(
@@ -76,14 +70,6 @@ export async function listManagedHumanUserResourceAuthorities(
76
70
  input: { resourceKind: UserResourceKind; cursor?: string | undefined; limit: number },
77
71
  ) {
78
72
  requireOwnerAuthority(authorization, workspaceId, LIST_PERMISSIONS[input.resourceKind]);
79
- // Before activation there cannot be usable personal-resource authority in
80
- // this organization. Discovery therefore has an exact empty answer; only
81
- // issue/revoke and runtime use remain behind the forward-only product gate.
82
- // This keeps ordinary workspace-owned Rig/Variable Set selection independent
83
- // from an optional personal-resource product that is not enabled.
84
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
85
- return { authorities: [], nextCursor: null };
86
- }
87
73
  return await listSelfUserResourceAuthorities(deps.db, {
88
74
  accountId: authorization.grant.accountId,
89
75
  workspaceId,
@@ -103,21 +89,7 @@ export async function issueManagedHumanUserResourceGrant(
103
89
  const modePermissions: Permission[] =
104
90
  request.mode === "session" ? ["sessions:control"] : ["sessions:create"];
105
91
  if (authorization.canonicalLocalHumanSession) {
106
- requireOwnerAuthority(authorization, workspaceId, [
107
- ...ISSUE_PERMISSIONS[request.resourceKind],
108
- "sessions:create",
109
- ]);
110
- if (request.resourceKind !== "connection" || request.mode !== "always") {
111
- throw new SessionTenancyManagedHumanRequiredError();
112
- }
113
- return await issueSelfLocalConnectionUseGrant(deps.db, {
114
- accountId: authorization.grant.accountId,
115
- workspaceId,
116
- subjectId: authorization.grant.subjectId,
117
- authorityId,
118
- context: request.context,
119
- workspaceSharedAcknowledged: request.workspaceSharedAcknowledged,
120
- });
92
+ throw new SessionTenancyManagedHumanRequiredError();
121
93
  }
122
94
  await requireOwnerProductGate(deps, authorization, workspaceId, [
123
95
  ...ISSUE_PERMISSIONS[request.resourceKind],
@@ -0,0 +1,117 @@
1
+ import {
2
+ canonicalizeConfiguredModelId,
3
+ configuredStaticUsageLimits,
4
+ type Settings,
5
+ } from "@opengeni/config";
6
+ import type { EntitlementsPort } from "@opengeni/contracts";
7
+ import { coreRoutedSubscriptionFunding, modelFundingForAdmission } from "./limits";
8
+ import {
9
+ checkWorkspaceAllowance,
10
+ getSpendableCreditBalance,
11
+ isCodexBilledTurn,
12
+ sumUsageQuantity,
13
+ type Database,
14
+ } from "@opengeni/db";
15
+
16
+ export type AgentRunAdmissionDenial =
17
+ | "insufficient_credits"
18
+ | "allowance_exhausted"
19
+ | "monthly_model_cost_limit"
20
+ | "monthly_agent_run_limit";
21
+
22
+ /** Read-only admission shared by service-authored runs and goal Resume. */
23
+ export async function agentRunAdmissionDenial(
24
+ services: { db: Database; settings: Settings; entitlements?: EntitlementsPort | null },
25
+ input: {
26
+ accountId: string;
27
+ workspaceId: string;
28
+ model: string;
29
+ requestedAgentRuns: number;
30
+ /** The accepted work's causal human, not the scheduler/service caller. */
31
+ initiatingHumanSubjectId?: string | null;
32
+ acceptedTurn?: { sessionId: string; turnId: string };
33
+ },
34
+ ): Promise<AgentRunAdmissionDenial | null> {
35
+ const codexBilled = await isCodexBilledTurn({
36
+ db: services.db,
37
+ settings: services.settings,
38
+ workspaceId: input.workspaceId,
39
+ model: input.model,
40
+ accountId: input.accountId,
41
+ subjectId: input.initiatingHumanSubjectId ?? null,
42
+ ...(input.acceptedTurn ? { acceptedTurn: input.acceptedTurn } : {}),
43
+ });
44
+ const externallyBilled = modelFundingForAdmission(
45
+ services.settings,
46
+ input.model,
47
+ codexBilled,
48
+ await coreRoutedSubscriptionFunding(services.db, services.settings, {
49
+ accountId: input.accountId,
50
+ workspaceId: input.workspaceId,
51
+ model: input.model,
52
+ subjectId: input.initiatingHumanSubjectId ?? null,
53
+ ...(input.acceptedTurn ? { acceptedTurn: input.acceptedTurn } : {}),
54
+ }),
55
+ ).fundedWithoutCredits;
56
+ if (
57
+ !externallyBilled &&
58
+ (services.settings.billingMode === "stripe" || services.settings.usageLimitsMode === "managed")
59
+ ) {
60
+ if (services.entitlements) {
61
+ const decision = await services.entitlements.admitRun({
62
+ accountId: input.accountId,
63
+ workspaceId: input.workspaceId,
64
+ action: "agent_run:create",
65
+ quantity: input.requestedAgentRuns,
66
+ });
67
+ if (!decision.allowed) return "insufficient_credits";
68
+ } else {
69
+ const balance = await getSpendableCreditBalance(
70
+ services.db,
71
+ input.accountId,
72
+ canonicalizeConfiguredModelId(services.settings, input.model),
73
+ );
74
+ if (balance.balanceMicros <= 0) return "insufficient_credits";
75
+ }
76
+ }
77
+ const refusal = await checkWorkspaceAllowance(services.db, {
78
+ accountId: input.accountId,
79
+ workspaceId: input.workspaceId,
80
+ subjectId: input.initiatingHumanSubjectId ?? null,
81
+ ...(externallyBilled ? { fundedWithoutCredits: true } : {}),
82
+ });
83
+ if (refusal) return refusal.code;
84
+ if (
85
+ services.settings.usageLimitsMode !== "static" &&
86
+ services.settings.usageLimitsMode !== "managed"
87
+ ) {
88
+ return null;
89
+ }
90
+ const limits = configuredStaticUsageLimits(services.settings);
91
+ if (!externallyBilled && limits.maxMonthlyCostMicrosPerAccount) {
92
+ const used = await sumUsageQuantity(services.db, {
93
+ accountId: input.accountId,
94
+ eventType: "model.cost",
95
+ since: startOfUtcMonth(),
96
+ });
97
+ if (used >= limits.maxMonthlyCostMicrosPerAccount) {
98
+ return "monthly_model_cost_limit";
99
+ }
100
+ }
101
+ if (limits.maxMonthlyAgentRunsPerWorkspace) {
102
+ const used = await sumUsageQuantity(services.db, {
103
+ workspaceId: input.workspaceId,
104
+ eventType: "agent_run.created",
105
+ since: startOfUtcMonth(),
106
+ });
107
+ if (used + input.requestedAgentRuns > limits.maxMonthlyAgentRunsPerWorkspace) {
108
+ return "monthly_agent_run_limit";
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+
114
+ function startOfUtcMonth(): Date {
115
+ const now = new Date();
116
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
117
+ }