@opengeni/core 0.28.1 → 1.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 (224) hide show
  1. package/README.md +1 -1
  2. package/dist/access/external-actor-authority.d.ts +47 -0
  3. package/dist/access/index.d.ts +182 -3
  4. package/dist/access/service-initiator.d.ts +3 -0
  5. package/dist/application/api-integration-servers.d.ts +20 -0
  6. package/dist/application/archived-session-imports.d.ts +6 -0
  7. package/dist/application/composer-submit.d.ts +24 -0
  8. package/dist/application/connect-authority.d.ts +13 -0
  9. package/dist/application/connect-operation.d.ts +31 -0
  10. package/dist/application/external-continuation.d.ts +15 -0
  11. package/dist/application/external-identity-lifecycle.d.ts +20 -0
  12. package/dist/application/external-link-work-admission.d.ts +12 -0
  13. package/dist/application/external-workspace-members.d.ts +34 -0
  14. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  15. package/dist/application/new-session-drafts.d.ts +39 -2
  16. package/dist/application/sandbox-recovery.d.ts +58 -0
  17. package/dist/application/session-commands.d.ts +26 -13
  18. package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
  19. package/dist/application/session-tenancy.d.ts +19 -0
  20. package/dist/application/user-resource-grants.d.ts +20 -0
  21. package/dist/billing/limits.d.ts +11 -0
  22. package/dist/canonical-human-identities.d.ts +7 -2
  23. package/dist/canonical-human-identities.js +17 -4
  24. package/dist/canonical-human-identities.js.map +1 -1
  25. package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
  26. package/dist/chunk-2MKMRGSY.js.map +1 -0
  27. package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
  28. package/dist/chunk-GVO3N37G.js.map +1 -0
  29. package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
  30. package/dist/chunk-LCQXNR62.js.map +1 -0
  31. package/dist/chunk-MM6N7CTO.js +387 -0
  32. package/dist/chunk-MM6N7CTO.js.map +1 -0
  33. package/dist/chunk-QO5GVFFO.js +235 -0
  34. package/dist/chunk-QO5GVFFO.js.map +1 -0
  35. package/dist/codex-model-availability.d.ts +14 -0
  36. package/dist/default-session-model.d.ts +139 -0
  37. package/dist/dependencies.d.ts +87 -4
  38. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  39. package/dist/domain/agent-config-resolution.d.ts +49 -0
  40. package/dist/domain/automations.d.ts +58 -0
  41. package/dist/domain/capabilities.d.ts +22 -4
  42. package/dist/domain/company-profile-agent-admin.d.ts +25 -0
  43. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  44. package/dist/domain/connector-tool-permissions.d.ts +36 -0
  45. package/dist/domain/direct-model-provider.d.ts +3 -0
  46. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  47. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  48. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  49. package/dist/domain/editable-artifacts/service.d.ts +4 -3
  50. package/dist/domain/environments.d.ts +5 -9
  51. package/dist/domain/external-creation-attribution.d.ts +6 -0
  52. package/dist/domain/file-owner.d.ts +8 -0
  53. package/dist/domain/github-action-policies.d.ts +36 -0
  54. package/dist/domain/github-repository-bindings.d.ts +117 -0
  55. package/dist/domain/github-skill-source.d.ts +5 -0
  56. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  57. package/dist/domain/insights-timing.d.ts +16 -0
  58. package/dist/domain/insights-usage.d.ts +27 -0
  59. package/dist/domain/insights.d.ts +28 -1
  60. package/dist/domain/knowledge-files.d.ts +20 -0
  61. package/dist/domain/knowledge-messages.d.ts +39 -0
  62. package/dist/domain/knowledge-messages.test.d.ts +1 -0
  63. package/dist/domain/knowledge-preparation.d.ts +10 -0
  64. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  65. package/dist/domain/knowledge-search.d.ts +15 -0
  66. package/dist/domain/knowledge-search.test.d.ts +1 -0
  67. package/dist/domain/knowledge.d.ts +9 -0
  68. package/dist/domain/mcp-account-bindings.d.ts +19 -0
  69. package/dist/domain/mcp-account-routes.d.ts +21 -0
  70. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  71. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  72. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  73. package/dist/domain/personal-connection-delegations.d.ts +87 -4
  74. package/dist/domain/pr-review.d.ts +249 -0
  75. package/dist/domain/resources.d.ts +24 -3
  76. package/dist/domain/scheduled-task-access.d.ts +247 -0
  77. package/dist/domain/scheduled-tasks.d.ts +103 -6
  78. package/dist/domain/session-connection-accounts.d.ts +16 -0
  79. package/dist/domain/session-file-access.d.ts +4 -0
  80. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  81. package/dist/domain/session-start-timing.d.ts +7 -0
  82. package/dist/domain/session-tool-policy.d.ts +93 -4
  83. package/dist/domain/sessions.d.ts +275 -28
  84. package/dist/domain/skill-imports.d.ts +1 -0
  85. package/dist/domain/skill-search.d.ts +30 -0
  86. package/dist/domain/skills.d.ts +117 -0
  87. package/dist/domain/slack-bot.d.ts +46 -1
  88. package/dist/domain/workspace-members.d.ts +8 -1
  89. package/dist/editable-artifact-live/application.d.ts +12 -3
  90. package/dist/editable-artifact-live/ports.d.ts +0 -1
  91. package/dist/editable-artifact-live/server.d.ts +5 -2
  92. package/dist/editable-artifact-live/ticket.d.ts +2 -2
  93. package/dist/editable-artifact-live/types.d.ts +14 -2
  94. package/dist/editable-artifact-live.js +2 -2
  95. package/dist/editable-artifacts.js +10 -4
  96. package/dist/index.d.ts +49 -3
  97. package/dist/index.js +18812 -6833
  98. package/dist/index.js.map +1 -1
  99. package/dist/managed-auth-session-sets.d.ts +131 -0
  100. package/dist/managed-auth-session-sets.js +51 -0
  101. package/dist/managed-auth-session-sets.js.map +1 -0
  102. package/dist/managed-session.d.ts +98 -11
  103. package/dist/model-catalog.d.ts +117 -0
  104. package/dist/product-usage-metrics.d.ts +30 -0
  105. package/dist/rigs/index.d.ts +22 -5
  106. package/dist/rigs/provider-images.d.ts +1 -1
  107. package/dist/sandbox/fleet.d.ts +59 -10
  108. package/dist/sandbox/routing.d.ts +22 -2
  109. package/dist/sandbox/runtime-settings.d.ts +56 -0
  110. package/dist/session-authorization.d.ts +38 -1
  111. package/dist/site-session-origin.d.ts +9 -0
  112. package/dist/transcription.d.ts +12 -1
  113. package/dist/turn-surface.d.ts +18 -0
  114. package/dist/user-presence.d.ts +32 -0
  115. package/package.json +18 -11
  116. package/src/access/external-actor-authority.ts +94 -0
  117. package/src/access/index.ts +1386 -44
  118. package/src/access/service-initiator.ts +50 -0
  119. package/src/application/api-integration-servers.ts +215 -0
  120. package/src/application/archived-session-imports.ts +161 -0
  121. package/src/application/composer-submit.ts +77 -0
  122. package/src/application/connect-authority.ts +77 -0
  123. package/src/application/connect-operation.ts +80 -0
  124. package/src/application/external-continuation.ts +129 -0
  125. package/src/application/external-identity-lifecycle.ts +48 -0
  126. package/src/application/external-link-work-admission.ts +90 -0
  127. package/src/application/external-workspace-members.ts +232 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +222 -29
  130. package/src/application/sandbox-recovery.ts +78 -0
  131. package/src/application/session-commands.ts +430 -73
  132. package/src/application/session-mcp-credential-rotation.ts +217 -0
  133. package/src/application/session-tenancy.ts +445 -0
  134. package/src/application/user-resource-grants.ts +154 -0
  135. package/src/billing/limits.ts +103 -24
  136. package/src/canonical-human-identities.ts +22 -3
  137. package/src/codex-model-availability.ts +136 -0
  138. package/src/default-session-model.ts +656 -0
  139. package/src/dependencies.ts +97 -5
  140. package/src/developer-setup-credential-ceiling.ts +60 -0
  141. package/src/domain/agent-config-resolution.ts +148 -0
  142. package/src/domain/automations.ts +270 -0
  143. package/src/domain/capabilities.ts +244 -151
  144. package/src/domain/company-profile-agent-admin.ts +56 -0
  145. package/src/domain/connection-account-selection-error.ts +19 -0
  146. package/src/domain/connector-tool-permissions.ts +371 -0
  147. package/src/domain/direct-model-provider.ts +62 -0
  148. package/src/domain/editable-artifacts/agent-application.ts +61 -15
  149. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  150. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  151. package/src/domain/editable-artifacts/ports.ts +11 -0
  152. package/src/domain/editable-artifacts/service.ts +32 -1
  153. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  154. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  155. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  156. package/src/domain/environments.ts +25 -45
  157. package/src/domain/external-creation-attribution.ts +22 -0
  158. package/src/domain/file-owner.ts +67 -0
  159. package/src/domain/github-action-policies.ts +169 -0
  160. package/src/domain/github-repository-bindings.ts +295 -0
  161. package/src/domain/github-skill-source.ts +233 -0
  162. package/src/domain/governed-learning-slack-publication.ts +157 -0
  163. package/src/domain/insights-timing.ts +95 -0
  164. package/src/domain/insights-usage.ts +67 -0
  165. package/src/domain/insights.ts +270 -169
  166. package/src/domain/knowledge-files.ts +46 -0
  167. package/src/domain/knowledge-messages.test.ts +91 -0
  168. package/src/domain/knowledge-messages.ts +106 -0
  169. package/src/domain/knowledge-preparation.test.ts +276 -0
  170. package/src/domain/knowledge-preparation.ts +147 -0
  171. package/src/domain/knowledge-search.test.ts +561 -0
  172. package/src/domain/knowledge-search.ts +256 -0
  173. package/src/domain/knowledge.ts +133 -0
  174. package/src/domain/mcp-account-bindings.ts +171 -0
  175. package/src/domain/mcp-account-routes.ts +121 -0
  176. package/src/domain/native-mcp-connection-admission.ts +14 -0
  177. package/src/domain/organization-integration-catalog.ts +88 -0
  178. package/src/domain/organization-membership-lifecycle.ts +10 -0
  179. package/src/domain/personal-connection-delegations.ts +758 -115
  180. package/src/domain/pr-review.ts +687 -0
  181. package/src/domain/resources.ts +182 -25
  182. package/src/domain/scheduled-task-access.ts +1052 -0
  183. package/src/domain/scheduled-tasks.ts +1952 -215
  184. package/src/domain/session-connection-accounts.ts +67 -0
  185. package/src/domain/session-file-access.ts +47 -0
  186. package/src/domain/session-native-connection-replacement.ts +64 -0
  187. package/src/domain/session-start-timing.ts +55 -0
  188. package/src/domain/session-tool-policy.ts +443 -19
  189. package/src/domain/sessions.ts +3417 -619
  190. package/src/domain/skill-imports.ts +137 -17
  191. package/src/domain/skill-search.ts +221 -0
  192. package/src/domain/skills.ts +104 -0
  193. package/src/domain/slack-bot.ts +128 -3
  194. package/src/domain/workspace-members.ts +34 -2
  195. package/src/editable-artifact-live/application.ts +29 -4
  196. package/src/editable-artifact-live/ports.ts +0 -1
  197. package/src/editable-artifact-live/server.ts +167 -13
  198. package/src/editable-artifact-live/ticket.ts +87 -6
  199. package/src/editable-artifact-live/types.ts +16 -2
  200. package/src/editable-artifact-live/wire.ts +0 -10
  201. package/src/index.ts +69 -4
  202. package/src/managed-auth-session-sets.ts +371 -0
  203. package/src/managed-session.ts +520 -2
  204. package/src/model-catalog.ts +873 -0
  205. package/src/product-usage-metrics.ts +101 -0
  206. package/src/rigs/index.ts +179 -63
  207. package/src/rigs/provider-images.ts +2 -3
  208. package/src/sandbox/fleet.ts +471 -59
  209. package/src/sandbox/routing.ts +160 -33
  210. package/src/sandbox/runtime-settings.ts +281 -0
  211. package/src/session-authorization.ts +262 -78
  212. package/src/site-session-origin.ts +22 -0
  213. package/src/transcription.ts +13 -1
  214. package/src/turn-surface.ts +45 -0
  215. package/src/user-presence.ts +134 -0
  216. package/dist/chunk-6WKPWE5S.js.map +0 -1
  217. package/dist/chunk-IBOEYG6N.js +0 -34
  218. package/dist/chunk-IBOEYG6N.js.map +0 -1
  219. package/dist/chunk-JJU6I5XD.js.map +0 -1
  220. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  221. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  222. package/dist/domain/packs.d.ts +0 -55
  223. package/src/domain/durable-learning-slack-publication.ts +0 -202
  224. package/src/domain/packs.ts +0 -556
