@opengeni/core 0.21.10 → 0.24.1

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 (116) hide show
  1. package/dist/access/index.d.ts +1 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +2 -2
  4. package/dist/billing/limits.d.ts +1 -1
  5. package/dist/chunk-6WKPWE5S.js +2380 -0
  6. package/dist/chunk-6WKPWE5S.js.map +1 -0
  7. package/dist/chunk-JJU6I5XD.js +2713 -0
  8. package/dist/chunk-JJU6I5XD.js.map +1 -0
  9. package/dist/chunk-NYPMQ7NO.js +2270 -0
  10. package/dist/chunk-NYPMQ7NO.js.map +1 -0
  11. package/dist/dependencies.d.ts +26 -6
  12. package/dist/domain/capabilities.d.ts +28 -3
  13. package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
  14. package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
  15. package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
  16. package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
  17. package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
  18. package/dist/domain/editable-artifacts/errors.d.ts +47 -0
  19. package/dist/domain/editable-artifacts/hash.d.ts +11 -0
  20. package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
  21. package/dist/domain/editable-artifacts/index.d.ts +14 -0
  22. package/dist/domain/editable-artifacts/ports.d.ts +341 -0
  23. package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
  24. package/dist/domain/editable-artifacts/service.d.ts +105 -0
  25. package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
  26. package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
  27. package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
  28. package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
  29. package/dist/domain/editable-artifacts/types.d.ts +410 -0
  30. package/dist/domain/memory-slack-delivery.d.ts +19 -0
  31. package/dist/domain/packs.d.ts +29 -1
  32. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  33. package/dist/domain/scheduled-tasks.d.ts +2 -2
  34. package/dist/domain/session-tool-policy.d.ts +2 -2
  35. package/dist/domain/sessions.d.ts +14 -6
  36. package/dist/domain/skill-imports.d.ts +32 -0
  37. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  38. package/dist/domain/timeline-annotations.d.ts +5 -0
  39. package/dist/domain/video-generation-capabilities.d.ts +9 -0
  40. package/dist/domain/video-generation.d.ts +47 -0
  41. package/dist/editable-artifact-live/application.d.ts +69 -0
  42. package/dist/editable-artifact-live/errors.d.ts +1 -0
  43. package/dist/editable-artifact-live/index.d.ts +8 -0
  44. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  45. package/dist/editable-artifact-live/ports.d.ts +100 -0
  46. package/dist/editable-artifact-live/server.d.ts +71 -0
  47. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  48. package/dist/editable-artifact-live/types.d.ts +213 -0
  49. package/dist/editable-artifact-live/wire.d.ts +34 -0
  50. package/dist/editable-artifact-live.d.ts +2 -0
  51. package/dist/editable-artifact-live.js +58 -0
  52. package/dist/editable-artifact-live.js.map +1 -0
  53. package/dist/editable-artifacts.d.ts +2 -0
  54. package/dist/editable-artifacts.js +179 -0
  55. package/dist/editable-artifacts.js.map +1 -0
  56. package/dist/index.d.ts +35 -26
  57. package/dist/index.js +2792 -609
  58. package/dist/index.js.map +1 -1
  59. package/dist/managed-session.d.ts +1 -1
  60. package/dist/rigs/index.d.ts +1 -1
  61. package/dist/rigs/provider-images.d.ts +31 -0
  62. package/dist/sandbox/routing.d.ts +1 -0
  63. package/dist/session-authorization.d.ts +1 -1
  64. package/dist/workflow-wake-contract.d.ts +7 -0
  65. package/package.json +18 -10
  66. package/src/application/new-session-drafts.ts +2 -0
  67. package/src/application/session-commands.ts +188 -107
  68. package/src/dependencies.ts +30 -2
  69. package/src/domain/capabilities.ts +604 -220
  70. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  71. package/src/domain/durable-learning-slack-publication.ts +202 -0
  72. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  73. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  74. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  75. package/src/domain/editable-artifacts/errors.ts +115 -0
  76. package/src/domain/editable-artifacts/hash.ts +131 -0
  77. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  78. package/src/domain/editable-artifacts/index.ts +14 -0
  79. package/src/domain/editable-artifacts/ports.ts +462 -0
  80. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  81. package/src/domain/editable-artifacts/service.ts +2402 -0
  82. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  83. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  84. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  85. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  86. package/src/domain/editable-artifacts/types.ts +835 -0
  87. package/src/domain/insights.ts +136 -33
  88. package/src/domain/memory-slack-delivery.ts +204 -0
  89. package/src/domain/memory-slack-publication.ts +5 -2
  90. package/src/domain/packs.ts +266 -5
  91. package/src/domain/personal-connection-delegations.ts +97 -6
  92. package/src/domain/scheduled-tasks.ts +155 -29
  93. package/src/domain/session-tool-policy.ts +8 -2
  94. package/src/domain/sessions.ts +274 -131
  95. package/src/domain/skill-imports.ts +350 -0
  96. package/src/domain/slack-publication-secret-safety.ts +42 -0
  97. package/src/domain/timeline-annotations.ts +204 -0
  98. package/src/domain/video-generation-capabilities.ts +69 -0
  99. package/src/domain/video-generation.ts +249 -0
  100. package/src/editable-artifact-live/application.ts +182 -0
  101. package/src/editable-artifact-live/errors.ts +4 -0
  102. package/src/editable-artifact-live/index.ts +8 -0
  103. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  104. package/src/editable-artifact-live/ports.ts +139 -0
  105. package/src/editable-artifact-live/server.ts +1707 -0
  106. package/src/editable-artifact-live/ticket.ts +217 -0
  107. package/src/editable-artifact-live/types.ts +286 -0
  108. package/src/editable-artifact-live/wire.ts +95 -0
  109. package/src/editable-artifact-live.ts +2 -0
  110. package/src/editable-artifacts.ts +2 -0
  111. package/src/index.ts +13 -0
  112. package/src/rigs/index.ts +26 -19
  113. package/src/rigs/provider-images.ts +110 -0
  114. package/src/sandbox/routing.ts +6 -3
  115. package/src/session-authorization.ts +3 -0
  116. package/src/workflow-wake-contract.ts +8 -0
