@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
@@ -35,6 +35,8 @@ import {
35
35
  type SessionMcpCredentialUpdateInput,
36
36
  type SessionMcpServerInput,
37
37
  type SessionMcpServerMetadata,
38
+ type SubmittedTimelineAnnotation,
39
+ type TimelineAnnotation,
38
40
  type UpdateSessionMcpApprovalPolicyResponse,
39
41
  type UpdateSessionToolPolicyRequest,
40
42
  type SessionAuthorizationPort,
@@ -59,7 +61,6 @@ import {
59
61
  getSession,
60
62
  SessionIdConflictError,
61
63
  getSessionSpawnDenialByIdempotencyKey,
62
- getSessionEvent,
63
64
  getWorkspaceControlEvent,
64
65
  getSessionLineage,
65
66
  getSessionTurn,
@@ -71,12 +72,13 @@ import {
71
72
  submitHumanPromptInTransaction,
72
73
  appendSessionEventsWithLockedSessionUpdate,
73
74
  updateSessionTitle as updateSessionTitleRow,
74
- withWorkspaceSubjectRls,
75
+ withWorkspaceSubjectSessionActivityRls,
75
76
  type CreateSessionMcpServerInput,
76
77
  type Database,
77
78
  type UpdateSessionMcpServerCredentialsInput,
78
79
  QueueCommandConflictError,
79
80
  AgentCommandAuthorityError,
81
+ runIdempotentPersistenceTransaction,
80
82
  SessionSpawnDeniedDbError,
81
83
  SessionControlConflictError,
82
84
  SessionToolPolicyVersionConflictError,
@@ -99,6 +101,7 @@ import type {
99
101
  import { requireSessionAuthorization } from "../session-authorization";
100
102
  import { swapActiveSandbox, type FleetContext } from "../sandbox/fleet";
101
103
  import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
104
+ import { validateSubmittedTimelineAnnotations } from "./timeline-annotations";
102
105
  import { requireVariableSetEncryption, validateVariableSetAttachment } from "./environments";
103
106
  import {
104
107
  freezePersonalConnectionDelegations,
@@ -241,7 +244,9 @@ export function creationInitiatorForGrant(grant: AccessGrant): FrozenCreationIni
241
244
  !Number.isSafeInteger(callerExecutionGeneration) ||
242
245
  callerExecutionGeneration < 1
243
246
  ) {
244
- throw new HTTPException(403, { message: "caller attempt claims are incomplete" });
247
+ throw new HTTPException(403, {
248
+ message: "caller attempt claims are incomplete",
249
+ });
245
250
  }
246
251
  const actor = {
247
252
  type: "agent_attempt",
@@ -284,11 +289,15 @@ function normalizedSessionMcpCredentialHeaders(
284
289
  const seen = new Set<string>();
285
290
  for (const [name, value] of entries) {
286
291
  if (!sessionMcpCredentialHeaderName.test(name)) {
287
- throw new HTTPException(422, { message: `invalid credential header name: ${name}` });
292
+ throw new HTTPException(422, {
293
+ message: `invalid credential header name: ${name}`,
294
+ });
288
295
  }
289
296
  const lower = name.toLowerCase();
290
297
  if (seen.has(lower)) {
291
- throw new HTTPException(422, { message: `duplicate credential header name: ${name}` });
298
+ throw new HTTPException(422, {
299
+ message: `duplicate credential header name: ${name}`,
300
+ });
292
301
  }
293
302
  seen.add(lower);
294
303
  if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
@@ -388,11 +397,15 @@ function validateSessionMcpServersForCreate(
388
397
  const metadata: SessionMcpServerMetadata[] = [];
389
398
  for (const server of servers) {
390
399
  if (seenIds.has(server.id)) {
391
- throw new HTTPException(422, { message: `duplicate session MCP server id: ${server.id}` });
400
+ throw new HTTPException(422, {
401
+ message: `duplicate session MCP server id: ${server.id}`,
402
+ });
392
403
  }
393
404
  seenIds.add(server.id);
394
405
  if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
395
- throw new HTTPException(422, { message: `MCP server id already exists: ${server.id}` });
406
+ throw new HTTPException(422, {
407
+ message: `MCP server id already exists: ${server.id}`,
408
+ });
396
409
  }
397
410
  const headers = normalizedSessionMcpCredentialHeaders(server.headers);
398
411
  const headersEncrypted = Object.fromEntries(
@@ -490,7 +503,9 @@ function validateSessionMcpCredentialUpdates(input: {
490
503
  }
491
504
  seenIds.add(update.id);
492
505
  if (!knownIds.has(update.id)) {
493
- throw new HTTPException(422, { message: `unknown session MCP server id: ${update.id}` });
506
+ throw new HTTPException(422, {
507
+ message: `unknown session MCP server id: ${update.id}`,
508
+ });
494
509
  }
495
510
  const headers = normalizedSessionMcpCredentialHeaders(update.headers);
496
511
  return {
@@ -602,10 +617,17 @@ export async function createAndStartSessionWithOutcome(input: {
602
617
  // target fails the create (422) — never a silent fall-back to the default box.
603
618
  // `workingDir` (optional) is the path/cwd base the chosen machine runs under,
604
619
  // seeded alongside the pointer through the epoch-fenced CAS.
605
- seedTargetSandbox?: { sandboxId: string; settings: Settings; workingDir?: string | null } | null;
620
+ seedTargetSandbox?: {
621
+ sandboxId: string;
622
+ settings: Settings;
623
+ workingDir?: string | null;
624
+ } | null;
606
625
  // Exact actor-private pre-session draft represented by this create. The
607
626
  // initializer consumes it only after the first durable runnable unit commits.
608
- consumeNewSessionDraft?: { subjectId: string; expectedRevision: number } | null;
627
+ consumeNewSessionDraft?: {
628
+ subjectId: string;
629
+ expectedRevision: number;
630
+ } | null;
609
631
  // A child may lower its inherited nested-agent depth limit freely; increases
610
632
  // are authorized by the caller's workspace:admin grant and checked again by
611
633
  // the database admission transaction.
@@ -767,7 +789,10 @@ async function finishStartSession(
767
789
  settings: Settings;
768
790
  workingDir?: string | null;
769
791
  } | null;
770
- consumeNewSessionDraft?: { subjectId: string; expectedRevision: number } | null;
792
+ consumeNewSessionDraft?: {
793
+ subjectId: string;
794
+ expectedRevision: number;
795
+ } | null;
771
796
  },
772
797
  session: Session,
773
798
  ): Promise<{ session: CreateSessionResponse; changed: boolean }> {
@@ -790,7 +815,11 @@ async function finishStartSession(
790
815
  sessionGroupId: session.sandboxGroupId,
791
816
  };
792
817
  const seeded = await swapActiveSandbox(
793
- { db: input.db, settings: input.seedTargetSandbox.settings, bus: input.bus },
818
+ {
819
+ db: input.db,
820
+ settings: input.seedTargetSandbox.settings,
821
+ bus: input.bus,
822
+ },
794
823
  ctx,
795
824
  input.seedTargetSandbox.sandboxId,
796
825
  // The working dir is committed in the SAME epoch-fenced CAS that seeds the
@@ -813,7 +842,10 @@ async function finishStartSession(
813
842
  createdEventPayload: {
814
843
  toolPolicy: input.toolPolicy,
815
844
  ...(input.variableSet
816
- ? { variableSetId: input.variableSet.id, variableSetName: input.variableSet.name }
845
+ ? {
846
+ variableSetId: input.variableSet.id,
847
+ variableSetName: input.variableSet.name,
848
+ }
817
849
  : {}),
818
850
  ...(input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}),
819
851
  },
@@ -863,10 +895,10 @@ export function workflowIdForSession(sessionId: string): string {
863
895
  * it cannot be resolved to a provider at run time, so we fail the request at
864
896
  * the API edge with 422 rather than enqueuing a turn the worker can't honor.
865
897
  *
866
- * `model` is the explicit, caller-supplied value (null/undefined when omitted).
867
- * An omitted model defaults to `settings.openaiModel` downstream which is
868
- * always first in `configuredAllowedModels` so only an explicit value is
869
- * checked. Centralized here so every model-carrying choke point
898
+ * `model` is the effective value selected by the caller's boundary. Top-level
899
+ * omission defaults to `settings.openaiModel`; child-session omission inherits
900
+ * the worker-signed calling turn before reaching this helper. Centralized here
901
+ * so every model-carrying choke point
870
902
  * (create-session, user-message/turn-accept, queued-turn update, and
871
903
  * scheduled-task agentConfig — a scheduled task is a session the worker runs
872
904
  * later) and the MCP surfaces that share them validate identically and cannot
@@ -1023,6 +1055,7 @@ export async function postUserMessageTurn(input: {
1023
1055
  workspaceId: string;
1024
1056
  sessionId: string;
1025
1057
  text: string;
1058
+ annotations?: TimelineAnnotation[];
1026
1059
  turnInstructions?: string | null;
1027
1060
  resources: ResourceRef[];
1028
1061
  model?: string | null;
@@ -1040,7 +1073,14 @@ export async function postUserMessageTurn(input: {
1040
1073
  expectedDraftRevision?: number | null;
1041
1074
  reasoningEffortFallback?: Settings["openaiReasoningEffort"];
1042
1075
  turnExecutionPolicy: TurnExecutionPolicyV1;
1043
- }): Promise<{ accepted: SessionEvent; turn: SessionTurn; replay: boolean }> {
1076
+ recordAgentRunUsage?: boolean;
1077
+ schedulePostCommit?: (task: () => Promise<void>) => void;
1078
+ }): Promise<{
1079
+ accepted: SessionEvent;
1080
+ turn: SessionTurn;
1081
+ interruptionCount: number;
1082
+ replay: boolean;
1083
+ }> {
1044
1084
  const { db, bus, workflowClient, settings, accountId, workspaceId, sessionId } = input;
1045
1085
  const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
1046
1086
  const requestedReasoningEffort = input.reasoningEffort ?? null;
@@ -1061,35 +1101,50 @@ export async function postUserMessageTurn(input: {
1061
1101
  const operationKey = input.clientEventId ?? crypto.randomUUID();
1062
1102
  let result;
1063
1103
  try {
1064
- result = await withWorkspaceSubjectRls(db, workspaceId, input.actor ?? accountId, (scoped) =>
1065
- scoped.transaction((tx) =>
1066
- submitHumanPromptInTransaction(tx as unknown as Database, {
1067
- accountId,
1104
+ result = await runIdempotentPersistenceTransaction(
1105
+ {
1106
+ stage: "session.prompt.submit",
1107
+ eventTypes: ["user.message", "turn.queued", "session.status.changed"],
1108
+ maxAttempts: 3,
1109
+ },
1110
+ async () =>
1111
+ await withWorkspaceSubjectSessionActivityRls(
1112
+ db,
1068
1113
  workspaceId,
1069
- sessionId,
1070
- subjectId: input.actor ?? accountId,
1071
- ...(input.actorLabel ? { subjectLabel: input.actorLabel } : {}),
1072
- actor: input.commandActor ?? {
1073
- type: "human",
1074
- subjectId: input.actor ?? accountId,
1075
- },
1076
- operationKey,
1077
- delivery: input.delivery ?? "send",
1078
- controlEtag: input.controlEtag ?? null,
1079
- expectedDraftRevision: input.expectedDraftRevision ?? null,
1080
- text: input.text,
1081
- turnInstructions: input.turnInstructions ?? null,
1082
- resources: input.resources,
1083
- model: requestedModel,
1084
- reasoningEffort: requestedReasoningEffort,
1085
- latencyMode: input.latencyMode ?? null,
1086
- reasoningEffortFallback: input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
1087
- turnExecutionPolicy: input.turnExecutionPolicy,
1088
- source: input.origin === "operator" ? "api" : "user",
1089
- personalConnectionDelegations: input.personalConnectionDelegations ?? [],
1090
- mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
1091
- }),
1092
- ),
1114
+ input.actor ?? accountId,
1115
+ (scoped) =>
1116
+ submitHumanPromptInTransaction(scoped, {
1117
+ accountId,
1118
+ workspaceId,
1119
+ sessionId,
1120
+ subjectId: input.actor ?? accountId,
1121
+ ...(input.actorLabel ? { subjectLabel: input.actorLabel } : {}),
1122
+ actor: input.commandActor ?? {
1123
+ type: "human",
1124
+ subjectId: input.actor ?? accountId,
1125
+ },
1126
+ operationKey,
1127
+ delivery: input.delivery ?? "send",
1128
+ controlEtag: input.controlEtag ?? null,
1129
+ expectedDraftRevision: input.expectedDraftRevision ?? null,
1130
+ text: input.text,
1131
+ annotations: input.annotations ?? [],
1132
+ turnInstructions: input.turnInstructions ?? null,
1133
+ resources: input.resources,
1134
+ model: requestedModel,
1135
+ reasoningEffort: requestedReasoningEffort,
1136
+ latencyMode: input.latencyMode ?? null,
1137
+ reasoningEffortFallback:
1138
+ input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
1139
+ turnExecutionPolicy: input.turnExecutionPolicy,
1140
+ source: input.origin === "operator" ? "api" : "user",
1141
+ ...(input.recordAgentRunUsage !== undefined
1142
+ ? { recordAgentRunUsage: input.recordAgentRunUsage }
1143
+ : {}),
1144
+ personalConnectionDelegations: input.personalConnectionDelegations ?? [],
1145
+ mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
1146
+ }),
1147
+ ),
1093
1148
  );
1094
1149
  } catch (error) {
1095
1150
  if (
@@ -1106,54 +1161,68 @@ export async function postUserMessageTurn(input: {
1106
1161
  }
1107
1162
  throw error;
1108
1163
  }
1109
- const events = await Promise.all(
1110
- result.eventIds.map((eventId) => getSessionEvent(db, workspaceId, eventId)),
1111
- );
1112
- if (events.some((event) => event === null)) {
1113
- throw new Error("Committed prompt events could not be reloaded");
1114
- }
1115
- const turn = await getSessionTurn(db, workspaceId, result.turnId);
1116
- if (!turn) throw new Error("Committed prompt turn could not be reloaded");
1117
- const accepted = events.find((event) => event?.id === result.acceptedEventId);
1118
- if (!accepted) throw new Error("Committed user.message event could not be reloaded");
1119
- await publishDurableSessionEvents(
1120
- bus,
1121
- workspaceId,
1122
- sessionId,
1123
- events.filter((event): event is SessionEvent => event !== null),
1124
- );
1125
- if (result.workspaceControlEventId) {
1126
- const controlEvent = await getWorkspaceControlEvent(
1127
- db,
1128
- workspaceId,
1129
- result.workspaceControlEventId,
1130
- );
1131
- if (!controlEvent) {
1132
- throw new Error(
1133
- `Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
1134
- );
1164
+ const postCommitTask = async () => {
1165
+ try {
1166
+ await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
1167
+ if (result.workspaceControlEventId) {
1168
+ const controlEvent = await getWorkspaceControlEvent(
1169
+ db,
1170
+ workspaceId,
1171
+ result.workspaceControlEventId,
1172
+ );
1173
+ if (!controlEvent) {
1174
+ throw new Error(
1175
+ `Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
1176
+ );
1177
+ }
1178
+ await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
1179
+ }
1180
+ } catch {
1181
+ console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
1182
+ errorClass: "PromptEventFanoutOperationError",
1183
+ errorCode: "session_prompt_event_fanout_failed",
1184
+ origin: "core",
1185
+ });
1135
1186
  }
1136
- await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
1137
- }
1138
- try {
1139
- await workflowClient.wakeSessionWorkflow({
1140
- accountId,
1141
- workspaceId,
1142
- sessionId,
1143
- workflowId: turn.temporalWorkflowId,
1144
- wakeRevision: result.wakeRevision,
1145
- ...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
1146
- ? { interruptionRequested: true }
1147
- : {}),
1187
+ try {
1188
+ await workflowClient.wakeSessionWorkflow({
1189
+ accountId,
1190
+ workspaceId,
1191
+ sessionId,
1192
+ workflowId: result.turn.temporalWorkflowId,
1193
+ wakeRevision: result.wakeRevision,
1194
+ ...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
1195
+ ? { interruptionRequested: true }
1196
+ : {}),
1197
+ });
1198
+ } catch {
1199
+ console.warn("[sessions] workflow wake failed; durable outbox will retry", {
1200
+ errorClass: "WorkflowWakeOperationError",
1201
+ errorCode: "session_workflow_wake_failed",
1202
+ origin: "core",
1203
+ });
1204
+ }
1205
+ };
1206
+ const schedulePostCommit =
1207
+ input.schedulePostCommit ??
1208
+ ((task: () => Promise<void>) => {
1209
+ void task();
1148
1210
  });
1211
+ try {
1212
+ schedulePostCommit(postCommitTask);
1149
1213
  } catch {
1150
- console.warn("[sessions] workflow wake failed; durable outbox will retry", {
1151
- errorClass: "WorkflowWakeOperationError",
1152
- errorCode: "session_workflow_wake_failed",
1214
+ console.warn("[sessions] prompt post-commit scheduling failed; durable recovery remains", {
1215
+ errorClass: "PromptPostCommitScheduleError",
1216
+ errorCode: "session_prompt_post_commit_schedule_failed",
1153
1217
  origin: "core",
1154
1218
  });
1155
1219
  }
1156
- return { accepted, turn, replay: result.replay };
1220
+ return {
1221
+ accepted: result.accepted,
1222
+ turn: result.turn,
1223
+ interruptionCount: result.interruptionCount,
1224
+ replay: result.replay,
1225
+ };
1157
1226
  }
1158
1227
 
1159
1228
  /**
@@ -1213,10 +1282,24 @@ export async function createSessionForRequestWithOutcome(
1213
1282
  message: `parent session not found in workspace: ${parentSessionId}`,
1214
1283
  });
1215
1284
  }
1285
+ const creationInitiator = creationInitiatorForGrant(grant);
1286
+ const parentCallingTurn =
1287
+ parentSession && creationInitiator.actor
1288
+ ? await getSessionTurn(db, workspaceId, creationInitiator.actor.turnId)
1289
+ : null;
1290
+ if (
1291
+ creationInitiator.actor &&
1292
+ (!parentCallingTurn || parentCallingTurn.sessionId !== parentSession?.id)
1293
+ ) {
1294
+ throw new HTTPException(403, {
1295
+ message: "caller attempt does not belong to the parent session",
1296
+ });
1297
+ }
1216
1298
  const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
1217
1299
  db,
1218
1300
  workspaceId,
1219
1301
  settings,
1302
+ { subjectId: grant.subjectId },
1220
1303
  );
1221
1304
  const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers")
1222
1305
  ? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers)
@@ -1263,7 +1346,10 @@ export async function createSessionForRequestWithOutcome(
1263
1346
  "child tools may only narrow the parent session tool policy",
1264
1347
  );
1265
1348
  selectedTools = requestedTools;
1266
- toolPolicy = { mode: "explicit", inheritedFromSessionId: parentSession.id };
1349
+ toolPolicy = {
1350
+ mode: "explicit",
1351
+ inheritedFromSessionId: parentSession.id,
1352
+ };
1267
1353
  } else {
1268
1354
  selectedTools = parentEffective;
1269
1355
  toolPolicy = {
@@ -1296,7 +1382,9 @@ export async function createSessionForRequestWithOutcome(
1296
1382
  });
1297
1383
  await validateGitHubRepositorySelection(db, workspaceId, resources);
1298
1384
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
1299
- throw new HTTPException(503, { message: "object storage is not configured" });
1385
+ throw new HTTPException(503, {
1386
+ message: "object storage is not configured",
1387
+ });
1300
1388
  }
1301
1389
  await validateFileResources(db, workspaceId, resources);
1302
1390
  // VariableSet attachment requires variable-sets:use on the calling grant
@@ -1341,25 +1429,56 @@ export async function createSessionForRequestWithOutcome(
1341
1429
  frozenRigVersionId = rig.activeVersion.id;
1342
1430
  }
1343
1431
  }
1344
- const model = canonicalConfiguredModel(settings, payload.model ?? settings.openaiModel);
1432
+ // A spawned worker is causally part of the exact turn that created it. Omitted
1433
+ // execution policy fields therefore inherit that calling turn rather than the
1434
+ // deployment defaults. This is especially important for Codex subscription
1435
+ // managers: falling back to the deployment model would silently move a child
1436
+ // onto the OpenGeni-credits billing path. Legacy session-bound grants without
1437
+ // exact attempt claims fall back to the parent session's persisted defaults.
1438
+ const inheritedModel = parentCallingTurn?.model ?? parentSession?.model ?? settings.openaiModel;
1439
+ const model = canonicalConfiguredModel(settings, payload.model ?? inheritedModel);
1345
1440
  if (model === null || model === undefined) {
1346
1441
  throw new Error("effective session model unexpectedly resolved to null");
1347
1442
  }
1348
- // Session creation persists the EFFECTIVE model — an omitted payload.model
1349
- // stamps the deployment default onto the session — so the policy must vet
1350
- // that effective value, not just explicit ones (a restricted workspace's
1351
- // default-model session would otherwise be born blocked).
1443
+ // Session creation persists the EFFECTIVE model — the explicit selection,
1444
+ // inherited calling-turn model, or deployment default — so the policy must
1445
+ // vet that effective value, not just explicit ones (a restricted workspace's
1446
+ // inherited/default-model session would otherwise be born blocked).
1352
1447
  await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
1353
- const reasoningEffort = payload.reasoningEffort ?? settings.openaiReasoningEffort;
1354
- const latencyMode = payload.latencyMode ?? "standard";
1448
+ const inheritedReasoningEffort =
1449
+ parentCallingTurn?.reasoningEffort ??
1450
+ (parentSession
1451
+ ? reasoningEffortForSession(parentSession.metadata, settings.openaiReasoningEffort)
1452
+ : settings.openaiReasoningEffort);
1453
+ const inheritedLatencyMode =
1454
+ parentCallingTurn?.latencyMode ??
1455
+ (parentSession ? latencyModeForSession(parentSession.metadata, "standard") : "standard");
1456
+ const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
1457
+ const latencyMode = payload.latencyMode ?? inheritedLatencyMode;
1458
+ const inheritedFromParent = parentSession !== null;
1355
1459
  const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
1356
1460
  modelId: model,
1357
1461
  requestedModelId: payload.model ?? null,
1358
- modelSource: payload.model === undefined ? "deployment" : "explicit",
1462
+ modelSource:
1463
+ payload.model === undefined
1464
+ ? inheritedFromParent
1465
+ ? "continuation"
1466
+ : "deployment"
1467
+ : "explicit",
1359
1468
  reasoningEffort,
1360
- reasoningSource: payload.reasoningEffort === undefined ? "deployment" : "explicit",
1469
+ reasoningSource:
1470
+ payload.reasoningEffort === undefined
1471
+ ? inheritedFromParent
1472
+ ? "continuation"
1473
+ : "deployment"
1474
+ : "explicit",
1361
1475
  latencyMode,
1362
- latencyModeSource: payload.latencyMode === undefined ? "deployment" : "explicit",
1476
+ latencyModeSource:
1477
+ payload.latencyMode === undefined
1478
+ ? inheritedFromParent
1479
+ ? "continuation"
1480
+ : "deployment"
1481
+ : "explicit",
1363
1482
  });
1364
1483
  // Parent linkage was resolved above, before context validation. A child with
1365
1484
  // no explicit permission override inherits the creating session's effective
@@ -1664,7 +1783,6 @@ export async function createSessionForRequestWithOutcome(
1664
1783
  model,
1665
1784
  });
1666
1785
  }
1667
- const creationInitiator = creationInitiatorForGrant(grant);
1668
1786
  let createOutcome: CreateSessionOutcome;
1669
1787
  try {
1670
1788
  createOutcome = await createAndStartSessionWithOutcome({
@@ -1727,7 +1845,11 @@ export async function createSessionForRequestWithOutcome(
1727
1845
  // (after the row exists, before the first turn dispatches). Validation
1728
1846
  // (ownership/liveness) lives in swapActiveSandbox; an invalid target 422s.
1729
1847
  seedTargetSandbox: payload.targetSandboxId
1730
- ? { sandboxId: payload.targetSandboxId, settings, workingDir: payload.workingDir ?? null }
1848
+ ? {
1849
+ sandboxId: payload.targetSandboxId,
1850
+ settings,
1851
+ workingDir: payload.workingDir ?? null,
1852
+ }
1731
1853
  : null,
1732
1854
  consumeNewSessionDraft:
1733
1855
  payload.expectedNewSessionDraftRevision !== undefined
@@ -1810,6 +1932,7 @@ export async function acceptSessionUserMessageWithOutcome(
1810
1932
  sessionId: string,
1811
1933
  input: {
1812
1934
  text: string;
1935
+ annotations?: SubmittedTimelineAnnotation[];
1813
1936
  turnInstructions?: string | null;
1814
1937
  resources?: ResourceRef[];
1815
1938
  model?: string | null;
@@ -1822,7 +1945,12 @@ export async function acceptSessionUserMessageWithOutcome(
1822
1945
  controlEtag?: string | null;
1823
1946
  expectedDraftRevision?: number | null;
1824
1947
  },
1825
- ): Promise<{ accepted: SessionEvent; turn: SessionTurn; replay: boolean }> {
1948
+ ): Promise<{
1949
+ accepted: SessionEvent;
1950
+ turn: SessionTurn;
1951
+ interruptionCount: number;
1952
+ replay: boolean;
1953
+ }> {
1826
1954
  const { settings, db, bus, workflowClient, objectStorage } = deps;
1827
1955
  await requireSessionAuthorization(deps, grant, {
1828
1956
  sessionId,
@@ -1864,6 +1992,12 @@ export async function acceptSessionUserMessageWithOutcome(
1864
1992
  latencyModeSource: input.latencyMode == null ? "session" : "explicit",
1865
1993
  });
1866
1994
  const requestedResources = normalizeResources(input.resources ?? []);
1995
+ const annotations = await validateSubmittedTimelineAnnotations(
1996
+ db,
1997
+ workspaceId,
1998
+ sessionId,
1999
+ input.annotations ?? [],
2000
+ );
1867
2001
  await requireLimit(deps, {
1868
2002
  accountId: grant.accountId,
1869
2003
  workspaceId,
@@ -1872,7 +2006,9 @@ export async function acceptSessionUserMessageWithOutcome(
1872
2006
  model: effectiveModel,
1873
2007
  });
1874
2008
  if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
1875
- throw new HTTPException(503, { message: "object storage is not configured" });
2009
+ throw new HTTPException(503, {
2010
+ message: "object storage is not configured",
2011
+ });
1876
2012
  }
1877
2013
  await validateFileResources(db, workspaceId, requestedResources);
1878
2014
  await validateGitHubRepositorySelection(db, workspaceId, [
@@ -1885,7 +2021,9 @@ export async function acceptSessionUserMessageWithOutcome(
1885
2021
  session: existingSession,
1886
2022
  updates: input.mcpCredentialUpdates ?? [],
1887
2023
  });
1888
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2024
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
2025
+ subjectId: grant.subjectId,
2026
+ });
1889
2027
  const personalConnectionDelegations = await freezePersonalConnectionDelegations({
1890
2028
  db,
1891
2029
  workspaceId,
@@ -1894,7 +2032,7 @@ export async function acceptSessionUserMessageWithOutcome(
1894
2032
  source: personalConnectionDelegationSourceForGrant(grant),
1895
2033
  });
1896
2034
  const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
1897
- const { accepted, turn, replay } = await postUserMessageTurn({
2035
+ const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
1898
2036
  db,
1899
2037
  bus,
1900
2038
  workflowClient,
@@ -1903,6 +2041,7 @@ export async function acceptSessionUserMessageWithOutcome(
1903
2041
  workspaceId,
1904
2042
  sessionId,
1905
2043
  text: input.text,
2044
+ annotations,
1906
2045
  turnInstructions: input.turnInstructions ?? null,
1907
2046
  resources: requestedResources,
1908
2047
  model: input.model ?? null,
@@ -1933,24 +2072,10 @@ export async function acceptSessionUserMessageWithOutcome(
1933
2072
  ? { expectedDraftRevision: input.expectedDraftRevision }
1934
2073
  : {}),
1935
2074
  ...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
2075
+ recordAgentRunUsage: true,
2076
+ ...(deps.schedulePromptPostCommit ? { schedulePostCommit: deps.schedulePromptPostCommit } : {}),
1936
2077
  });
1937
- await recordWorkspaceUsage(deps, {
1938
- accountId: grant.accountId,
1939
- workspaceId,
1940
- subjectId: grant.subjectId,
1941
- eventType: "agent_run.created",
1942
- quantity: 1,
1943
- unit: "run",
1944
- sourceResourceType: "session_turn",
1945
- sourceResourceId: turn.id,
1946
- sessionId,
1947
- turnId: turn.id,
1948
- initiator: turn.initiator,
1949
- initiatorContext: turn.initiatorContext,
1950
- origin: turn.source,
1951
- idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`,
1952
- });
1953
- return { accepted, turn, replay };
2078
+ return { accepted, turn, interruptionCount, replay };
1954
2079
  }
1955
2080
 
1956
2081
  /** Backward-compatible entity-returning path used by existing REST callers. */
@@ -1960,15 +2085,20 @@ export async function acceptSessionUserMessage(
1960
2085
  workspaceId: Parameters<typeof acceptSessionUserMessageWithOutcome>[2],
1961
2086
  sessionId: Parameters<typeof acceptSessionUserMessageWithOutcome>[3],
1962
2087
  input: Parameters<typeof acceptSessionUserMessageWithOutcome>[4],
1963
- ): Promise<{ accepted: SessionEvent; turn: SessionTurn }> {
1964
- const { accepted, turn } = await acceptSessionUserMessageWithOutcome(
2088
+ ): Promise<{
2089
+ accepted: SessionEvent;
2090
+ turn: SessionTurn;
2091
+ interruptionCount: number;
2092
+ replay: boolean;
2093
+ }> {
2094
+ const { accepted, turn, interruptionCount, replay } = await acceptSessionUserMessageWithOutcome(
1965
2095
  deps,
1966
2096
  grant,
1967
2097
  workspaceId,
1968
2098
  sessionId,
1969
2099
  input,
1970
2100
  );
1971
- return { accepted, turn };
2101
+ return { accepted, turn, interruptionCount, replay };
1972
2102
  }
1973
2103
 
1974
2104
  /**
@@ -2002,7 +2132,12 @@ export async function updateSessionTitle(
2002
2132
  surface: "core",
2003
2133
  });
2004
2134
  const workspaceId = grant.workspaceId;
2005
- const result = await updateSessionTitleRow(db, { workspaceId, sessionId, title, source });
2135
+ const result = await updateSessionTitleRow(db, {
2136
+ workspaceId,
2137
+ sessionId,
2138
+ title,
2139
+ source,
2140
+ });
2006
2141
  if (result.updated) {
2007
2142
  await appendAndPublishEvents(db, bus, workspaceId, sessionId, [
2008
2143
  {
@@ -2053,7 +2188,9 @@ export async function updateSessionMcpApprovalPolicy(
2053
2188
  async (_session, context) => {
2054
2189
  const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
2055
2190
  if (!result.server) {
2056
- throw new HTTPException(404, { message: "session MCP server not found" });
2191
+ throw new HTTPException(404, {
2192
+ message: "session MCP server not found",
2193
+ });
2057
2194
  }
2058
2195
  outcome.server = result.server;
2059
2196
  return {
@@ -2070,6 +2207,7 @@ export async function updateSessionMcpApprovalPolicy(
2070
2207
  : [],
2071
2208
  };
2072
2209
  },
2210
+ { activity: "semantic" },
2073
2211
  );
2074
2212
  const updatedServer = outcome.server;
2075
2213
  if (!updatedServer) {
@@ -2153,6 +2291,7 @@ export async function updateSessionToolPolicy(
2153
2291
  deps.db,
2154
2292
  grant.workspaceId,
2155
2293
  deps.settings,
2294
+ { subjectId: grant.subjectId },
2156
2295
  );
2157
2296
  const runtimeSettings = settingsWithSessionMcpServerMetadata(
2158
2297
  capabilityRuntimeSettings,
@@ -2168,7 +2307,9 @@ export async function updateSessionToolPolicy(
2168
2307
  (tool) => !validatedIds.has(`${tool.kind}:${tool.id}`),
2169
2308
  );
2170
2309
  if (unknown) {
2171
- throw new HTTPException(422, { message: `unknown MCP server id: ${unknown.id}` });
2310
+ throw new HTTPException(422, {
2311
+ message: `unknown MCP server id: ${unknown.id}`,
2312
+ });
2172
2313
  }
2173
2314
  return withFirstPartyTools(validatedTools, runtimeSettings);
2174
2315
  })()
@@ -2197,7 +2338,9 @@ export async function updateSessionToolPolicy(
2197
2338
  if (session.parentSessionId) {
2198
2339
  const parent = await context.getLockedSession(session.parentSessionId);
2199
2340
  if (!parent) {
2200
- throw new HTTPException(409, { message: "parent session is no longer available" });
2341
+ throw new HTTPException(409, {
2342
+ message: "parent session is no longer available",
2343
+ });
2201
2344
  }
2202
2345
  const parentTracksWorkspaceDefaults = parent.toolPolicy?.mode === "workspace_default";
2203
2346
  const parentEffective = withFirstPartyTools(
@@ -2310,7 +2453,7 @@ export async function updateSessionToolPolicy(
2310
2453
  },
2311
2454
  };
2312
2455
  },
2313
- { lockParentSession: true },
2456
+ { activity: "semantic", lockParentSession: true },
2314
2457
  );
2315
2458
  if (events.length > 0) {
2316
2459
  await publishDurableSessionEvents(deps.bus, grant.workspaceId, sessionId, events);