@opengeni/worker-bundle 1.0.0-canary.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 (244) hide show
  1. package/dist/activities/agent-run-admission.d.ts +3 -1
  2. package/dist/activities/agent-turn/admission.d.ts +24 -3
  3. package/dist/activities/agent-turn/agent-build.d.ts +4 -0
  4. package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
  5. package/dist/activities/agent-turn/code-search.d.ts +60 -0
  6. package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
  7. package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
  8. package/dist/activities/agent-turn/codex.d.ts +3 -2
  9. package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
  10. package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
  11. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  12. package/dist/activities/agent-turn/errors.d.ts +82 -11
  13. package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
  14. package/dist/activities/agent-turn/final-reply.d.ts +18 -0
  15. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  16. package/dist/activities/agent-turn/finalization.d.ts +4 -2
  17. package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
  18. package/dist/activities/agent-turn/governance-model.d.ts +9 -2
  19. package/dist/activities/agent-turn/history-sink.d.ts +2 -0
  20. package/dist/activities/agent-turn/history.d.ts +2 -0
  21. package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
  22. package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
  23. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  24. package/dist/activities/agent-turn/model-usage.d.ts +6 -0
  25. package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
  26. package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
  27. package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
  28. package/dist/activities/agent-turn/quiescence.d.ts +7 -7
  29. package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
  30. package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
  31. package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
  32. package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
  33. package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
  34. package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
  35. package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
  36. package/dist/activities/agent-turn/session-title.d.ts +34 -2
  37. package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
  38. package/dist/activities/agent-turn/skill-read.d.ts +70 -2
  39. package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
  40. package/dist/activities/agent-turn/skill-search.d.ts +3 -1
  41. package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
  42. package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
  43. package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
  44. package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
  45. package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
  46. package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
  47. package/dist/activities/agent-turn/turn-context.d.ts +29 -0
  48. package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
  49. package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
  50. package/dist/activities/automations.d.ts +3 -1
  51. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  52. package/dist/activities/codemode-dispatcher.d.ts +9 -2
  53. package/dist/activities/codex-capacity.d.ts +18 -2
  54. package/dist/activities/codex-rotation.d.ts +7 -1
  55. package/dist/activities/context-compaction.d.ts +16 -2
  56. package/dist/activities/gmail-files.d.ts +32 -0
  57. package/dist/activities/goals.d.ts +8 -0
  58. package/dist/activities/knowledge-indexing.d.ts +19 -0
  59. package/dist/activities/mcp-account-routes.d.ts +3 -0
  60. package/dist/activities/mcp-credentials.d.ts +12 -13
  61. package/dist/activities/mcp-operation-authority.d.ts +0 -5
  62. package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
  63. package/dist/activities/open-suffix-resume.d.ts +13 -0
  64. package/dist/activities/parent-wake.d.ts +6 -12
  65. package/dist/activities/retained-screenshots.d.ts +7 -2
  66. package/dist/activities/rig-verification.d.ts +2 -3
  67. package/dist/activities/run-credential-renewal.d.ts +1 -0
  68. package/dist/activities/run-credentials.d.ts +15 -3
  69. package/dist/activities/sandbox-images.d.ts +1 -1
  70. package/dist/activities/sandbox-lease.d.ts +47 -9
  71. package/dist/activities/scheduled-tasks.d.ts +4 -35
  72. package/dist/activities/session-state.d.ts +70 -3
  73. package/dist/activities/types.d.ts +55 -6
  74. package/dist/activities/workspace-capture.d.ts +2 -0
  75. package/dist/activities/workspace-credential-provider.d.ts +46 -0
  76. package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
  77. package/dist/activities-control-BNPTRTOE.js.map +1 -0
  78. package/dist/activities-control.d.ts +182 -4
  79. package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
  80. package/dist/activities-turn-5LGZOSVU.js.map +1 -0
  81. package/dist/activities.d.ts +182 -4
  82. package/dist/artifact-materializer-entry.js +5 -2
  83. package/dist/artifact-materializer-entry.js.map +1 -1
  84. package/dist/artifact-outbox-entry.js +5 -2
  85. package/dist/artifact-outbox-entry.js.map +1 -1
  86. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  87. package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
  88. package/dist/chunk-3ZTRWDMN.js.map +1 -0
  89. package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
  90. package/dist/chunk-4ER3YXOT.js.map +1 -0
  91. package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
  92. package/dist/chunk-LJX7Z2KP.js.map +1 -0
  93. package/dist/editable-artifact-materializer-service.d.ts +2 -2
  94. package/dist/editable-artifact-outbox-service.d.ts +2 -2
  95. package/dist/host-export-pump.d.ts +3 -1
  96. package/dist/host-export.d.ts +1 -1
  97. package/dist/host-export.js +1 -1
  98. package/dist/http.d.ts +1 -1
  99. package/dist/index.d.ts +95 -3
  100. package/dist/index.js +137 -43
  101. package/dist/index.js.map +1 -1
  102. package/dist/observability-metrics.d.ts +118 -8
  103. package/dist/retained-process-diagnostics.d.ts +4 -0
  104. package/dist/retained-process-retry.d.ts +7 -0
  105. package/dist/sandbox-resume.d.ts +55 -13
  106. package/dist/sandbox-routing.d.ts +5 -3
  107. package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
  108. package/dist/session-event-fanout.d.ts +6 -0
  109. package/dist/warm-capture-holder.d.ts +3 -0
  110. package/dist/worker-service-lifecycle.d.ts +12 -0
  111. package/dist/workflow-bundle.js +273 -41
  112. package/dist/workflows/activities.d.ts +1 -1
  113. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  114. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  115. package/dist/workflows/session.d.ts +36 -0
  116. package/dist/workflows.d.ts +1 -0
  117. package/package.json +21 -19
  118. package/src/activities/agent-run-admission.ts +17 -1
  119. package/src/activities/agent-turn/admission.ts +66 -3
  120. package/src/activities/agent-turn/agent-build.ts +84 -10
  121. package/src/activities/agent-turn/claim.ts +186 -43
  122. package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
  123. package/src/activities/agent-turn/code-search.ts +275 -0
  124. package/src/activities/agent-turn/codex-capacity.ts +172 -8
  125. package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
  126. package/src/activities/agent-turn/codex.ts +13 -5
  127. package/src/activities/agent-turn/compaction-prep.ts +170 -102
  128. package/src/activities/agent-turn/credential-leases.ts +122 -216
  129. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  130. package/src/activities/agent-turn/errors.ts +647 -48
  131. package/src/activities/agent-turn/failure-settlement.ts +682 -130
  132. package/src/activities/agent-turn/final-reply.ts +46 -0
  133. package/src/activities/agent-turn/finalization-monitor.ts +120 -0
  134. package/src/activities/agent-turn/finalization.ts +146 -43
  135. package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
  136. package/src/activities/agent-turn/governance-model.ts +69 -28
  137. package/src/activities/agent-turn/history-sink.ts +16 -2
  138. package/src/activities/agent-turn/history.ts +38 -11
  139. package/src/activities/agent-turn/input-wait-reply.ts +103 -0
  140. package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
  141. package/src/activities/agent-turn/media-artifacts.ts +2 -1
  142. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  143. package/src/activities/agent-turn/model-usage.ts +64 -3
  144. package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
  145. package/src/activities/agent-turn/optional-repositories.ts +224 -0
  146. package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
  147. package/src/activities/agent-turn/quiescence.ts +10 -7
  148. package/src/activities/agent-turn/recovery-warning.ts +16 -0
  149. package/src/activities/agent-turn/refresh-credentials.ts +44 -0
  150. package/src/activities/agent-turn/run-credentials.ts +116 -55
  151. package/src/activities/agent-turn/run.ts +320 -93
  152. package/src/activities/agent-turn/sandbox-establish.ts +84 -8
  153. package/src/activities/agent-turn/sandbox-provision.ts +3 -1
  154. package/src/activities/agent-turn/sandbox-route.ts +4 -21
  155. package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
  156. package/src/activities/agent-turn/session-title.ts +81 -5
  157. package/src/activities/agent-turn/skill-checkout.ts +111 -24
  158. package/src/activities/agent-turn/skill-install.ts +1 -1
  159. package/src/activities/agent-turn/skill-read.ts +374 -28
  160. package/src/activities/agent-turn/skill-remove.ts +46 -0
  161. package/src/activities/agent-turn/skill-save.ts +1 -1
  162. package/src/activities/agent-turn/skill-search.ts +10 -1
  163. package/src/activities/agent-turn/skill-selection.ts +4 -0
  164. package/src/activities/agent-turn/skill-tools.ts +112 -18
  165. package/src/activities/agent-turn/skill-transfer.ts +68 -23
  166. package/src/activities/agent-turn/stream-attempt.ts +313 -77
  167. package/src/activities/agent-turn/subscription-lease.ts +115 -0
  168. package/src/activities/agent-turn/tool-environment.ts +316 -167
  169. package/src/activities/agent-turn/tool-policy.ts +80 -1
  170. package/src/activities/agent-turn/turn-context.ts +35 -0
  171. package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
  172. package/src/activities/agent-turn/xai-capacity.ts +94 -51
  173. package/src/activities/automations.ts +76 -4
  174. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  175. package/src/activities/capabilities.ts +108 -11
  176. package/src/activities/codemode-dispatcher.ts +36 -8
  177. package/src/activities/codex-capacity.ts +60 -25
  178. package/src/activities/codex-rotation.ts +39 -17
  179. package/src/activities/context-compaction.ts +75 -18
  180. package/src/activities/gmail-files.ts +88 -0
  181. package/src/activities/goals.ts +116 -121
  182. package/src/activities/google-drive-publication.ts +1 -3
  183. package/src/activities/image-generation-operation.ts +1 -1
  184. package/src/activities/knowledge-indexing.ts +269 -15
  185. package/src/activities/knowledge-source-sync.ts +10 -0
  186. package/src/activities/mcp-account-routes.ts +17 -0
  187. package/src/activities/mcp-credentials.ts +83 -194
  188. package/src/activities/mcp-operation-authority.ts +16 -41
  189. package/src/activities/model-call-fact-reconciler.ts +112 -0
  190. package/src/activities/open-suffix-resume.ts +15 -2
  191. package/src/activities/parent-wake.ts +46 -21
  192. package/src/activities/retained-screenshots.ts +82 -11
  193. package/src/activities/rig-verification.ts +38 -37
  194. package/src/activities/run-credential-renewal.ts +13 -1
  195. package/src/activities/run-credentials.ts +59 -17
  196. package/src/activities/run-input.ts +25 -16
  197. package/src/activities/sandbox-images.ts +0 -1
  198. package/src/activities/sandbox-lease.ts +798 -82
  199. package/src/activities/scheduled-tasks.ts +550 -178
  200. package/src/activities/session-state.ts +245 -6
  201. package/src/activities/site-auth-maintenance.ts +3 -0
  202. package/src/activities/types.ts +72 -5
  203. package/src/activities/workflow-wake.ts +2 -17
  204. package/src/activities/workspace-capture.ts +13 -2
  205. package/src/activities/workspace-credential-provider.ts +283 -0
  206. package/src/activities-control.ts +4 -0
  207. package/src/activities-turn.ts +5 -0
  208. package/src/activities.ts +13 -0
  209. package/src/activity-services.ts +16 -4
  210. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  211. package/src/editable-artifact-hint-broker.ts +3 -0
  212. package/src/editable-artifact-materializer-service.ts +7 -3
  213. package/src/editable-artifact-outbox-service.ts +4 -4
  214. package/src/github-rest-mcp.ts +1 -2
  215. package/src/host-export-pump.ts +26 -4
  216. package/src/host-export.ts +3 -0
  217. package/src/http.ts +4 -2
  218. package/src/index.ts +99 -37
  219. package/src/observability-metrics.ts +510 -5
  220. package/src/personal-github-git-credentials.ts +2 -1
  221. package/src/pr-review-credentials.ts +1 -3
  222. package/src/retained-process-diagnostics.ts +50 -0
  223. package/src/retained-process-retry.ts +36 -0
  224. package/src/sandbox-resume.ts +388 -101
  225. package/src/sandbox-routing.ts +150 -25
  226. package/src/sandbox-snapshot-diagnostics.ts +112 -0
  227. package/src/session-event-fanout.ts +23 -0
  228. package/src/warm-capture-holder.ts +15 -0
  229. package/src/worker-service-lifecycle.ts +53 -0
  230. package/src/workflows/activities.ts +16 -1
  231. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  232. package/src/workflows/file-upload-reaper.ts +2 -0
  233. package/src/workflows/scheduled-tasks.ts +26 -13
  234. package/src/workflows/session.ts +242 -22
  235. package/src/workflows.ts +4 -0
  236. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  237. package/dist/activities/packs.d.ts +0 -55
  238. package/dist/activities-control-HMFGSKC5.js.map +0 -1
  239. package/dist/activities-turn-AP67JADS.js.map +0 -1
  240. package/dist/chunk-A4XQWVZK.js.map +0 -1
  241. package/dist/chunk-CYITU5UH.js.map +0 -1
  242. package/dist/chunk-RAMNYSAW.js.map +0 -1
  243. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
  244. package/src/activities/packs.ts +0 -166