@@ -16,12 +16,11 @@ import type {
16
16
  WorkspaceInferenceControlRequest,
17
17
  WorkspaceInferenceControlResponse,
18
18
  } from "@opengeni/contracts";
19
+ import { DraftTimelineAnnotations } from "@opengeni/contracts";
20
+ import type { Settings } from "@opengeni/config";
21
+ import { HTTPException } from "hono/http-exception";
19
22
  import {
20
- DraftTimelineAnnotations,
21
- latencyModeForMetadata,
22
- reasoningEffortForMetadata,
23
- } from "@opengeni/contracts";
24
- import {
23
+ checkWorkspaceAllowance,
25
24
  deleteSessionQueueItemInTransaction,
26
25
  editQueuedTurnInTransaction,
27
26
  getComposerDraftInTransaction,
@@ -29,9 +28,12 @@ import {
29
28
  getSessionEvent,
30
29
  getWorkspaceControlEvent,
31
30
  getSessionQueueSnapshot,
31
+ frozenInitiatorForCommandActor,
32
+ isCodexBilledTurn,
32
33
  moveQueuedTurnInTransaction,
33
34
  mutateSessionControlInTransaction,
34
35
  mutateWorkspaceControlInTransaction,
36
+ setWorkspacePauseTimerInTransaction,
35
37
  projectEffectiveControlForRelatedAccess,
36
38
  runIdempotentPersistenceTransaction,
37
39
  saveComposerDraftInTransaction,
@@ -39,6 +41,7 @@ import {
39
41
  serializeEffectiveSessionControl,
40
42
  steerAgentSessionInTransaction,
41
43
  steerQueuedTurnInTransaction,
44
+ workspaceControlRequestLockTimeoutMs,
42
45
  withWorkspaceRls,
43
46
  withWorkspaceSessionActivityRls,
44
47
  withWorkspaceSubjectRls,
@@ -55,6 +58,9 @@ import {
55
58
  import type { SessionWorkflowClient } from "../dependencies";
56
59
  import { normalizeResources } from "../domain/resources";
57
60
  import { validateDraftTimelineAnnotations } from "../domain/timeline-annotations";
61
+ import { initiatingHumanForAllowance } from "../domain/sessions";
62
+ import { modelFundingForAdmission } from "../billing/limits";
63
+ import { resolveWorkspaceCatalogSettings } from "../model-catalog";
58
64
  import {
59
65
  requireSessionAuthorization,
60
66
  type ResolvedSessionAuthorization,
@@ -150,6 +156,51 @@ function agentActor(context: AgentSessionCommandContext) {
150
156
  };
151
157
  }
152
158
 
159
+ /**
160
+ * Called inside the command's exact live-attempt fence, after receipt recovery
161
+ * but before any fresh work or Steer interruption. Attribution comes from the
162
+ * caller's accepted turn, never the target creator or request subject.
163
+ */
164
+ async function assertFreshAgentCommandAllowance(
165
+ tx: SessionActivityDatabase,
166
+ deps: { settings?: Settings },
167
+ context: AgentSessionCommandContext,
168
+ targetSessionId: string,
169
+ ): Promise<void> {
170
+ const target = await getSession(tx, context.workspaceId, targetSessionId);
171
+ if (!target || target.accountId !== context.accountId) {
172
+ throw new HTTPException(404, { message: "Target session not found" });
173
+ }
174
+ if (deps.settings) {
175
+ const { settings } = await resolveWorkspaceCatalogSettings(tx, deps.settings, {
176
+ accountId: target.accountId,
177
+ workspaceId: context.workspaceId,
178
+ retainedProductModelId: target.model,
179
+ });
180
+ const codexBilled = await isCodexBilledTurn({
181
+ db: tx,
182
+ settings,
183
+ workspaceId: context.workspaceId,
184
+ model: target.model,
185
+ });
186
+ if (modelFundingForAdmission(settings, target.model, codexBilled).fundedWithoutCredits) {
187
+ return;
188
+ }
189
+ }
190
+ const frozen = await frozenInitiatorForCommandActor(tx, context.workspaceId, agentActor(context));
191
+ const refusal = await checkWorkspaceAllowance(tx, {
192
+ accountId: target.accountId,
193
+ workspaceId: context.workspaceId,
194
+ subjectId: initiatingHumanForAllowance(frozen),
195
+ });
196
+ if (refusal) {
197
+ throw new HTTPException(402, {
198
+ message: refusal.message,
199
+ cause: { allowed: false, ...refusal },
200
+ });
201
+ }
202
+ }
203
+
153
204
  /**
154
205
  * Retry only one operation-keyed session command transaction. Callers keep
155
206
  * event publication and Temporal wake delivery after this returns, so a
@@ -185,18 +236,68 @@ async function runSessionCommandPersistenceTransaction<T>(
185
236
  );
186
237
  }
187
238
 
188
- async function publishAndWakeAgentCommand(
239
+ type SessionCommandPostCommitDeps = {
240
+ /** Historical name; now schedules replayable follow-up for every interactive session command. */
241
+ schedulePromptPostCommit?: ((task: () => Promise<void>) => void) | undefined;
242
+ };
243
+
244
+ type SessionCommandPostCommitStep = {
245
+ kind: "session_event_fanout" | "workspace_control_fanout" | "workflow_wake";
246
+ run: () => Promise<void>;
247
+ };
248
+
249
+ /**
250
+ * A committed interactive command must never wait for ephemeral fanout or an
251
+ * immediate Temporal nudge. Durable events and wake outboxes are the recovery
252
+ * contract; these steps only reduce propagation latency.
253
+ */
254
+ function scheduleSessionCommandPostCommit(
255
+ deps: SessionCommandPostCommitDeps,
256
+ operation: string,
257
+ steps: SessionCommandPostCommitStep[],
258
+ ): void {
259
+ const task = async () => {
260
+ await Promise.all(
261
+ steps.map(async (step) => {
262
+ try {
263
+ await step.run();
264
+ } catch {
265
+ console.warn("[session-commands] replayable post-commit step failed", {
266
+ errorClass: "SessionCommandPostCommitError",
267
+ errorCode: "session_command_post_commit_failed",
268
+ origin: "core",
269
+ operation,
270
+ step: step.kind,
271
+ });
272
+ }
273
+ }),
274
+ );
275
+ };
276
+ try {
277
+ const schedule =
278
+ deps.schedulePromptPostCommit ??
279
+ ((pending: () => Promise<void>) => {
280
+ void pending();
281
+ });
282
+ schedule(task);
283
+ } catch {
284
+ console.warn("[session-commands] post-commit scheduling failed", {
285
+ errorClass: "SessionCommandPostCommitScheduleError",
286
+ errorCode: "session_command_post_commit_schedule_failed",
287
+ origin: "core",
288
+ operation,
289
+ });
290
+ }
291
+ }
292
+
293
+ async function wakeSessionCommand(
189
294
  deps: {
190
- db: Database;
191
- bus: EventBus;
192
295
  workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
193
- sessionAuthorization?: SessionAuthorizationPort | null;
194
296
  },
195
297
  input: {
196
298
  accountId: string;
197
299
  workspaceId: string;
198
300
  sessionId: string;
199
- eventIds: string[];
200
301
  workflowId: string;
201
302
  wakeRevision: number | null;
202
303
  shouldSignal: boolean;
@@ -204,7 +305,6 @@ async function publishAndWakeAgentCommand(
204
305
  controlRequested?: boolean;
205
306
  },
206
307
  ): Promise<void> {
207
- await publishSessionEventIds(deps, input.workspaceId, input.sessionId, input.eventIds);
208
308
  if (!input.shouldSignal || input.wakeRevision === null) return;
209
309
  try {
210
310
  await deps.workflowClient.wakeSessionWorkflow({
@@ -218,14 +318,11 @@ async function publishAndWakeAgentCommand(
218
318
  : {}),
219
319
  });
220
320
  } catch {
221
- console.warn(
222
- "[session-commands] immediate Agent command wake failed; durable outbox will retry",
223
- {
224
- errorClass: "WorkflowWakeOperationError",
225
- errorCode: "agent_command_wake_failed",
226
- origin: "core",
227
- },
228
- );
321
+ console.warn("[session-commands] immediate command wake failed; durable outbox will retry", {
322
+ errorClass: "WorkflowWakeOperationError",
323
+ errorCode: "session_command_wake_failed",
324
+ origin: "core",
325
+ });
229
326
  }
230
327
  }
231
328
 
@@ -290,10 +387,11 @@ async function publishWorkspaceControlEvent(
290
387
  export async function sendAgentSessionMessage(
291
388
  deps: {
292
389
  db: Database;
390
+ settings?: Settings;
293
391
  bus: EventBus;
294
392
  workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
295
393
  sessionAuthorization?: SessionAuthorizationPort | null;
296
- },
394
+ } & SessionCommandPostCommitDeps,
297
395
  context: AgentSessionCommandContext,
298
396
  input: { targetSessionId: string; text: string; idempotencyKey: string },
299
397
  ) {
@@ -312,30 +410,57 @@ export async function sendAgentSessionMessage(
312
410
  actor: agentActor(context),
313
411
  operationKey: input.idempotencyKey,
314
412
  text: input.text,
413
+ assertFreshAdmission: (scoped) =>
414
+ assertFreshAgentCommandAllowance(scoped, deps, context, input.targetSessionId),
415
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
315
416
  }),
316
417
  },
317
418
  );
318
- await publishAndWakeAgentCommand(deps, {
319
- accountId: context.accountId,
320
- workspaceId: context.workspaceId,
321
- sessionId: input.targetSessionId,
322
- eventIds: result.eventIds,
323
- workflowId: result.workflowId,
324
- wakeRevision: result.wakeRevision,
325
- shouldSignal: result.shouldSignal,
326
- interruptionCount: 0,
327
- });
328
- await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
419
+ scheduleSessionCommandPostCommit(deps, "agent_message", [
420
+ {
421
+ kind: "session_event_fanout",
422
+ run: async () =>
423
+ await publishSessionEventIds(
424
+ deps,
425
+ context.workspaceId,
426
+ input.targetSessionId,
427
+ result.eventIds,
428
+ ),
429
+ },
430
+ {
431
+ kind: "workspace_control_fanout",
432
+ run: async () =>
433
+ await publishWorkspaceControlEvent(
434
+ deps,
435
+ context.workspaceId,
436
+ result.workspaceControlEventId,
437
+ ),
438
+ },
439
+ {
440
+ kind: "workflow_wake",
441
+ run: async () =>
442
+ await wakeSessionCommand(deps, {
443
+ accountId: context.accountId,
444
+ workspaceId: context.workspaceId,
445
+ sessionId: input.targetSessionId,
446
+ workflowId: result.workflowId,
447
+ wakeRevision: result.wakeRevision,
448
+ shouldSignal: result.shouldSignal,
449
+ interruptionCount: 0,
450
+ }),
451
+ },
452
+ ]);
329
453
  return result;
330
454
  }
331
455
 
332
456
  export async function steerAgentSession(
333
457
  deps: {
334
458
  db: Database;
459
+ settings?: Settings;
335
460
  bus: EventBus;
336
461
  workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
337
462
  sessionAuthorization?: SessionAuthorizationPort | null;
338
- },
463
+ } & SessionCommandPostCommitDeps,
339
464
  context: AgentSessionCommandContext,
340
465
  input: {
341
466
  targetSessionId: string;
@@ -358,21 +483,47 @@ export async function steerAgentSession(
358
483
  actor: agentActor(context),
359
484
  operationKey: input.idempotencyKey,
360
485
  instruction: input.instruction,
486
+ assertFreshAdmission: (scoped) =>
487
+ assertFreshAgentCommandAllowance(scoped, deps, context, input.targetSessionId),
488
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
361
489
  }),
362
490
  },