@@ -1,5 +1,5 @@
1
1
  import type { Context } from "hono";
2
- import type { ManagedAuth } from "./managed-auth-type";
2
+ import type { ManagedAuth } from "./managed-auth-type.js";
3
3
  /**
4
4
  * Read a Better Auth session without bypassing its sliding-cookie renewal.
5
5
  *
@@ -1,5 +1,6 @@
1
1
  import type { AccessGrant, CreateRigRequest, RigDefinitionEditPayload, ProposeRigChangeRequest, Rig, RigChange, RigVersion, UpdateRigRequest } from "@opengeni/contracts";
2
2
  import { type Database } from "@opengeni/db";
3
+ export * from "./provider-images.js";
3
4
  export declare const MAX_RIGS_PER_WORKSPACE = 50;
4
5
  export declare const MAX_CHECKS_PER_RIG = 100;
5
6
  export declare const MAX_CREDENTIAL_HOOKS_PER_RIG = 50;
@@ -54,4 +55,3 @@ export declare function createRigVersionForApi(deps: RigServices, grant: AccessG
54
55
  export declare function activateRigVersionForApi(deps: RigServices, grant: AccessGrant, rig: Rig, versionId: string): Promise<RigVersion>;
55
56
  export declare function listRigVersionsForApi(deps: RigServices, workspaceId: string, rigId: string): Promise<RigVersion[]>;
56
57
  export declare function listRigChangesForApi(deps: RigServices, workspaceId: string, rigId: string, limit?: number): Promise<RigChange[]>;
57
- export {};
@@ -0,0 +1,31 @@
1
+ import { type RigChangeVerification, type RigProviderImage, type RigProviderImages, type RigVersion, type SandboxBackend } from "@opengeni/contracts";
2
+ export type RigProviderImageDefinition = Pick<RigVersion, "image" | "setupScript" | "checks" | "credentialHooks" | "defaultVariableSetIds">;
3
+ export declare function rigProviderImageProviderBindingKeyHash(bindingKey: string): string;
4
+ export declare function rigProviderImageSetupHash(definition: Pick<RigProviderImageDefinition, "setupScript">): string;
5
+ /**
6
+ * Hash every exact rig-definition input that can affect whether a provider
7
+ * image may be reused. Credential values and variable-set contents are
8
+ * intentionally absent: verification never injects them into the build box.
9
+ * Their declared hook/set identities still participate so a new rig version
10
+ * cannot silently inherit an older build record.
11
+ */
12
+ export declare function rigProviderImageContentHash(input: {
13
+ backend: SandboxBackend;
14
+ sourceImage: string | null;
15
+ definition: RigProviderImageDefinition;
16
+ }): string;
17
+ /** Modal's patched snapshot API accepts a caller-owned UUID idempotency key.
18
+ * v2 denotes images proven by a separate cold boot before publication. */
19
+ export declare function rigProviderImageBuildRequestId(input: {
20
+ targetId: string;
21
+ backend: SandboxBackend;
22
+ contentHash: string;
23
+ }): string;
24
+ export declare function rigProviderImageMatchesDefinition(image: RigProviderImage, definition: RigProviderImageDefinition): boolean;
25
+ /**
26
+ * Promotion copies only a finalized, structurally valid build record whose
27
+ * hashes match the exact version definition being inserted. A malformed or
28
+ * stale optimization record is discarded; the version remains correct and
29
+ * falls back to runtime setup.
30
+ */
31
+ export declare function rigProviderImagesFromVerification(verification: RigChangeVerification | null, definition: RigProviderImageDefinition): RigProviderImages;
@@ -7,6 +7,7 @@ export type ChannelARoutingServices = {
7
7
  settings: Settings;
8
8
  bus?: EventBus;
9
9
  onSandboxOperation?: RoutingSandboxOperationObserver;
10
+ waitSignal?: AbortSignal;
10
11
  };