@@ -1,4 +1,8 @@
1
+ import { measureMcpPhase, withMcpCallIdentity } from "@opengeni/observability";
1
2
  import {
3
+ appendSessionHistoryItems,
4
+ sessionTurnFinalReplyFacts,
5
+ sessionTurnHasFinalReplyNudge,
2
6
  getSessionEvent,
3
7
  getHumanInputResumeForEvent,
4
8
  getSessionHumanInputRequest,
@@ -15,13 +19,17 @@ import {
15
19
  } from "@opengeni/db";
16
20
  import { publishDurableSessionEvents } from "@opengeni/events";
17
21
  import {
22
+ AssistantMessagePhaseTracker,
18
23
  normalizeModelCallUsage,
19
24
  normalizeSdkEvent,
25
+ withMcpToolDisplayMetadata,
26
+ withToolCallFamily,
20
27
  extractOpenSuffixFromRunState,
21
28
  assertOpenSuffixResumable,
22
29
  interruptionKindForCallItem,
23
30
  releaseMcpResultCustomDataFromSdkEvent,
24
31
  findCompactionNeededError,
32
+ compactionProviderRejection,
25
33
  withRunCredentialsSession,
26
34
  runOwnedSandboxSetup,
27
35
  type SandboxFileDownload,
@@ -59,7 +67,7 @@ import {
59
67
  import {
60
68
  compactRetainedScreenshotHistory,
61
69
  sdkEventContainsInlineImage,
62
- retainComputerScreenshot,
70
+ retainSessionScreenshot,
63
71
  typedScreenshotFromSdkEvent,
64
72
  unavailableRetainedSessionImage,
65
73
  } from "../retained-screenshots";
@@ -69,6 +77,7 @@ import {
69
77
  isCompletedGeneratedImageSdkEvent,
70
78
  } from "../generated-images";
71
79
  import { ToolResultSpill } from "./tool-result-spill";
80
+ import { ownedTurnSandboxForAgent } from "./turn-sandbox-access";
72
81
  import { createTurnCredentialLeases } from "./credential-leases";
73
82
  import { createTurnMediaArtifacts } from "./media-artifacts";
74
83
  import { createTurnHistorySink } from "./history-sink";
@@ -79,23 +88,27 @@ import {
79
88
  import {
80
89
  OPEN_SUFFIX_RUN_STATE_BLOB,
81
90
  resolveWorkspaceAgentHumanInputEnabled,
91
+ withPublicApprovalFields,
82
92
  type RetainedArtifactMetadata,
83
93
  type SessionEvent,
94
+ type SessionTurn,
84
95
  } from "@opengeni/contracts";
85
96
  import { createModelCheckpointMemoryCollector } from "../../model-checkpoint-memory-collector";
97
+ import { createModelCallAdmission } from "./model-call-admission";
86
98
 
87
99
  import {
88
100
  assertWorkspaceHumanInputAllowed,
89
101
  stableHumanInputRequestId,
90
102
  stableInteractionInterventionId,
91
103
  stableInteractionInterventionOperationId,
92
- BudgetExhaustedError,
93
- ensureRunAllowed,
104
+ ensureRunAllowedBetweenModelCalls,
94
105
  } from "./admission";
95
106
  import {
96
107
  compactionFailureReason,
97
108
  safeErrorDiagnostic,
98
109
  compactionFailureReasonFromError,
110
+ compactionFailureTurnEventPayload,
111
+ modelPreparationFailureEventPayload,
99
112
  isCompactionSummaryFailure,
100
113
  PostCompactionContinuationEmptyError,
101
114
  shouldRecoverCompactionProviderFailure,
@@ -104,9 +117,9 @@ import {
104
117
  import {
105
118
  pendingToolCallFromSdkEvent,
106
119
  toolCallProducesRetainableSessionImage,
120
+ retainableBrowserScreenshotToolCall,
107
121
  completedToolCallFromSdkEvent,
108
122
  } from "./history";
109
- import { checkpointHistoryBeforeProviderDispatch } from "./provider-dispatch-barrier";
110
123
  import {
111
124
  modelUsageSourceKey,
112
125
  recordCompletedModelCallBeforeOwnershipFences,
@@ -121,14 +134,19 @@ import {
121
134
  recordModelUsageAndDebitCredits,
122
135
  recordAuthoritativeModelCallFact,
123
136
  } from "./model-usage";
124
- import { startParallelSessionTitleGeneration } from "./session-title";
137
+ import {
138
+ sessionTitleGenerationOptions,
139
+ startParallelSessionTitleGeneration,
140
+ } from "./session-title";
125
141
  import {
126
142
  assertAgentStreamNotCancelled,
127
143
  assertSuccessfulAgentStreamCompletion,
128
144
  requireAgentStreamFinalOutput,
129
145
  } from "./quiescence";
146
+ import { inputWaitReply, latestDurableTurnMessageText } from "./input-wait-reply";
130
147
  import { waitForTurnOperation } from "./sandbox-provision";
131
148
  import { createSharedRigSetupCoordinator } from "./sandbox-shared-preparation";
149
+ import { finalReplyNudge, needsFinalReply } from "./final-reply";
132
150
 
133
151
  import type { CompactionSummarizer } from "../context-compaction";
134
152
  import type { TurnExecutionPolicyV1 } from "@opengeni/contracts";
@@ -169,11 +187,12 @@ export type TurnStreamAttemptDeps = {
169
187
  providerTurn: ProviderTurnState;
170
188
  leases: ReturnType<typeof createTurnCredentialLeases>;
171
189
  historySink: ReturnType<typeof createTurnHistorySink>;
190
+ checkpointBeforeProviderDispatch: () => Promise<void>;
172
191
  media: ReturnType<typeof createTurnMediaArtifacts>;
173
192
  toolResultSpill: ToolResultSpill;
174
193
  claimedResult: ClaimedResult;
175
194
  flushRuntimeBatcher: () => Promise<void>;
176
- finalizeTurnOpStreamOps: () => Promise<void>;
195
+ finalizeTurnOpStreamOps: (toolCallIds?: readonly string[]) => Promise<void>;
177
196
  runWorkspaceMutationForSandbox: <T>(
178
197
  sandbox: ResumedTurnSandbox,
179
198
  operation: string,
@@ -221,7 +240,13 @@ export type TurnStreamAttemptDeps = {
221
240
  activeSandboxBackend: Settings["sandboxBackend"] | undefined;
222
241
  groupBoxBackend: Settings["sandboxBackend"];
223
242
  turnExecutionPolicy: TurnExecutionPolicyV1;
224
- turn: { executionGeneration: number; model: string };
243
+ turn: Pick<SessionTurn, "initiator" | "initiatorContext"> & {
244
+ initiatingHumanSubjectId: string | null;
245
+ id: string;
246
+ executionGeneration: number;
247
+ model: string;
248
+ source?: string;
249
+ };
225
250
  trigger: NonNullable<Awaited<ReturnType<typeof getSessionEvent>>>;
226
251
  humanInputResume: Awaited<ReturnType<typeof getHumanInputResumeForEvent>>;
227
252
  attachPendingUpdatesAfterOpenSuffix: () => Promise<boolean>;
@@ -287,6 +312,7 @@ export async function runTurnStreamAttempt(
287
312
  providerTurn,
288
313
  leases,
289
314
  historySink,
315
+ checkpointBeforeProviderDispatch,
290
316
  media,
291
317
  toolResultSpill,
292
318
  claimedResult,
@@ -560,15 +586,45 @@ export async function runTurnStreamAttempt(
560
586
  // calling runStreamAttempt again; resetting this state there would reuse
561
587
  // the first no-response-ID fallback key and suppress a real model call.
562
588
  const modelResponseState = createModelResponseEventState(claimedModelUsageSourceKeys);
589
+ // Text of the newest assistant message any stream of this activity completed
590
+ // durably: the reply a wait-ended human turn records on turn.completed.
591
+ let latestAssistantMessageText: string | null = null;
563
592
  let workerPreparationTotalRecorded = false;
593
+ let toolsExecuted = false;
594
+ let finalReplyNudged = false;
595
+ const revalidateModelCallAdmission = async () => {
596
+ await historySink.reconcileConversationTruth({ requireDurable: true });
597
+ await ensureRunAllowedBetweenModelCalls({
598
+ settings,
599
+ db,
600
+ accountId: input.accountId,
601
+ workspaceId: input.workspaceId,
602
+ isExternallyBilledTurn: billingState.isExternallyBilledTurn,
603
+ entitlements,
604
+ chargesOpenGeniCredits: billingState.chargesOpenGeniCredits,
605
+ countsTowardTokenCap: billingState.countsTowardTokenCap,
606
+ initiatingHumanSubjectId: turn.initiatingHumanSubjectId,
607
+ });
608
+ };
564
609
  const runStreamAttempt = async (options: {
565
610
  requireTerminalModelResponse: boolean;
566
- }): Promise<RunAgentTurnResult> => {
611
+ }): Promise<RunAgentTurnResult | "reply_nudge"> => {
567
612
  if (!runInput) {
568
613
  throw new Error("Run input was not prepared");
569
614
  }
570
- const responseCountBeforeStream = modelResponseState.responseCount;
615
+ // The previous stream was persisted before compaction; the sink is now
616
+ // seeded from its durable replacement. Do not reconcile the old prefix
617
+ // against that replacement while checking the next stream's admission.
571
618
  eventing.stream = undefined;
619
+ // Compaction commits its paid usage and replacement history before this
620
+ // boundary, both during preparation and in-activity recovery. Revalidate
621
+ // the accepted turn's frozen human before another stream can dispatch.
622
+ if (options.requireTerminalModelResponse) await revalidateModelCallAdmission();
623
+ const modelCallAdmission = createModelCallAdmission({
624
+ signal: runtimeCancellationSignal,
625
+ admit: revalidateModelCallAdmission,
626
+ });
627
+ const responseCountBeforeStream = modelResponseState.responseCount;
572
628
  eventing.batcher = null;
573
629
  // The SDK emits every processed call item for one model response before
574
630
  // it emits any result for that response. Keep that response-local batch
@@ -578,6 +634,12 @@ export async function runTurnStreamAttempt(
578
634
  let currentToolBatchCallIds = new Set<string>();
579
635
  let currentToolBatchCompletedCallIds = new Set<string>();
580
636
  let streamSawPerResponseUsage = false;
637
+ // Deltas learn the phase a provider declares when it announces a message;
638
+ // undeclared messages the SDK runs past (same response asks for tools)
639
+ // are commentary. Every SDK event of this stream is normalized once.
640
+ const messagePhases = new AssistantMessagePhaseTracker();
641
+ // Text of the newest assistant message this stream completed durably.
642
+ let latestStreamedAssistantText: string | null = null;
581
643
  // Actual input tokens of the most recent model response this turn; the
582
644
  // pre-read trigger for the NEXT turn. Persisted at every turn-end path.
583
645
  throwIfWorkerShuttingDown();
@@ -589,7 +651,7 @@ export async function runTurnStreamAttempt(
589
651
  let fallbackProviderRequestStartedAt: number | null = null;
590
652
  let fallbackProviderRequestLifecycleStartedAt: number | null = null;
591
653
  const recordFallbackProviderDispatchAtWire = async (): Promise<void> => {
592
- await checkpointHistoryBeforeProviderDispatch(historySink);
654
+ await checkpointBeforeProviderDispatch();
593
655
  if (
594
656
  providerPublishesNativeRequestEvents ||
595
657
  eventing.firstModelRequestPreparationRecorded ||
@@ -650,8 +712,7 @@ export async function runTurnStreamAttempt(
650
712
  });
651
713
  }
652
714
  };
653
- const ownedEstablished =
654
- sandboxState.resolvedSandbox?.established ?? sandboxState.lazyOwnedSandbox;
715
+ const ownedEstablished = ownedTurnSandboxForAgent(sandboxState);
655
716
  const runStreamOnce = async (): ReturnType<OpenGeniRuntime["runStream"]> => {
656
717
  const eagerResolvedSandbox = sandboxState.resolvedSandbox;
657
718
  // Eager owned sessions must settle the exact platform-setup provider
@@ -759,8 +820,8 @@ export async function runTurnStreamAttempt(
759
820
  eventing.firstModelRequestPreparationStartedAt = performance.now();
760
821
  eventing.firstModelRequestCheckpointAt = eventing.firstModelRequestPreparationStartedAt;
761
822
  }
762
- const providerDispatchStartedAt = performance.now();
763
- let providerDispatchOutcome: "completed" | "failed" = "completed";
823
+ const streamInitializationStartedAt = performance.now();
824
+ let streamInitializationOutcome: "completed" | "failed" = "completed";
764
825
  try {
765
826
  // This histogram describes worker preparation until the first entry
766
827
  // into the runtime. Lazy SDK request preparation and the durable
@@ -779,6 +840,8 @@ export async function runTurnStreamAttempt(
779
840
  }
780
841
  attempt.modelRequestStarted = true;
781
842
  return await runtime.runStream(agent, runInput!, eventing.modelRunSettings, {
843
+ beforeModelRequest: modelCallAdmission.beforeModelRequest,
844
+ onModelResponse: modelCallAdmission.onModelResponse,
782
845
  signal: runtimeCancellationSignal,
783
846
  sandboxEnvironment,
784
847
  onModelVisibleContext: async (snapshot) => {
@@ -885,6 +948,12 @@ export async function runTurnStreamAttempt(
885
948
  ...(!providerPublishesNativeRequestEvents
886
949
  ? {
887
950
  onModelTransportStarted: recordFallbackProviderDispatchAtWire,
951
+ onModelTransportDispatched: (clock) => {
952
+ eventing.initialModelWireDispatch.record(
953
+ { provider: streamProvider, dispatchId },
954
+ clock,
955
+ );
956
+ },
888
957
  }
889
958
  : {}),
890
959
  ...(eventing.toolCancellationFenceRef.current
@@ -894,15 +963,15 @@ export async function runTurnStreamAttempt(
894
963
  : {}),
895
964
  });
896
965
  } catch (error) {
897
- providerDispatchOutcome = "failed";
966
+ streamInitializationOutcome = "failed";
898
967
  throw error;
899
968
  } finally {
900
969
  recordTurnStartupPhase(observability, {
901
- phase: "provider_dispatch",
970
+ phase: "runtime_stream_initialization",
902
971
  provider: turnExecutionPolicy.providerId,
903
972
  backend: activeSandboxBackend ?? groupBoxBackend,
904
- outcome: providerDispatchOutcome,
905
- durationSeconds: (performance.now() - providerDispatchStartedAt) / 1_000,
973
+ outcome: streamInitializationOutcome,
974
+ durationSeconds: (performance.now() - streamInitializationStartedAt) / 1_000,
906
975
  });
907
976
  }
908
977
  };
@@ -914,7 +983,13 @@ export async function runTurnStreamAttempt(
914
983
  if (leases.xai.lost) {
915
984
  throw new Error("xAI credential lease expired before the model run");
916
985
  }
917
- eventing.stream = await withProviderRequestContext(runStreamOnce);
986
+ try {
987
+ eventing.stream = await withProviderRequestContext(runStreamOnce);
988
+ } catch (error) {
989
+ modelCallAdmission.fail(error);
990
+ modelCallAdmission.close();
991
+ throw error;
992
+ }
918
993
  // Bounded provider label for the streaming SLIs — the resolved registry
919
994
  // provider id (or the built-in OpenAI/Azure provider), never a raw
920
995
  // user-supplied model string.
@@ -960,6 +1035,7 @@ export async function runTurnStreamAttempt(
960
1035
  attemptId: input.attemptId,
961
1036
  dispatchId,
962
1037
  executionGeneration: attempt.executionGeneration,
1038
+ ...eventing.initialModelWireDispatch.payload({ provider: streamProvider, dispatchId }),
963
1039
  },
964
1040
  },
965
1041
  ]);
@@ -977,6 +1053,7 @@ export async function runTurnStreamAttempt(
977
1053
  }
978
1054
  await settleFallbackProviderFirstByte();
979
1055
  let stableToolCallIdsToClear: string[] | null = null;
1056
+ let newlyDurableToolCallId: string | null = null;
980
1057
  let completedCurrentToolBatch = false;
981
1058
  let retainedScreenshotMetadata: RetainedArtifactMetadata | null = null;
982
1059
  let normalizedSdkEvents: ReturnType<typeof normalizeSdkEvent> | null = null;
@@ -1046,6 +1123,10 @@ export async function runTurnStreamAttempt(
1046
1123
  attemptId: input.attemptId,
1047
1124
  dispatchId,
1048
1125
  executionGeneration: attempt.executionGeneration,
1126
+ ...eventing.initialModelWireDispatch.payload({
1127
+ provider: streamProvider,
1128
+ dispatchId,
1129
+ }),
1049
1130
  },
1050
1131
  },
1051
1132
  ]);
@@ -1067,35 +1148,22 @@ export async function runTurnStreamAttempt(
1067
1148
  await historySink.reconcileConversationTruth();
1068
1149
  turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
1069
1150
  modelCheckpointMemoryCollector.schedule(observability);
1070
- try {
1071
- await ensureRunAllowed(
1072
- settings,
1073
- db,
1074
- input.accountId,
1075
- input.workspaceId,
1076
- billingState.isExternallyBilledTurn,
1077
- entitlements,
1078
- billingState.chargesOpenGeniCredits,
1079
- billingState.countsTowardTokenCap,
1080
- );
1081
- } catch (limitError) {
1082
- // Capture the run state at the boundary so the budget valve in
1083
- // the outer catch can end this segment gracefully with full
1084
- // conversation context preserved for the post-top-up resume.
1085
- let serializedRunState: string | null = null;
1086
- try {
1087
- serializedRunState = media.compactMediaRunState(
1088
- String(eventing.stream.state.toString()),
1089
- );
1090
- } catch {
1091
- serializedRunState = null;
1092
- }
1093
- throw new BudgetExhaustedError(
1094
- limitError instanceof Error ? limitError.message : String(limitError),
1095
- serializedRunState,
1096
- );
1097
- }
1151
+ await ensureRunAllowedBetweenModelCalls({
1152
+ settings,
1153
+ db,
1154
+ accountId: input.accountId,
1155
+ workspaceId: input.workspaceId,
1156
+ isExternallyBilledTurn: billingState.isExternallyBilledTurn,
1157
+ entitlements,
1158
+ chargesOpenGeniCredits: billingState.chargesOpenGeniCredits,
1159
+ countsTowardTokenCap: billingState.countsTowardTokenCap,
1160
+ initiatingHumanSubjectId: turn.initiatingHumanSubjectId,
1161
+ serializedRunState: () =>
1162
+ media.compactMediaRunState(String(eventing.stream!.state.toString())),
1163
+ });
1098
1164
  }
1165
+ // Release only after both the debit and frozen-human admission finish.
1166
+ modelCallAdmission.settle(next.value);
1099
1167
  const durableSdkEvent = generatedImageReceipt
1100
1168
  ? compactGeneratedImageSdkEvent(next.value, generatedImageReceipt)
1101
1169
  : next.value;
@@ -1122,10 +1190,17 @@ export async function runTurnStreamAttempt(
1122
1190
  currentToolBatchCallIds.add(pendingToolCall.callId);
1123
1191
  if (toolCallProducesRetainableSessionImage(pendingToolCall.callName)) {
1124
1192
  media.retainedSessionImageCallIds.add(pendingToolCall.callId);
1193
+ if (retainableBrowserScreenshotToolCall(pendingToolCall.callName)) {
1194
+ media.retainedSessionImageKindsByCallId.set(
1195
+ pendingToolCall.callId,
1196
+ "browser_screenshot",
1197
+ );
1198
+ }
1125
1199
  }
1126
1200
  }
1127
1201
  const completedToolCall = completedToolCallFromSdkEvent(durableSdkEvent);
1128
1202
  if (completedToolCall) {
1203
+ toolsExecuted = true;
1129
1204
  retainedScreenshotMetadata =
1130
1205
  media.retainedScreenshotReceiptsByCallId.get(completedToolCall.callId) ?? null;
1131
1206
  const typedScreenshot = retainedScreenshotMetadata
@@ -1144,6 +1219,7 @@ export async function runTurnStreamAttempt(
1144
1219
  toolCallId: completedToolCall.callId,
1145
1220
  toolOutputId: completedToolCall.callId,
1146
1221
  reason: "unsupported",
1222
+ kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId),
1147
1223
  });
1148
1224
  media.retainedScreenshotReceiptsByCallId.set(
1149
1225
  completedToolCall.callId,
@@ -1166,12 +1242,13 @@ export async function runTurnStreamAttempt(
1166
1242
  toolCallId: typedScreenshot.callId,
1167
1243
  toolOutputId: typedScreenshot.toolOutputId,
1168
1244
  reason: "pending",
1245
+ kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId),
1169
1246
  });
1170
1247
  media.retainedScreenshotReceiptsByCallId.set(
1171
1248
  completedToolCall.callId,
1172
1249
  retainedScreenshotMetadata,
1173
1250
  );
1174
- retainedScreenshotMetadata = await retainComputerScreenshot({
1251
+ retainedScreenshotMetadata = await retainSessionScreenshot({
1175
1252
  db,
1176
1253
  objectStorage,
1177
1254
  accountId: input.accountId,
@@ -1180,6 +1257,7 @@ export async function runTurnStreamAttempt(
1180
1257
  turnId: activeTurnId,
1181
1258
  attemptId: input.attemptId,
1182
1259
  output: typedScreenshot,
1260
+ kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId),
1183
1261
  });
1184
1262
  media.retainedScreenshotReceiptsByCallId.set(
1185
1263
  completedToolCall.callId,
@@ -1190,6 +1268,7 @@ export async function runTurnStreamAttempt(
1190
1268
  durableSdkEvent as typeof next.value,
1191
1269
  retainedScreenshotMetadata
1192
1270
  ? {
1271
+ messagePhases,
1193
1272
  toolOutputOverride: retainedScreenshotMetadata,
1194
1273
  retainedOutputEvidence: retainedScreenshotMetadata.available
1195
1274
  ? retainedScreenshotMetadata
@@ -1198,7 +1277,7 @@ export async function runTurnStreamAttempt(
1198
1277
  reason: retainedScreenshotMetadata.reason,
1199
1278
  },
1200
1279
  }
1201
- : {},
1280
+ : { messagePhases },
1202
1281
  );
1203
1282
  const normalizedToolOutput = normalizedSdkEvents.find(
1204
1283
  (event) =>
@@ -1246,6 +1325,7 @@ export async function runTurnStreamAttempt(
1246
1325
  "turn attempt ended while recording a tool-call result",
1247
1326
  );
1248
1327
  }
1328
+ if (recorded.recorded) newlyDurableToolCallId = completedToolCall.callId;
1249
1329
  const videoAcceptance = videoGenerationAcceptancesByCallId.get(completedToolCall.callId);
1250
1330
  if (videoAcceptance && startVideoGenerationWorkflow) {
1251
1331
  try {
@@ -1291,6 +1371,7 @@ export async function runTurnStreamAttempt(
1291
1371
  durableSdkEvent as typeof next.value,
1292
1372
  retainedScreenshotMetadata
1293
1373
  ? {
1374
+ messagePhases,
1294
1375
  toolOutputOverride: retainedScreenshotMetadata,
1295
1376
  retainedOutputEvidence: retainedScreenshotMetadata.available
1296
1377
  ? retainedScreenshotMetadata
@@ -1299,11 +1380,33 @@ export async function runTurnStreamAttempt(
1299
1380
  reason: retainedScreenshotMetadata.reason,
1300
1381
  },
1301
1382
  }
1302
- : {},
1383
+ : { messagePhases },
1303
1384
  );
1304
1385
  for (const event of normalized) {
1386
+ if (event.type === "agent.toolCall.created") {
1387
+ const preparedServers = eventing.preparedTools?.mcpServers ?? [];
1388
+ // Display metadata and the content-free analytics family are
1389
+ // event-copy enrichments only; the executable call is unchanged.
1390
+ event.payload = withToolCallFamily(
1391
+ preparedServers,
1392
+ runSettings.mcpServers,
1393
+ withMcpToolDisplayMetadata(preparedServers, event.payload),
1394
+ );
1395
+ }
1305
1396
  streamTiming.onEvent(event.type);
1306
- await eventing.batcher.push(event);
1397
+ if (event.type === "agent.toolCall.output") {
1398
+ const batcher = eventing.batcher;
1399
+ await withMcpCallIdentity((event.payload as { id: string }).id, () =>
1400
+ measureMcpPhase("event_persistence", () => batcher.push(event)),
1401
+ );
1402
+ } else {
1403
+ await eventing.batcher.push(event);
1404
+ }
1405
+ if (event.type === "agent.message.completed") {
1406
+ // Completed messages are structural: push returns once durable.
1407
+ latestStreamedAssistantText = (event.payload as { text: string }).text;
1408
+ latestAssistantMessageText = latestStreamedAssistantText;
1409
+ }
1307
1410
  }
1308
1411
  // Structural tool-output events await their durable append before
1309
1412
  // push returns. The complete result is now retained in the event
@@ -1331,10 +1434,20 @@ export async function runTurnStreamAttempt(
1331
1434
  }
1332
1435
  for (const callId of stableToolCallIdsToClear) {
1333
1436
  media.retainedSessionImageCallIds.delete(callId);
1437
+ media.retainedSessionImageKindsByCallId.delete(callId);
1334
1438
  }
1335
1439
  }
1440
+ if (newlyDurableToolCallId) {
1441
+ // The exact call/result receipt and structural output event are now
1442
+ // durable even when parallel SDK history is still non-monotonic.
1443
+ // Release only this result owner's completed foreground ops. A
1444
+ // missing/duplicate receipt cannot license new output collection;
1445
+ // the complete-turn hook remains its later durability boundary.
1446
+ await finalizeTurnOpStreamOps([newlyDurableToolCallId]);
1447
+ }
1336
1448
  }
1337
1449
  } catch (error) {
1450
+ modelCallAdmission.fail(error);
1338
1451
  // Event processing can fail while SDK completion is still pending.
1339
1452
  // Close this stream before any failure publication; a legitimate
1340
1453
  // compaction retry may start a new, independently fenced generation.
@@ -1357,6 +1470,10 @@ export async function runTurnStreamAttempt(
1357
1470
  attemptId: input.attemptId,
1358
1471
  dispatchId,
1359
1472
  executionGeneration: attempt.executionGeneration,
1473
+ ...eventing.initialModelWireDispatch.payload({
1474
+ provider: streamProvider,
1475
+ dispatchId,
1476
+ }),
1360
1477
  },
1361
1478
  },
1362
1479
  ]);
@@ -1381,15 +1498,13 @@ export async function runTurnStreamAttempt(
1381
1498
  await eventing.publish!([
1382
1499
  {
1383
1500
  type: "turn.startup.phase.failed",
1384
- payload: {
1385
- phase: "model_preparation",
1386
- durationMs: Math.round(durationMs),
1387
- },
1501
+ payload: modelPreparationFailureEventPayload(error, durationMs),
1388
1502
  },
1389
1503
  ]);
1390
1504
  }
1391
1505
  throw error;
1392
1506
  } finally {
1507
+ modelCallAdmission.close();
1393
1508
  if (!streamDone) {
1394
1509
  // ReadableStream cancellation synchronously trips the Agents SDK's
1395
1510
  // abort controller, but its returned promise may wait for an
@@ -1411,7 +1526,10 @@ export async function runTurnStreamAttempt(
1411
1526
  // External Codemode stays reachable until finalization. Close wait
1412
1527
  // admission before any terminal output/history decision, and drain an
1413
1528
  // already-admitted wait before consulting the actual runner-yield latch.
1414
- await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
1529
+ // Drain trusted receipts before deciding whether an empty stream may need
1530
+ // a handoff. Do not terminally seal the attempt until that decision: the
1531
+ // one same-turn stream still uses the ordinary input-wait admission gate.
1532
+ await eventing.preparedTools?.inputWaitYield?.drainForHandoff(runtimeCancellationSignal);
1415
1533
  if (
1416
1534
  options.requireTerminalModelResponse &&
1417
1535
  !eventing.preparedTools?.inputWaitYield?.yielded &&
@@ -1524,6 +1642,7 @@ export async function runTurnStreamAttempt(
1524
1642
  }
1525
1643
  }
1526
1644
  if (eventing.stream.interruptions.length > 0) {
1645
+ await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
1527
1646
  await historySink.reconcileConversationTruth({ requireDurable: true });
1528
1647
  const approvals = runtime.serializeApprovals(eventing.stream.interruptions);
1529
1648
  const humanInputInterruptions =
@@ -1639,7 +1758,16 @@ export async function runTurnStreamAttempt(
1639
1758
  ? [
1640
1759
  {
1641
1760
  type: "session.requiresAction" as const,
1642
- payload: { approvals },
1761
+ payload: {
1762
+ approvals: approvals.map((approval) =>
1763
+ withPublicApprovalFields(
1764
+ withMcpToolDisplayMetadata(
1765
+ eventing.preparedTools?.mcpServers ?? [],
1766
+ approval,
1767
+ ),
1768
+ ),
1769
+ ),
1770
+ },
1643
1771
  },
1644
1772
  ]
1645
1773
  : []),
@@ -1671,6 +1799,77 @@ export async function runTurnStreamAttempt(
1671
1799
  const finalOutput = String(
1672
1800
  requireAgentStreamFinalOutput(eventing.stream.finalOutput, inputWaitYielded),
1673
1801
  );
1802
+ const durableReplyFacts =
1803
+ !inputWaitYielded && finalOutput.trim().length === 0
1804
+ ? await sessionTurnFinalReplyFacts(db, input.workspaceId, input.sessionId, activeTurnId)
1805
+ : { toolsExecuted: false, completedGoal: false };
1806
+ let emptyFinalReply = false;
1807
+ if (
1808
+ needsFinalReply({
1809
+ output: finalOutput,
1810
+ inputWaitYielded:
1811
+ inputWaitYielded || eventing.preparedTools?.inputWaitYield?.requested === true,
1812
+ interrupted: false, // interruption settlement returned above
1813
+ maintenance: turn.source === "compaction",
1814
+ toolsExecuted: toolsExecuted || finalReplyNudged || durableReplyFacts.toolsExecuted,
1815
+ completedGoal: durableReplyFacts.completedGoal,
1816
+ })
1817
+ ) {
1818
+ await historySink.reconcileConversationTruth({ requireDurable: true });
1819
+ // Consult retained truth, including inactive compacted rows, so an
1820
+ // attempt replacement or compaction cannot spend this bound again.
1821
+ if (
1822
+ finalReplyNudged ||
1823
+ (await sessionTurnHasFinalReplyNudge(
1824
+ db,
1825
+ input.workspaceId,
1826
+ input.sessionId,
1827
+ activeTurnId,
1828
+ finalReplyNudge(activeTurnId).content[0]!.text,
1829
+ ))
1830
+ ) {
1831
+ // A second empty response is a delivery-quality notice, not failed
1832
+ // execution: preserve goal continuation and later machine-input wakes.
1833
+ emptyFinalReply = true;
1834
+ } else {
1835
+ const appended = await appendSessionHistoryItems(db, {
1836
+ accountId: input.accountId,
1837
+ workspaceId: input.workspaceId,
1838
+ sessionId: input.sessionId,
1839
+ turnId: activeTurnId,
1840
+ expectedExecutionGeneration: attempt.executionGeneration,
1841
+ expectedAttemptId: input.attemptId,
1842
+ items: [
1843
+ {
1844
+ position: await nextSessionHistoryPosition(db, input.workspaceId, input.sessionId),
1845
+ item: finalReplyNudge(activeTurnId),
1846
+ },
1847
+ ],
1848
+ });
1849
+ if (!appended) throw new TurnAttemptFencedError("turn ended before final reply handoff");
1850
+ finalReplyNudged = true;
1851
+ await prepareRunAttemptInput();
1852
+ return "reply_nudge";
1853
+ }
1854
+ }
1855
+ await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
1856
+ // The final output is the newest message this stream completed, already
1857
+ // durable with its provider identity and phase. A phase-less settlement
1858
+ // copy is published only when this stream did not complete that text.
1859
+ const finalOutputAlreadyCompleted = latestStreamedAssistantText === finalOutput;
1860
+ // A wait ends the turn with empty output; a human's message still gets
1861
+ // its answer recorded for unread attention and Slack.
1862
+ const reply = await inputWaitReply({
1863
+ inputWaitYielded,
1864
+ turn,
1865
+ latestAssistantMessageText,
1866
+ readLatestDurableTurnMessage: async () =>
1867
+ await latestDurableTurnMessageText(db, {
1868
+ workspaceId: input.workspaceId,
1869
+ sessionId: input.sessionId,
1870
+ turnId: turn.id,
1871
+ }),
1872
+ });
1674
1873
  await historySink.reconcileConversationTruth({ requireDurable: true });
1675
1874
  // Op-stream durability fence: the tool outputs are now durably in the
1676
1875
  // history store (a redispatch would NOT re-execute them), so this
@@ -1683,10 +1882,17 @@ export async function runTurnStreamAttempt(
1683
1882
  if (
1684
1883
  !(await eventing.settle!({
1685
1884
  events: [
1686
- ...(inputWaitYielded
1885
+ ...(inputWaitYielded || finalOutputAlreadyCompleted
1687
1886
  ? []
1688
1887
  : [{ type: "agent.message.completed" as const, payload: { text: finalOutput } }]),
1689
- { type: "turn.completed", payload: { output: finalOutput } },
1888
+ {
1889
+ type: "turn.completed",
1890
+ payload: {
1891
+ output: finalOutput,
1892
+ ...(reply === null ? {} : { reply }),
1893
+ ...(emptyFinalReply ? { emptyFinalReply: true } : {}),
1894
+ },
1895
+ },
1690
1896
  { type: "session.status.changed", payload: { status: "idle" } },
1691
1897
  ],
1692
1898
  turnStatus: "completed",
@@ -1746,7 +1952,11 @@ export async function runTurnStreamAttempt(
1746
1952
  ) {
1747
1953
  return claimedResult({ status: "cancelled" });
1748
1954
  }
1955
+ // Preparation may have spent the last allowance on a completed summary.
1956
+ // Neither the title sidecar nor ordinary inference may dispatch afterward.
1957
+ await revalidateModelCallAdmission();
1749
1958
  if (
1959
+ turn.source !== "compaction" &&
1750
1960
  generateSessionTitleInParallel &&
1751
1961
  runtime.generateSessionTitle &&
1752
1962
  !runtimeCancellationSignal.aborted
@@ -1759,18 +1969,16 @@ export async function runTurnStreamAttempt(
1759
1969
  signal: runtimeCancellationSignal,
1760
1970
  generate: async (signal) =>
1761
1971
  await withSessionTitleProviderRequestContext(() =>
1762
- runtime.generateSessionTitle!(runSettings, sessionTitlePrompt, {
1763
- ...(resolvedModel
1764
- ? {
1765
- client: resolvedModel.client,
1766
- provider: resolvedModel.provider,
1767
- model: resolvedModel.model,
1768
- }
1769
- : {}),
1770
- modelName: turnExecutionPolicy.upstreamModelId,
1771
- ...(serviceTier ? { serviceTier } : {}),
1772
- signal,
1773
- }),
1972
+ runtime.generateSessionTitle!(
1973
+ runSettings,
1974
+ sessionTitlePrompt,
1975
+ sessionTitleGenerationOptions({
1976
+ resolvedModel,
1977
+ modelName: turnExecutionPolicy.upstreamModelId,
1978
+ serviceTier,
1979
+ signal,
1980
+ }),
1981
+ ),
1774
1982
  ),
1775
1983
  onError: (error) => {
1776
1984
  observability.warn("parallel session title generation failed", {
@@ -1784,11 +1992,19 @@ export async function runTurnStreamAttempt(
1784
1992
  }
1785
1993
  try {
1786
1994
  let retriedAfterCompaction = false;
1995
+ finalReplyNudged = await sessionTurnHasFinalReplyNudge(
1996
+ db,
1997
+ input.workspaceId,
1998
+ input.sessionId,
1999
+ activeTurnId,
2000
+ finalReplyNudge(activeTurnId).content[0]!.text,
2001
+ );
1787
2002
  while (true) {
1788
2003
  try {
1789
2004
  const result = await runStreamAttempt({
1790
2005
  requireTerminalModelResponse: retriedAfterCompaction,
1791
2006
  });
2007
+ if (result === "reply_nudge") continue;
1792
2008
  if (retriedAfterCompaction) {
1793
2009
  observability.info("context compaction recovery succeeded after in-activity retry", {
1794
2010
  sessionId: input.sessionId,
@@ -1822,6 +2038,7 @@ export async function runTurnStreamAttempt(
1822
2038
  let compacted = false;
1823
2039
  let compactionHandled = false;
1824
2040
  let compactionFailureMessage: string | null = null;
2041
+ let compactionFailureError: unknown = null;
1825
2042
  let compactionRequestCleared = false;
1826
2043
  try {
1827
2044
  const outcome = await forceContextCompaction(
@@ -1856,6 +2073,7 @@ export async function runTurnStreamAttempt(
1856
2073
  {
1857
2074
  clearRequestedCompaction: recoveryKind === "operator",
1858
2075
  publishLiveEvents: publishCompactionLiveEvents,
2076
+ providerRejection: compactionProviderRejection(compactError),
1859
2077
  },
1860
2078
  );
1861
2079
  compactionRequestCleared = landmark.requestConsumed;
@@ -1863,6 +2081,7 @@ export async function runTurnStreamAttempt(
1863
2081
  await finishParallelSessionTitle();
1864
2082
  const deferredSteer = await settleDeferredSteerAfterCompaction();
1865
2083
  if (deferredSteer) return deferredSteer;
2084
+ compactionFailureError = compactError;
1866
2085
  compactionFailureMessage = String(compactionFailureReasonFromError(compactError));
1867
2086
  observability.warn("context compaction recovery compaction failed", {
1868
2087
  sessionId: input.sessionId,
@@ -1880,13 +2099,9 @@ export async function runTurnStreamAttempt(
1880
2099
  events: [
1881
2100
  {
1882
2101
  type: "turn.failed",
1883
- payload: {
2102
+ payload: compactionFailureTurnEventPayload(compactionFailureError, {
1884
2103
  error: errorMessage,
1885
- code: "context_compaction_failed",
1886
- retryable: false,
1887
- recovery: "user_message",
1888
- compacted: false,
1889
- },
2104
+ }),
1890
2105
  },
1891
2106
  {
1892
2107
  type: "session.status.changed",
@@ -1919,6 +2134,27 @@ export async function runTurnStreamAttempt(
1919
2134
  await finishParallelSessionTitle();
1920
2135
  const deferredSteer = await settleDeferredSteerAfterCompaction();
1921
2136
  if (deferredSteer) return deferredSteer;
2137
+ if (turn.source === "compaction") {
2138
+ const settled = await eventing.settle!({
2139
+ events: [
2140
+ {
2141
+ type: "turn.completed",
2142
+ payload: {
2143
+ maintenance: "context_compaction",
2144
+ result: compacted ? "compacted" : "already_applied",
2145
+ },
2146
+ },
2147
+ { type: "session.status.changed", payload: { status: "idle" } },
2148
+ ],
2149
+ turnStatus: "completed",
2150
+ sessionStatus: "idle",
2151
+ activeTurnId: null,
2152
+ });
2153
+ if (!settled) return claimedResult({ status: "cancelled" });
2154
+ control.turnMetricOutcome = "completed";
2155
+ control.activityStatus = "idle";
2156
+ return claimedResult({ status: "idle" });
2157
+ }
1922
2158
  // Codex parity: compaction remains inside the same logical turn and
1923
2159
  // the same activity. Rebuild the model-visible history from the
1924
2160
  // durable replacement and continue the sampling loop; do not create