363
491
  );
364
- await publishAndWakeAgentCommand(deps, {
365
- accountId: context.accountId,
366
- workspaceId: context.workspaceId,
367
- sessionId: input.targetSessionId,
368
- eventIds: result.eventIds,
369
- workflowId: result.workflowId,
370
- wakeRevision: result.wakeRevision,
371
- shouldSignal: result.shouldSignal,
372
- interruptionCount: result.interruptionCount,
373
- controlRequested: true,
374
- });
375
- await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
492
+ scheduleSessionCommandPostCommit(deps, "agent_steer", [
493
+ {
494
+ kind: "session_event_fanout",
495
+ run: async () =>
496
+ await publishSessionEventIds(
497
+ deps,
498
+ context.workspaceId,
499
+ input.targetSessionId,
500
+ result.eventIds,
501
+ ),
502
+ },
503
+ {
504
+ kind: "workspace_control_fanout",
505
+ run: async () =>
506
+ await publishWorkspaceControlEvent(
507
+ deps,
508
+ context.workspaceId,
509
+ result.workspaceControlEventId,
510
+ ),
511
+ },
512
+ {
513
+ kind: "workflow_wake",
514
+ run: async () =>
515
+ await wakeSessionCommand(deps, {
516
+ accountId: context.accountId,
517
+ workspaceId: context.workspaceId,
518
+ sessionId: input.targetSessionId,
519
+ workflowId: result.workflowId,
520
+ wakeRevision: result.wakeRevision,
521
+ shouldSignal: result.shouldSignal,
522
+ interruptionCount: result.interruptionCount,
523
+ controlRequested: true,
524
+ }),
525
+ },
526
+ ]);
376
527
  return result;