11
12
  /** Map the deployment relay URL to the leaf's `SelfhostedRelayConfig` shape. The
12
13
  * relay URL (`OPENGENI_SELFHOSTED_RELAY_URL`) may carry a path (the relay's wss
@@ -1,6 +1,6 @@
1
1
  import { SessionAuthorizationActor, SessionAuthorizationListScope, type AccessGrant, type SessionAuthorizationOperation, type SessionAuthorizationSurface, type SessionAuthorizationTarget } from "@opengeni/contracts";
2
2
  import { type Database } from "@opengeni/db";
3
- import type { AppDependencies } from "./dependencies";
3
+ import type { AppDependencies } from "./dependencies.js";
4
4
  export type SessionAuthorizationDependencies = Pick<AppDependencies, "db" | "sessionAuthorization">;
5
5
  /** Maximum time an omitted host hint leaves a live session stream unchecked. */
6
6
  export declare const SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS = 15000;
@@ -2,3 +2,10 @@
2
2
  export declare const SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
3
3
  export declare const SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
4
4
  export declare const SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 10000;
5
+ /**
6
+ * Temporal delivers activity cancellation through heartbeats. Keep both the
7
+ * activity's local heartbeat timer and the worker's SDK throttle at this bound
8
+ * so the end-to-end Pause/Steer physical-cancellation contract is not already
9
+ * exhausted before sandbox/tool quiescence and replacement admission begin.
10
+ */
11
+ export declare const TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "0.21.10",
3
+ "version": "0.24.1",
4
4
  "description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -21,6 +21,14 @@
21
21
  ".": {
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.js"
24
+ },
25
+ "./editable-artifacts": {
26
+ "types": "./dist/editable-artifacts.d.ts",
27
+ "import": "./dist/editable-artifacts.js"
28
+ },
29
+ "./editable-artifact-live": {
30
+ "types": "./dist/editable-artifact-live.d.ts",
31
+ "import": "./dist/editable-artifact-live.js"
24
32
  }
25
33
  },
26
34
  "publishConfig": {
@@ -34,15 +42,15 @@
34
42
  },