377
528
  }
378
529
 
@@ -380,9 +531,12 @@ export async function controlAgentSessionWorkstream(
380
531
  deps: {
381
532
  db: Database;
382
533
  bus: EventBus;
383
- workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
534
+ workflowClient: Pick<
535
+ SessionWorkflowClient,
536
+ "requestSessionWorkflowWakeDispatch" | "wakeSessionWorkflow"
537
+ >;
384
538
  sessionAuthorization?: SessionAuthorizationPort | null;
385
- },
539
+ } & SessionCommandPostCommitDeps,
386
540
  context: AgentSessionCommandContext,
387
541
  input: {
388
542
  targetSessionId: string;
@@ -412,14 +566,60 @@ export async function controlAgentSessionWorkstream(
412
566
  operationKey: input.idempotencyKey,
413
567
  action: input.action,
414
568
  reason: input.reason ?? null,
569
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
415
570
  }),
416
571
  },
417
572
  );
418
- await publishSessionEventIds(deps, context.workspaceId, input.targetSessionId, [
419
- result.sessionControlEventId,
573
+ scheduleSessionCommandPostCommit(deps, "agent_control", [
574
+ ...(result.sessionControlEventId
575
+ ? [
576
+ {
577
+ kind: "session_event_fanout" as const,
578
+ run: async () =>
579
+ await publishSessionEventIds(deps, context.workspaceId, input.targetSessionId, [
580
+ result.sessionControlEventId!,
581
+ ]),
582
+ },
583
+ ]
584
+ : []),
585
+ ...(result.workspaceControlEventId
586
+ ? [
587
+ {
588
+ kind: "workspace_control_fanout" as const,
589
+ run: async () =>
590
+ await publishWorkspaceControlEvent(
591
+ deps,
592
+ context.workspaceId,
593
+ result.workspaceControlEventId!,
594
+ ),
595
+ },
596
+ ]
597
+ : []),
598
+ ...(result.wakeCount > 0
599
+ ? [
600
+ {
601
+ kind: "workflow_wake" as const,
602
+ run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
603
+ },
604
+ ]
605
+ : []),
606
+ {
607
+ kind: "workflow_wake",
608
+ run: async () => {
609
+ if (!result.workflowWake) return;
610
+ await wakeSessionCommand(deps, {
611
+ accountId: result.workflowWake.accountId,
612
+ workspaceId: result.workflowWake.workspaceId,
613
+ sessionId: result.workflowWake.sessionId,
614
+ workflowId: result.workflowWake.temporalWorkflowId,
615
+ wakeRevision: result.workflowWake.wakeRevision,
616
+ shouldSignal: true,
617
+ interruptionCount: result.interruptionCount,
618
+ controlRequested: input.action === "pause",
619
+ });
620
+ },
621
+ },
420
622
  ]);
421
- await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
422
- await requestControlWakeDispatch(deps, result.wakeCount);
423
623
  return { ...result, authorization };
424
624
  }
425
625
 
@@ -479,7 +679,7 @@ export async function moveHumanQueuePrompt(
479
679
  db: Database;
480
680
  bus: EventBus;
481
681
  sessionAuthorization?: SessionAuthorizationPort | null;
482
- },
682
+ } & SessionCommandPostCommitDeps,
483
683
  context: HumanSessionCommandContext,
484
684
  turnId: string,
485
685
  input: MoveSessionQueueItemRequest,
@@ -499,6 +699,7 @@ export async function moveHumanQueuePrompt(
499
699
  expectedQueueVersion: input.expectedQueueVersion,
500
700
  actor: { type: "human", subjectId: context.subjectId },
501
701
  operationKey: input.clientEventId,
702
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
502
703
  }),
503
704
  },
504
705
  );
@@ -511,7 +712,13 @@ export async function moveHumanQueuePrompt(
511
712
  authorization?.relatedSessionAccess ?? "root",
512
713
  ),
513
714
  };
514
- await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
715
+ scheduleSessionCommandPostCommit(deps, "human_queue_move", [
716
+ {
717
+ kind: "session_event_fanout",
718
+ run: async () =>
719
+ await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds),
720
+ },
721
+ ]);
515
722
  return response;
516
723
  }
517
724
 
@@ -520,7 +727,7 @@ export async function deleteHumanQueuePrompt(
520
727
  db: Database;
521
728
  bus: EventBus;
522
729
  sessionAuthorization?: SessionAuthorizationPort | null;
523
- },
730
+ } & SessionCommandPostCommitDeps,
524
731
  context: HumanSessionCommandContext,
525
732
  turnId: string,
526
733
  input: DeleteSessionQueueItemRequest,