35
43
  "dependencies": {
36
44
  "@modelcontextprotocol/sdk": "^1.29.0",
37
- "@opengeni/codex": "^0.2.13",
38
- "@opengeni/config": "^0.12.1",
39
- "@opengeni/contracts": "^0.40.0",
40
- "@opengeni/db": "^0.28.9",
41
- "@opengeni/documents": "^0.5.18",
42
- "@opengeni/events": "^0.3.88",
43
- "@opengeni/observability": "^0.5.6",
44
- "@opengeni/runtime": "^0.18.24",
45
- "@opengeni/storage": "^0.2.75",
45
+ "@opengeni/codex": "^0.2.15",
46
+ "@opengeni/config": "^0.13.2",
47
+ "@opengeni/contracts": "^0.44.1",
48
+ "@opengeni/db": "^0.31.1",
49
+ "@opengeni/documents": "^0.5.32",
50
+ "@opengeni/events": "^0.3.103",
51
+ "@opengeni/observability": "^0.6.2",
52
+ "@opengeni/runtime": "^0.19.2",
53
+ "@opengeni/storage": "^0.2.87",
46
54
  "hono": "^4.12.18"
47
55
  },
48
56
  "engines": {
@@ -67,6 +67,7 @@ async function hydrateNewSessionDraft(
67
67
  deps.db,
68
68
  workspaceId,
69
69
  deps.settings,
70
+ { subjectId: grant.subjectId },
70
71
  );
71
72
  const resources = [] as NewSessionDraftValue["resources"];
72
73
  for (const resource of mapped.resources) {
@@ -200,6 +201,7 @@ export async function saveActorNewSessionDraft(
200
201
  deps.db,
201
202
  workspaceId,
202
203
  deps.settings,
204
+ { subjectId: grant.subjectId },
203
205
  );
204
206
  const resources = normalizeResources(input.resources);
205
207
  const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
@@ -16,7 +16,11 @@ import type {
16
16
  WorkspaceInferenceControlRequest,
17
17
  WorkspaceInferenceControlResponse,
18
18
  } from "@opengeni/contracts";
19
- import { latencyModeForMetadata, reasoningEffortForMetadata } from "@opengeni/contracts";
19
+ import {
20
+ DraftTimelineAnnotations,
21
+ latencyModeForMetadata,
22
+ reasoningEffortForMetadata,
23
+ } from "@opengeni/contracts";
20
24
  import {
21
25
  deleteSessionQueueItemInTransaction,
22
26
  editQueuedTurnInTransaction,
@@ -36,8 +40,11 @@ import {
36
40
  steerAgentSessionInTransaction,
37
41
  steerQueuedTurnInTransaction,
38
42
  withWorkspaceRls,
43
+ withWorkspaceSessionActivityRls,
39
44
  withWorkspaceSubjectRls,
45
+ withWorkspaceSubjectSessionActivityRls,
40
46
  type Database,
47
+ type SessionActivityDatabase,
41
48
  type SessionCommandReceiptRow,
42
49
  } from "@opengeni/db";
43
50
  import {
@@ -47,6 +54,7 @@ import {
47
54
  } from "@opengeni/events";
48
55
  import type { SessionWorkflowClient } from "../dependencies";
49
56
  import { normalizeResources } from "../domain/resources";
57
+ import { validateDraftTimelineAnnotations } from "../domain/timeline-annotations";
50
58
  import {
51
59
  requireSessionAuthorization,
52
60
  type ResolvedSessionAuthorization,
@@ -143,17 +151,18 @@ function agentActor(context: AgentSessionCommandContext) {
143
151
  }
144
152
 
145
153
  /**
146
- * Retry only one operation-keyed Agent command transaction. The caller keeps
154
+ * Retry only one operation-keyed session command transaction. Callers keep
147
155
  * event publication and Temporal wake delivery after this returns, so a
148
- * deadlock/serialization retry can never replay an external effect.
156
+ * deadlock/serialization retry can never replay an external effect. Human
157
+ * draft ownership is applied inside every retry attempt when required.
149
158
  */
150
- async function runAgentCommandPersistenceTransaction<T>(
159
+ async function runSessionCommandPersistenceTransaction<T>(
151
160
  deps: { db: Database },
152
- context: AgentSessionCommandContext,
161
+ scope: { workspaceId: string; subjectId?: string },
153
162
  input: {
154
163
  stage: string;
155
164
  eventTypes: string[];
156
- transaction: (tx: Database) => Promise<T>;
165
+ transaction: (tx: SessionActivityDatabase) => Promise<T>;
157
166
  },
158
167
  ): Promise<T> {
159
168
  return await runIdempotentPersistenceTransaction(
@@ -162,10 +171,17 @@ async function runAgentCommandPersistenceTransaction<T>(
162
171
  eventTypes: input.eventTypes,
163
172
  maxAttempts: 3,
164
173
  },
165
- async () =>
166
- await withWorkspaceRls(deps.db, context.workspaceId, async (scoped) =>
167
- scoped.transaction(async (tx) => await input.transaction(tx as unknown as Database)),
168
- ),
174
+ async () => {
175
+ const run = async (scoped: SessionActivityDatabase) => await input.transaction(scoped);
176
+ return scope.subjectId === undefined
177
+ ? await withWorkspaceSessionActivityRls(deps.db, scope.workspaceId, run)
178
+ : await withWorkspaceSubjectSessionActivityRls(
179
+ deps.db,
180
+ scope.workspaceId,
181
+ scope.subjectId,
182
+ run,
183
+ );
184
+ },
169
185
  );
170
186
  }
171
187
 
@@ -282,19 +298,23 @@ export async function sendAgentSessionMessage(
282
298
  input: { targetSessionId: string; text: string; idempotencyKey: string },
283
299
  ) {
284
300
  await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.append");
285
- const result = await runAgentCommandPersistenceTransaction(deps, context, {
286
- stage: "session_commands.agent_message",
287
- eventTypes: ["system.update.pending"],
288
- transaction: async (tx) =>
289
- await sendAgentMessageInTransaction(tx, {
290
- accountId: context.accountId,
291
- workspaceId: context.workspaceId,
292
- targetSessionId: input.targetSessionId,
293
- actor: agentActor(context),
294
- operationKey: input.idempotencyKey,
295
- text: input.text,
296
- }),
297
- });
301
+ const result = await runSessionCommandPersistenceTransaction(
302
+ deps,
303
+ { workspaceId: context.workspaceId },
304
+ {
305
+ stage: "session_commands.agent_message",
306
+ eventTypes: ["system.update.pending"],
307
+ transaction: async (tx) =>
308
+ await sendAgentMessageInTransaction(tx, {
309
+ accountId: context.accountId,
310
+ workspaceId: context.workspaceId,
311
+ targetSessionId: input.targetSessionId,
312
+ actor: agentActor(context),
313
+ operationKey: input.idempotencyKey,
314
+ text: input.text,
315
+ }),
316
+ },
317
+ );
298
318
  await publishAndWakeAgentCommand(deps, {
299
319
  accountId: context.accountId,
300
320
  workspaceId: context.workspaceId,
@@ -317,22 +337,30 @@ export async function steerAgentSession(
317
337
  sessionAuthorization?: SessionAuthorizationPort | null;
318
338
  },
319
339
  context: AgentSessionCommandContext,
320
- input: { targetSessionId: string; instruction: string; idempotencyKey: string },
340
+ input: {
341
+ targetSessionId: string;
342
+ instruction: string;
343
+ idempotencyKey: string;
344
+ },
321
345
  ) {
322
346
  await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.steer");
323
- const result = await runAgentCommandPersistenceTransaction(deps, context, {
324
- stage: "session_commands.agent_steer",
325
- eventTypes: ["session.control.steer_requested", "system.update.pending", "turn.superseded"],
326
- transaction: async (tx) =>
327
- await steerAgentSessionInTransaction(tx, {
328
- accountId: context.accountId,
329
- workspaceId: context.workspaceId,
330
- targetSessionId: input.targetSessionId,
331
- actor: agentActor(context),
332
- operationKey: input.idempotencyKey,
333
- instruction: input.instruction,
334
- }),
335
- });
347
+ const result = await runSessionCommandPersistenceTransaction(
348
+ deps,
349
+ { workspaceId: context.workspaceId },
350
+ {
351
+ stage: "session_commands.agent_steer",
352
+ eventTypes: ["session.control.steer_requested", "system.update.pending", "turn.superseded"],
353
+ transaction: async (tx) =>
354
+ await steerAgentSessionInTransaction(tx, {
355
+ accountId: context.accountId,
356
+ workspaceId: context.workspaceId,
357
+ targetSessionId: input.targetSessionId,
358
+ actor: agentActor(context),
359
+ operationKey: input.idempotencyKey,
360
+ instruction: input.instruction,
361
+ }),
362
+ },
363
+ );
336
364
  await publishAndWakeAgentCommand(deps, {
337
365
  accountId: context.accountId,
338
366
  workspaceId: context.workspaceId,
@@ -369,18 +397,23 @@ export async function controlAgentSessionWorkstream(
369
397
  input.targetSessionId,
370
398
  "session.control",
371
399
  );
372
- const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
373
- scoped.transaction((tx) =>
374
- mutateSessionControlInTransaction(tx as unknown as Database, {
375
- accountId: context.accountId,
376
- workspaceId: context.workspaceId,
377
- sessionId: input.targetSessionId,
378
- actor: agentActor(context),
379
- operationKey: input.idempotencyKey,
380
- action: input.action,
381
- reason: input.reason ?? null,
382
- }),
383
- ),
400
+ const result = await runSessionCommandPersistenceTransaction(
401
+ deps,
402
+ { workspaceId: context.workspaceId },
403
+ {
404
+ stage: "session_commands.agent_control",
405
+ eventTypes: [input.action === "pause" ? "session.control.paused" : "session.control.resumed"],
406
+ transaction: async (tx) =>
407
+ await mutateSessionControlInTransaction(tx, {
408
+ accountId: context.accountId,
409
+ workspaceId: context.workspaceId,
410
+ sessionId: input.targetSessionId,
411
+ actor: agentActor(context),
412
+ operationKey: input.idempotencyKey,
413
+ action: input.action,
414
+ reason: input.reason ?? null,
415
+ }),
416
+ },
384
417
  );
385
418
  await publishSessionEventIds(deps, context.workspaceId, input.targetSessionId, [
386
419
  result.sessionControlEventId,
@@ -412,6 +445,7 @@ function composerDraft(
412
445
  return {
413
446
  revision: row.revision,
414
447
  text: row.text,
448
+ annotations: DraftTimelineAnnotations.parse(row.annotations),
415
449
  resources: row.resources as ComposerDraft["resources"],
416
450
  model: row.model,
417
451
  reasoningEffort: row.reasoningEffort as ComposerDraft["reasoningEffort"],
@@ -441,23 +475,32 @@ async function authoritativeQueue(
441
475
  }
442
476
 
443
477
  export async function moveHumanQueuePrompt(
444
- deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
478
+ deps: {
479
+ db: Database;
480
+ bus: EventBus;
481
+ sessionAuthorization?: SessionAuthorizationPort | null;
482
+ },
445
483
  context: HumanSessionCommandContext,
446
484
  turnId: string,
447
485
  input: MoveSessionQueueItemRequest,
448
486
  ): Promise<SessionQueueMutationResponse> {
449
487
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
450
- const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
451
- scoped.transaction((tx) =>
452
- moveQueuedTurnInTransaction(tx as unknown as Database, {
453
- ...context,
454
- turnId,
455
- beforeTurnId: input.beforeTurnId,
456
- expectedQueueVersion: input.expectedQueueVersion,
457
- actor: { type: "human", subjectId: context.subjectId },
458
- operationKey: input.clientEventId,
459
- }),
460
- ),
488
+ const result = await runSessionCommandPersistenceTransaction(
489
+ deps,
490
+ { workspaceId: context.workspaceId },
491
+ {
492
+ stage: "session_commands.human_queue_move",
493
+ eventTypes: ["session.queue.changed"],
494
+ transaction: async (tx) =>
495
+ await moveQueuedTurnInTransaction(tx, {
496
+ ...context,
497
+ turnId,
498
+ beforeTurnId: input.beforeTurnId,
499
+ expectedQueueVersion: input.expectedQueueVersion,
500
+ actor: { type: "human", subjectId: context.subjectId },
501
+ operationKey: input.clientEventId,
502
+ }),
503
+ },
461
504
  );
462
505
  const response = {
463
506
  receipt: receipt(result.receipt),
@@ -473,23 +516,32 @@ export async function moveHumanQueuePrompt(
473
516
  }
474
517
 
475
518
  export async function deleteHumanQueuePrompt(
476
- deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
519
+ deps: {
520
+ db: Database;
521
+ bus: EventBus;
522
+ sessionAuthorization?: SessionAuthorizationPort | null;
523
+ },
477
524
  context: HumanSessionCommandContext,
478
525
  turnId: string,
479
526
  input: DeleteSessionQueueItemRequest,
480
527
  ): Promise<SessionQueueMutationResponse> {
481
528
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
482
- const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
483
- scoped.transaction((tx) =>
484
- deleteSessionQueueItemInTransaction(tx as unknown as Database, {
485
- ...context,
486
- turnId,
487
- expectedTurnVersion: input.expectedTurnVersion,
488
- actor: { type: "human", subjectId: context.subjectId },
489
- operationKey: input.clientEventId,
490
- reason: input.reason ?? null,
491
- }),
492
- ),
529
+ const result = await runSessionCommandPersistenceTransaction(
530
+ deps,
531
+ { workspaceId: context.workspaceId },
532
+ {
533
+ stage: "session_commands.human_queue_delete",
534
+ eventTypes: ["session.queue.changed"],
535
+ transaction: async (tx) =>
536
+ await deleteSessionQueueItemInTransaction(tx, {
537
+ ...context,
538
+ turnId,
539
+ expectedTurnVersion: input.expectedTurnVersion,
540
+ actor: { type: "human", subjectId: context.subjectId },
541
+ operationKey: input.clientEventId,
542
+ reason: input.reason ?? null,
543
+ }),
544
+ },
493
545
  );
494
546
  const response = {
495
547
  receipt: receipt(result.receipt),
@@ -505,19 +557,24 @@ export async function deleteHumanQueuePrompt(
505
557
  }
506
558
 
507
559
  export async function editHumanQueuePrompt(
508
- deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
560
+ deps: {
561
+ db: Database;
562
+ bus: EventBus;
563
+ sessionAuthorization?: SessionAuthorizationPort | null;
564
+ },
509
565
  context: HumanSessionCommandContext,
510
566
  turnId: string,
511
567
  input: EditSessionQueueItemRequest,
512
568
  ): Promise<SessionQueueMutationResponse> {
513
569
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
514
- const result = await withWorkspaceSubjectRls(
515
- deps.db,
516
- context.workspaceId,
517
- context.subjectId,
518
- (scoped) =>
519
- scoped.transaction((tx) =>
520
- editQueuedTurnInTransaction(tx as unknown as Database, {
570
+ const result = await runSessionCommandPersistenceTransaction(
571
+ deps,
572
+ { workspaceId: context.workspaceId, subjectId: context.subjectId },
573
+ {
574
+ stage: "session_commands.human_queue_edit",
575
+ eventTypes: ["session.queue.changed"],
576
+ transaction: async (tx) =>
577
+ await editQueuedTurnInTransaction(tx, {
521
578
  ...context,
522
579
  turnId,
523
580
  expectedTurnVersion: input.expectedTurnVersion,
@@ -526,7 +583,7 @@ export async function editHumanQueuePrompt(
526
583
  actor: { type: "human", subjectId: context.subjectId },
527
584
  operationKey: input.clientEventId,
528
585
  }),
529
- ),
586
+ },
530
587
  );
531
588
  const response = {
532
589
  receipt: receipt(result.receipt),
@@ -543,23 +600,32 @@ export async function editHumanQueuePrompt(
543
600
  }
544
601
 
545
602
  export async function steerHumanQueuePrompt(
546
- deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
603
+ deps: {
604
+ db: Database;
605
+ bus: EventBus;
606
+ sessionAuthorization?: SessionAuthorizationPort | null;
607
+ },
547
608
  context: HumanSessionCommandContext,
548
609
  turnId: string,
549
610
  input: SteerSessionQueueItemRequest,
550
611
  ): Promise<SessionQueueMutationResponse> {
551
612
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
552
- const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
553
- scoped.transaction((tx) =>
554
- steerQueuedTurnInTransaction(tx as unknown as Database, {
555
- ...context,
556
- turnId,
557
- expectedTurnVersion: input.expectedTurnVersion,
558
- controlEtag: input.controlEtag ?? null,
559
- actor: { type: "human", subjectId: context.subjectId },
560
- operationKey: input.clientEventId,
561
- }),
562
- ),
613
+ const result = await runSessionCommandPersistenceTransaction(
614
+ deps,
615
+ { workspaceId: context.workspaceId },
616
+ {
617
+ stage: "session_commands.human_queue_steer",
618
+ eventTypes: ["session.control.steer_requested", "session.queue.changed", "turn.superseded"],
619
+ transaction: async (tx) =>
620
+ await steerQueuedTurnInTransaction(tx, {
621
+ ...context,
622
+ turnId,
623
+ expectedTurnVersion: input.expectedTurnVersion,
624
+ controlEtag: input.controlEtag ?? null,
625
+ actor: { type: "human", subjectId: context.subjectId },
626
+ operationKey: input.clientEventId,
627
+ }),
628
+ },
563
629
  );
564
630
  const response = {
565
631
  receipt: receipt(result.receipt),
@@ -586,19 +652,26 @@ export async function controlHumanSessionWorkstreamWithOutcome(
586
652
  input: SessionControlRequest,
587
653
  ): Promise<{ response: SessionControlResponse; replay: boolean }> {
588
654
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
589
- const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
590
- scoped.transaction((tx) =>
591
- mutateSessionControlInTransaction(tx as unknown as Database, {
592
- accountId: context.accountId,
593
- workspaceId: context.workspaceId,
594
- sessionId: context.sessionId,
595
- actor: { type: "human", subjectId: context.subjectId },
596
- operationKey: input.clientEventId,
597
- action: input.action,
598
- reason: input.reason ?? null,
599
- expectedControlEtag: input.expectedControlEtag ?? null,
600
- }),
601
- ),
655
+ const result = await runSessionCommandPersistenceTransaction(
656
+ deps,
657
+ { workspaceId: context.workspaceId },
658
+ {
659
+ stage: "session_commands.human_control",
660
+ eventTypes: [
661
+ input.action === "resume" ? "session.control.resumed" : "session.control.paused",
662
+ ],
663
+ transaction: async (tx) =>
664
+ await mutateSessionControlInTransaction(tx, {
665
+ accountId: context.accountId,
666
+ workspaceId: context.workspaceId,
667
+ sessionId: context.sessionId,
668
+ actor: { type: "human", subjectId: context.subjectId },
669
+ operationKey: input.clientEventId,
670
+ action: input.action,
671
+ reason: input.reason ?? null,
672
+ expectedControlEtag: input.expectedControlEtag ?? null,
673
+ }),
674
+ },
602
675
  );
603
676
  const response = {
604
677
  receipt: receipt(result.receipt),
@@ -686,6 +759,7 @@ export async function getHumanComposerDraft(
686
759
  return {
687
760
  revision: 0,
688
761
  text: "",
762
+ annotations: [],
689
763
  resources: [],
690
764
  model: session.model,
691
765
  reasoningEffort: reasoningEffortForMetadata(session.metadata, "medium"),
@@ -702,6 +776,12 @@ export async function saveHumanComposerDraft(
702
776
  input: SaveComposerDraftRequest,
703
777
  ): Promise<ComposerDraft> {
704
778
  await authorizeHumanSessionCommand(deps, context, "session.composer.write");
779
+ const annotations = await validateDraftTimelineAnnotations(
780
+ deps.db,
781
+ context.workspaceId,
782
+ context.sessionId,
783
+ input.annotations ?? [],
784
+ );
705
785
  const row = await withWorkspaceSubjectRls(
706
786
  deps.db,
707
787
  context.workspaceId,
@@ -711,6 +791,7 @@ export async function saveHumanComposerDraft(
711
791
  saveComposerDraftInTransaction(tx as unknown as Database, {
712
792
  ...context,
713
793
  ...input,
794
+ annotations,
714
795
  resources: normalizeResources(input.resources),
715
796
  subjectId: context.subjectId,
716
797
  }),