@@ -540,6 +747,7 @@ export async function deleteHumanQueuePrompt(
540
747
  actor: { type: "human", subjectId: context.subjectId },
541
748
  operationKey: input.clientEventId,
542
749
  reason: input.reason ?? null,
750
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
543
751
  }),
544
752
  },
545
753
  );
@@ -552,7 +760,13 @@ export async function deleteHumanQueuePrompt(
552
760
  authorization?.relatedSessionAccess ?? "root",
553
761
  ),
554
762
  };
555
- await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
763
+ scheduleSessionCommandPostCommit(deps, "human_queue_delete", [
764
+ {
765
+ kind: "session_event_fanout",
766
+ run: async () =>
767
+ await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds),
768
+ },
769
+ ]);
556
770
  return response;
557
771
  }
558
772
 
@@ -561,7 +775,7 @@ export async function editHumanQueuePrompt(
561
775
  db: Database;
562
776
  bus: EventBus;
563
777
  sessionAuthorization?: SessionAuthorizationPort | null;
564
- },
778
+ } & SessionCommandPostCommitDeps,
565
779
  context: HumanSessionCommandContext,
566
780
  turnId: string,
567
781
  input: EditSessionQueueItemRequest,
@@ -582,6 +796,7 @@ export async function editHumanQueuePrompt(
582
796
  replaceDraft: input.replaceDraft,
583
797
  actor: { type: "human", subjectId: context.subjectId },
584
798
  operationKey: input.clientEventId,
799
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
585
800
  }),
586
801
  },
587
802
  );
@@ -595,7 +810,13 @@ export async function editHumanQueuePrompt(
595
810
  ),
596
811
  draft: composerDraft(result.draft)!,
597
812
  };
598
- await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
813
+ scheduleSessionCommandPostCommit(deps, "human_queue_edit", [
814
+ {
815
+ kind: "session_event_fanout",
816
+ run: async () =>
817
+ await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds),
818
+ },
819
+ ]);
599
820
  return response;
600
821
  }
601
822
 
@@ -603,8 +824,9 @@ export async function steerHumanQueuePrompt(
603
824
  deps: {
604
825
  db: Database;
605
826
  bus: EventBus;
827
+ workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
606
828
  sessionAuthorization?: SessionAuthorizationPort | null;
607
- },
829
+ } & SessionCommandPostCommitDeps,
608
830
  context: HumanSessionCommandContext,
609
831
  turnId: string,
610
832
  input: SteerSessionQueueItemRequest,
@@ -624,6 +846,7 @@ export async function steerHumanQueuePrompt(
624
846
  controlEtag: input.controlEtag ?? null,
625
847
  actor: { type: "human", subjectId: context.subjectId },
626
848
  operationKey: input.clientEventId,
849
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
627
850
  }),
628
851
  },
629
852
  );
@@ -636,8 +859,36 @@ export async function steerHumanQueuePrompt(
636
859
  authorization?.relatedSessionAccess ?? "root",
637
860
  ),
638
861
  };
639
- await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
640
- await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
862
+ scheduleSessionCommandPostCommit(deps, "human_queue_steer", [
863
+ {
864
+ kind: "session_event_fanout",
865
+ run: async () =>
866
+ await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds),
867
+ },
868
+ {
869
+ kind: "workspace_control_fanout",
870
+ run: async () =>
871
+ await publishWorkspaceControlEvent(
872
+ deps,
873
+ context.workspaceId,
874
+ result.workspaceControlEventId,
875
+ ),
876
+ },
877
+ {
878
+ kind: "workflow_wake",
879
+ run: async () =>
880
+ await wakeSessionCommand(deps, {
881
+ accountId: context.accountId,
882
+ workspaceId: context.workspaceId,
883
+ sessionId: context.sessionId,
884
+ workflowId: result.workflowId,
885
+ wakeRevision: result.wakeRevision,
886
+ shouldSignal: true,
887
+ interruptionCount: result.interruptionCount,
888
+ controlRequested: true,
889
+ }),
890
+ },
891
+ ]);
641
892
  return response;
642
893
  }
643
894
 
@@ -645,12 +896,19 @@ export async function controlHumanSessionWorkstreamWithOutcome(
645
896
  deps: {
646
897
  db: Database;
647
898
  bus: EventBus;
648
- workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
899
+ workflowClient: Pick<
900
+ SessionWorkflowClient,
901
+ "requestSessionWorkflowWakeDispatch" | "wakeSessionWorkflow"
902
+ >;
649
903
  sessionAuthorization?: SessionAuthorizationPort | null;
650
- },
904
+ } & SessionCommandPostCommitDeps,
651
905
  context: HumanSessionCommandContext,
652
906
  input: SessionControlRequest,
653
- ): Promise<{ response: SessionControlResponse; replay: boolean }> {
907
+ ): Promise<{
908
+ response: SessionControlResponse;
909
+ outcome: "changed" | "unchanged" | "replayed";
910
+ replay: boolean;
911
+ }> {
654
912
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
655
913
  const result = await runSessionCommandPersistenceTransaction(
656
914
  deps,
@@ -670,6 +928,7 @@ export async function controlHumanSessionWorkstreamWithOutcome(
670
928
  action: input.action,
671
929
  reason: input.reason ?? null,
672
930
  expectedControlEtag: input.expectedControlEtag ?? null,
931
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
673
932
  }),
674
933
  },
675
934
  );
@@ -685,12 +944,56 @@ export async function controlHumanSessionWorkstreamWithOutcome(
685
944
  cancelledSessionCount: result.cancelledSessionCount,
686
945
  cancelledTurnCount: result.cancelledTurnCount,
687
946
  };
688
- for (const affected of result.affectedSessionEvents) {
689
- await publishSessionEventIds(deps, context.workspaceId, affected.sessionId, affected.eventIds);
690
- }
691
- await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
692
- await requestControlWakeDispatch(deps, result.wakeCount);
693
- return { response, replay: result.replay };
947
+ scheduleSessionCommandPostCommit(deps, "human_control", [
948
+ ...result.affectedSessionEvents.map((affected) => ({
949
+ kind: "session_event_fanout" as const,
950
+ run: async () =>
951
+ await publishSessionEventIds(
952
+ deps,
953
+ context.workspaceId,
954
+ affected.sessionId,
955
+ affected.eventIds,
956
+ ),
957
+ })),
958
+ ...(result.workspaceControlEventId
959
+ ? [
960
+ {
961
+ kind: "workspace_control_fanout" as const,
962
+ run: async () =>
963
+ await publishWorkspaceControlEvent(
964
+ deps,
965
+ context.workspaceId,
966
+ result.workspaceControlEventId!,
967
+ ),
968
+ },
969
+ ]
970
+ : []),
971
+ ...(result.wakeCount > 0
972
+ ? [
973
+ {
974
+ kind: "workflow_wake" as const,
975
+ run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
976
+ },
977
+ ]
978
+ : []),
979
+ {
980
+ kind: "workflow_wake",
981
+ run: async () => {
982
+ if (!result.workflowWake) return;
983
+ await wakeSessionCommand(deps, {
984
+ accountId: result.workflowWake.accountId,
985
+ workspaceId: result.workflowWake.workspaceId,
986
+ sessionId: result.workflowWake.sessionId,
987
+ workflowId: result.workflowWake.temporalWorkflowId,
988
+ wakeRevision: result.workflowWake.wakeRevision,
989
+ shouldSignal: true,
990
+ interruptionCount: result.interruptionCount,
991
+ controlRequested: input.action === "pause",
992
+ });
993
+ },
994
+ },
995
+ ]);
996
+ return { response, outcome: result.outcome, replay: result.replay };
694
997
  }
695
998
 
696
999
  /** Backward-compatible response path used by the REST control route. */
@@ -707,7 +1010,7 @@ export async function controlHumanWorkspace(
707
1010
  db: Database;
708
1011
  bus: EventBus;
709
1012
  workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
710
- },
1013
+ } & SessionCommandPostCommitDeps,
711
1014
  context: Omit<HumanSessionCommandContext, "sessionId">,
712
1015
  input: WorkspaceInferenceControlRequest,
713
1016
  ): Promise<WorkspaceInferenceControlResponse> {
@@ -721,6 +1024,7 @@ export async function controlHumanWorkspace(
721
1024
  action: input.action,
722
1025
  reason: input.reason ?? null,
723
1026
  expectedRevision: input.expectedRevision ?? null,
1027
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
724
1028
  }),
725
1029
  ),
726
1030
  );
@@ -731,8 +1035,29 @@ export async function controlHumanWorkspace(
731
1035
  interruptionCount: result.interruptionCount,
732
1036
  wakeCount: result.wakeCount,
733
1037
  };
734
- await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
735
- await requestControlWakeDispatch(deps, result.wakeCount);
1038
+ scheduleSessionCommandPostCommit(deps, "human_workspace_control", [
1039
+ ...(result.workspaceControlEventId
1040
+ ? [
1041
+ {
1042
+ kind: "workspace_control_fanout" as const,
1043
+ run: async () =>
1044
+ await publishWorkspaceControlEvent(
1045
+ deps,
1046
+ context.workspaceId,
1047
+ result.workspaceControlEventId!,
1048
+ ),
1049
+ },
1050
+ ]
1051
+ : []),
1052
+ ...(result.wakeCount > 0
1053
+ ? [
1054
+ {
1055
+ kind: "workflow_wake" as const,
1056
+ run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
1057
+ },
1058
+ ]
1059
+ : []),
1060
+ ]);
736
1061
  return response;
737
1062
  }
738
1063
 
@@ -762,8 +1087,8 @@ export async function getHumanComposerDraft(
762
1087
  annotations: [],
763
1088
  resources: [],
764
1089
  model: session.model,
765
- reasoningEffort: reasoningEffortForMetadata(session.metadata, "medium"),
766
- latencyMode: latencyModeForMetadata(session.metadata, "standard"),
1090
+ reasoningEffort: session.reasoningEffort,
1091
+ latencyMode: session.latencyMode,
767
1092
  sourceTurnId: null,
768
1093
  sourceTurnVersion: null,
769
1094
  updatedAt: null,
@@ -794,8 +1119,40 @@ export async function saveHumanComposerDraft(
794
1119
  annotations,
795
1120
  resources: normalizeResources(input.resources),
796
1121
  subjectId: context.subjectId,
1122
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
797
1123
  }),
798
1124
  ),
799
1125
  );
800
1126
  return composerDraft(row)!;
801
1127
  }
1128
+
1129
+ export async function controlHumanWorkspaceTimer(
1130
+ deps: Parameters<typeof controlHumanWorkspace>[0],
1131
+ context: Parameters<typeof controlHumanWorkspace>[1],
1132
+ input: import("@opengeni/contracts").WorkspacePauseTimerRequest,
1133
+ ): Promise<void> {
1134
+ const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
1135
+ scoped.transaction((tx) =>
1136
+ setWorkspacePauseTimerInTransaction(tx as unknown as Database, {
1137
+ ...input,
1138
+ ...context,
1139
+ }),
1140
+ ),
1141
+ );
1142
+ scheduleSessionCommandPostCommit(deps, "human_workspace_timer", [
1143
+ ...(result.workspaceControlEventId
1144
+ ? [
1145
+ {
1146
+ kind: "workspace_control_fanout" as const,
1147
+ run: async () =>
1148
+ await publishWorkspaceControlEvent(
1149
+ deps,
1150
+ context.workspaceId,
1151
+ result.workspaceControlEventId!,
1152
+ ),
1153
+ },
1154
+ ]
1155
+ : []),
1156
+ { kind: "workflow_wake" as const, run: async () => await requestControlWakeDispatch(deps, 1) },
1157
+ ]);
1158
+ }