@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
@@ -0,0 +1,46 @@
1
+ /** One durable, same-logical-turn handoff, not a retry of model/tool effects. */
2
+ export function finalReplyNudge(turnId: string) {
3
+ return {
4
+ type: "message" as const,
5
+ role: "developer" as const,
6
+ content: [
7
+ {
8
+ type: "input_text" as const,
9
+ text: `[Runtime final-reply handoff for turn ${turnId}]\nYour final user-facing answer was empty. Reply to the user now with the requested deliverable or a concise outcome and its retained artifact link. Goal evidence is a short ledger proof, not the answer, and goal_complete does not deliver a chat reply. Use the completed work and tool results already in history; do not repeat completed tools or restart the task. If this is a late child result after goal completion, integrate material new findings and deliver any still-missing handoff. Do not change the completed goal or invent a result.`,
10
+ },
11
+ ],
12
+ };
13
+ }
14
+
15
+ export function hasFinalReplyNudge(history: readonly unknown[], turnId: string): boolean {
16
+ const text = finalReplyNudge(turnId).content[0]!.text;
17
+ return history.some((item) => {
18
+ const message = item as { role?: unknown; content?: unknown } | null;
19
+ return (
20
+ message?.role === "developer" &&
21
+ Array.isArray(message.content) &&
22
+ message.content.some(
23
+ (part: { type?: unknown; text?: unknown }) =>
24
+ part?.type === "input_text" && part.text === text,
25
+ )
26
+ );
27
+ });
28
+ }
29
+
30
+ export function needsFinalReply(input: {
31
+ output: unknown;
32
+ inputWaitYielded: boolean;
33
+ interrupted: boolean;
34
+ maintenance: boolean;
35
+ toolsExecuted: boolean;
36
+ completedGoal: boolean;
37
+ }): boolean {
38
+ return (
39
+ typeof input.output === "string" &&
40
+ input.output.trim().length === 0 &&
41
+ !input.inputWaitYielded &&
42
+ !input.interrupted &&
43
+ !input.maintenance &&
44
+ (input.toolsExecuted || input.completedGoal)
45
+ );
46
+ }
@@ -0,0 +1,120 @@
1
+ import type { Observability } from "@opengeni/observability";
2
+ import type { TurnHeartbeatDetails } from "../../op-journal";
3
+ import { armTurnQuiescenceWatchdog } from "./quiescence";
4
+
5
+ export const TURN_FINALIZATION_STAGES = [
6
+ "tool_writers",
7
+ "credential_renewals",
8
+ "credential_cleanup",
9
+ "quiescence_receipt",
10
+ "event_flush",
11
+ "provider_leases",
12
+ "workspace_capture",
13
+ "tool_close",
14
+ "sandbox_provisioning",
15
+ "sandbox_rotation",
16
+ "workspace_snapshot",
17
+ "sandbox_release",
18
+ ] as const;
19
+ export type TurnFinalizationStage = (typeof TURN_FINALIZATION_STAGES)[number];
20
+
21
+ const INFLIGHT = {
22
+ name: "opengeni_turn_finalization_inflight",
23
+ help: "Physical turn finalizers currently waiting in each cleanup stage.",
24
+ };
25
+ const SLOW_STAGES = {
26
+ name: "opengeni_turn_finalization_slow_total",
27
+ help: "Turn cleanup stages observed to exceed thirty seconds.",
28
+ };
29
+ const initialized = new WeakSet<Observability>();
30
+ function diagnose(operation: () => void): void {
31
+ try {
32
+ operation();
33
+ } catch {
34
+ // Diagnostics cannot own physical cleanup or containment.
35
+ }
36
+ }
37
+
38
+ /** Starts only after agent execution ends. A cleanup-stage deadline never caps
39
+ * agent work and never detaches a physical writer to manufacture quiescence. */
40
+ export function startTurnFinalizationMonitor(input: {
41
+ observability: Observability;
42
+ details: TurnHeartbeatDetails;
43
+ heartbeat: (details: TurnHeartbeatDetails) => void;
44
+ requestWorkerDrain: () => void;
45
+ timeoutMs?: number;
46
+ slowAfterMs?: number;
47
+ }) {
48
+ const { observability, details } = input;
49
+ if (!initialized.has(observability)) {
50
+ for (const stage of TURN_FINALIZATION_STAGES) {
51
+ diagnose(() => observability.incrementGauge({ ...INFLIGHT, labels: { stage }, amount: 0 }));
52
+ diagnose(() =>
53
+ observability.incrementCounter({ ...SLOW_STAGES, labels: { stage }, amount: 0 }),
54
+ );
55
+ }
56
+ initialized.add(observability);
57
+ }
58
+ let stage: TurnFinalizationStage | null = null;
59
+ let disarm = () => {};
60
+ let stopped = false;
61
+ let slowTimer: ReturnType<typeof setTimeout> | undefined;
62
+ const leave = () => {
63
+ disarm();
64
+ clearTimeout(slowTimer);
65
+ if (stage) {
66
+ const previous = stage;
67
+ diagnose(() =>
68
+ observability.incrementGauge({ ...INFLIGHT, labels: { stage: previous }, amount: -1 }),
69
+ );
70
+ }
71
+ stage = null;
72
+ };
73
+ return {
74
+ enter(next: TurnFinalizationStage) {
75
+ if (stopped || stage === next) return;
76
+ leave();
77
+ stage = next;
78
+ details.phase = "finalizing";
79
+ details.finalizationStage = next;
80
+ details.finalizationStageStartedAt = new Date().toISOString();
81
+ diagnose(() => observability.incrementGauge({ ...INFLIGHT, labels: { stage: next } }));
82
+ disarm = armTurnQuiescenceWatchdog({
83
+ enabled: true,
84
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
85
+ onTimeout: () => {
86
+ try {
87
+ observability.error("turn finalization stalled; containing worker", {
88
+ surface: "turn_finalization",
89
+ outcome: "containment",
90
+ reason: next,
91
+ });
92
+ } catch {
93
+ // Diagnostic failure must never disable physical containment.
94
+ }
95
+ },
96
+ terminateWorker: input.requestWorkerDrain,
97
+ });
98
+ // Record the precursor while the process remains scrapeable.
99
+ slowTimer = setTimeout(
100
+ () =>
101
+ diagnose(() => {
102
+ observability.incrementCounter({ ...SLOW_STAGES, labels: { stage: next } });
103
+ observability.warn("turn finalization is taking longer than expected", {
104
+ surface: "turn_finalization",
105
+ outcome: "slow",
106
+ reason: next,
107
+ });
108
+ }),
109
+ input.slowAfterMs ?? 30_000,
110
+ );
111
+ slowTimer.unref?.();
112
+ diagnose(() => input.heartbeat(details));
113
+ },
114
+ stop() {
115
+ if (stopped) return;
116
+ stopped = true;
117
+ leave();
118
+ },
119
+ };
120
+ }
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  commitSessionAttemptQuiescence,
3
3
  listPendingSessionTurns,
4
- recordCodexAccountUsageWithWakeTargets,
4
+ recordCodexAccountUsageForFinalization,
5
+ recordClaudeAccountUsage,
6
+ releaseClaudeCredentialLease,
5
7
  releaseCodexCredentialLease,
6
8
  releaseXaiCredentialLease,
7
9
  updateXaiQuotaMetadata,
@@ -11,9 +13,10 @@ import { appendAndPublishTurnEventsFenced, publishDurableSessionEvents } from "@
11
13
  import { sandboxLeaseTelemetryKey } from "@opengeni/observability";
12
14
  import { clearRunCredentialsForAttempt } from "@opengeni/runtime";
13
15
  import { fetchXaiSubscriptionQuota } from "@opengeni/xai-subscription";
14
- import type { Settings } from "@opengeni/config";
16
+ import { environmentsEncryptionKeyBytes, type Settings } from "@opengeni/config";
15
17
  import { signalCodexCapacityWakeTargets } from "../codex-capacity";
16
- import type { currentActivityContext } from "../streaming";
18
+ import { startActivityHeartbeat, type currentActivityContext } from "../streaming";
19
+ import { startTurnFinalizationMonitor } from "./finalization-monitor";
17
20
  import type { CodemodeTokenRenewalController } from "../codemode-token-renewal";
18
21
  import type { RunCredentialRenewalController } from "../run-credential-renewal";
19
22
  import type {
@@ -32,6 +35,7 @@ import {
32
35
  makeMachineOpObserver,
33
36
  recordSessionEventAppendPhase,
34
37
  turnLifecycleMetricsFor,
38
+ runtimeMetricsHooksForObservability,
35
39
  } from "../../observability-metrics";
36
40
  import {
37
41
  maybePersistWarmWorkspaceSnapshot,
@@ -43,7 +47,6 @@ import { safeErrorDiagnostic, safeErrorForTelemetry } from "./errors";
43
47
  import {
44
48
  assertPhysicalToolQuiescenceForCancellation,
45
49
  assertSessionAttemptQuiescenceRecoveryDurable,
46
- armTurnQuiescenceWatchdog,
47
50
  clearAttemptCredentialsWithSettledFence,
48
51
  drainAttemptOwnedSandboxWriters,
49
52
  persistOrSignalSessionAttemptQuiescence,
@@ -72,6 +75,8 @@ export type TurnFinalizationDeps = {
72
75
  wakeSessionWorkflow: ActivityServices["wakeSessionWorkflow"];
73
76
  signalSessionAttemptQuiesced: ActivityServices["signalSessionAttemptQuiesced"];
74
77
  signalCodexCapacityWorkflow: ActivityServices["signalCodexCapacityWorkflow"];
78
+ requestWorkerDrain: ActivityServices["requestWorkerDrain"];
79
+ turnFinalizationTimeoutMs: ActivityServices["turnFinalizationTimeoutMs"];
75
80
  cancellationSignal: AbortSignal | undefined;
76
81
  sandboxResumeController: AbortController;
77
82
  activityContext: ReturnType<typeof currentActivityContext>;
@@ -99,6 +104,45 @@ export type TurnFinalizationDeps = {
99
104
  };
100
105
 
101
106
  export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<void> {
107
+ const details = deps.eventing.heartbeatDetails ?? {
108
+ sessionId: deps.input.sessionId,
109
+ turnId: deps.attempt.turnId,
110
+ opAcks: {},
111
+ };
112
+ deps.eventing.heartbeatDetails = details;
113
+ // Keep one heartbeat owner through all physical cleanup, with the same op
114
+ // acknowledgements used during execution. It must no longer say "running".
115
+ if (deps.eventing.heartbeatTimer) clearInterval(deps.eventing.heartbeatTimer);
116
+ deps.eventing.heartbeatTimer = startActivityHeartbeat(deps.activityContext, details);
117
+ const monitor = startTurnFinalizationMonitor({
118
+ observability: deps.observability,
119
+ ...(deps.turnFinalizationTimeoutMs === undefined
120
+ ? {}
121
+ : { timeoutMs: deps.turnFinalizationTimeoutMs }),
122
+ details,
123
+ heartbeat: (value) => {
124
+ try {
125
+ deps.activityContext?.heartbeat({ ...value, at: new Date().toISOString() });
126
+ } catch {
127
+ // A closed Temporal transport is not proof of physical quiescence.
128
+ }
129
+ },
130
+ requestWorkerDrain: deps.requestWorkerDrain,
131
+ });
132
+ try {
133
+ monitor.enter("tool_writers");
134
+ await finalizeTurnAttemptSteps(deps, monitor);
135
+ } finally {
136
+ monitor.stop();
137
+ if (deps.eventing.heartbeatTimer) clearInterval(deps.eventing.heartbeatTimer);
138
+ deps.eventing.heartbeatTimer = undefined;
139
+ }
140
+ }
141
+
142
+ async function finalizeTurnAttemptSteps(
143
+ deps: TurnFinalizationDeps,
144
+ monitor: ReturnType<typeof startTurnFinalizationMonitor>,
145
+ ): Promise<void> {
102
146
  const {
103
147
  input,
104
148
  settings,
@@ -142,17 +186,6 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
142
186
  let finalizationError: unknown;
143
187
  let physicalToolQuiescenceConfirmed = !control.acknowledgeQuiescence;
144
188
  let quiescenceReceiptOrProofDurable = !control.acknowledgeQuiescence;
145
- const disarmQuiescenceWatchdog = armTurnQuiescenceWatchdog({
146
- enabled: control.acknowledgeQuiescence,
147
- onTimeout: () => {
148
- observability.error("turn quiescence drain exceeded hard containment deadline", {
149
- "opengeni.session_id": input.sessionId,
150
- "opengeni.turn_id": attempt.turnId ?? "",
151
- "opengeni.attempt_id": input.attemptId,
152
- });
153
- },
154
- terminateWorker: () => process.exit(1),
155
- });
156
189
  const finalizerSignal = turnFinalizerCancellationSignal(
157
190
  cancellationSignal,
158
191
  control.activityStatus,
@@ -187,6 +220,8 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
187
220
  renewals.codemodeTokenRenewal as CodemodeTokenRenewalController | null;
188
221
  renewals.codemodeTokenRenewal = null;
189
222
  renewals.runCredentialRenewalClosed = true;
223
+ renewals.runMcpCredentials?.close();
224
+ delete renewals.runMcpCredentials;
190
225
  const runRenewalToStop = renewals.runCredentialRenewal as RunCredentialRenewalController | null;
191
226
  renewals.runCredentialRenewal = null;
192
227
 
@@ -200,6 +235,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
200
235
  // and durably settled before workspace capture. Only receipt
201
236
  // publication remains conditional on acknowledgeQuiescence.
202
237
  toolCancellationFence,
238
+ onStage: monitor.enter,
203
239
  cancellationReason: cancellationSignal?.reason ?? new Error("TURN_ATTEMPT_FINALIZED"),
204
240
  gitCredentialRenewals: gitRenewalsToStop,
205
241
  codemodeTokenRenewal: codemodeRenewalToStop,
@@ -211,6 +247,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
211
247
  // attempt's generation before publishing physical quiescence. A failure
212
248
  // deliberately keeps the receipt closed.
213
249
  if (credentialSessionToClear) {
250
+ monitor.enter("credential_cleanup");
214
251
  const clearAttemptCredentials = async (): Promise<void> =>
215
252
  await clearRunCredentialsForAttempt(credentialSessionToClear, {
216
253
  sessionId: input.sessionId,
@@ -258,6 +295,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
258
295
  // and before lease, cache, recording, or provider cleanup. Its
259
296
  // transaction also enqueues the exact workflow wake that will admit
260
297
  // the replacement; Temporal activity terminalization does neither.
298
+ monitor.enter("quiescence_receipt");
261
299
  const proof: SessionAttemptQuiescenceProof = {
262
300
  accountId: input.accountId,
263
301
  workspaceId: input.workspaceId,
@@ -304,6 +342,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
304
342
  signalProof: signalSessionAttemptQuiesced,
305
343
  heartbeat: (deliveryAttempt, retryMs) => {
306
344
  activityContext?.heartbeat({
345
+ ...eventing.heartbeatDetails,
307
346
  phase: "quiescence-proof-delivery",
308
347
  sessionId: input.sessionId,
309
348
  attemptId: input.attemptId,
@@ -336,7 +375,6 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
336
375
  },
337
376
  });
338
377
  quiescenceReceiptOrProofDurable = true;
339
- disarmQuiescenceWatchdog();
340
378
  if (recoveryMode === "signal") {
341
379
  observability.info("agent turn quiescence proof handed to workflow recovery", {
342
380
  "opengeni.session_id": input.sessionId,
@@ -351,6 +389,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
351
389
  // the turn. Sync observer → buffer → single awaited append here (no unawaited
352
390
  // DB write inside the activity). Scoped to this turn; skipped if no turnId
353
391
  // (the op ran under a turn, so on the normal path turnId is set).
392
+ monitor.enter("event_flush");
354
393
  const machineOpEvents = machineOpObserver.drainEvents();
355
394
  if (machineOpEvents.length > 0 && attempt.turnId && attempt.executionGeneration > 0) {
356
395
  await waitForTurnFinalizerStep(
@@ -377,17 +416,65 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
377
416
  // Multi-account P4: flush the serving account's free per-turn caches ONCE,
378
417
  // best-effort (same discipline as today's usage write). Both writers skip
379
418
  // version/updatedAt, so neither can race the token-refresh CAS.
419
+ monitor.enter("provider_leases");
420
+ const claudeEncryptionKey = environmentsEncryptionKeyBytes(settings);
421
+ if (claudeEncryptionKey && providerTurn.claudeAuthoritySnapshot && leases.claude.subjectId) {
422
+ for (const snapshot of providerTurn.latestClaudeUsage.values()) {
423
+ if (
424
+ (!snapshot.observation && !snapshot.refresh) ||
425
+ snapshot.expectedConnectionId !== providerTurn.effectiveClaudeCredentialId ||
426
+ snapshot.expectedCredentialVersion !== providerTurn.effectiveClaudeCredentialVersion
427
+ )
428
+ continue;
429
+ await waitForTurnFinalizerStep(
430
+ recordClaudeAccountUsage(
431
+ db,
432
+ {
433
+ accountId: input.accountId,
434
+ workspaceId: input.workspaceId,
435
+ subjectId: leases.claude.subjectId,
436
+ credentialId: snapshot.expectedConnectionId,
437
+ authoritySnapshot: providerTurn.claudeAuthoritySnapshot,
438
+ },
439
+ {
440
+ encryptionKey: claudeEncryptionKey,
441
+ token: snapshot.token,
442
+ expectedCredentialVersion: snapshot.expectedCredentialVersion,
443
+ ...(snapshot.observation ? { observation: snapshot.observation } : {}),
444
+ ...(snapshot.refresh ? { refresh: snapshot.refresh } : {}),
445
+ },
446
+ ).catch(() => null),
447
+ finalizerSignal,
448
+ );
449
+ }
450
+ }
380
451
  if (providerTurn.effectiveCodexCredentialId) {
381
452
  // Part A: the latest scraped usage-header snapshot → the P2 usage cache. A
382
- // full both-windows snapshot (parseCodexUsageHeaders gates on both), so this
383
- // is byte-identical to the /wham/usage write — no partial-window clobber.
384
- if (providerTurn.latestCodexUsage) {
453
+ // full duration-identified snapshot (parseCodexUsageHeaders gates on both),
454
+ // so untyped response headers cannot mislabel weekly-only quota.
455
+ if (
456
+ providerTurn.latestCodexUsage &&
457
+ attempt.turnId &&
458
+ leases.codex.held &&
459
+ leases.codex.holderId &&
460
+ leases.codex.generation !== null &&
461
+ providerTurn.effectiveCodexCredentialVersion !== null
462
+ ) {
385
463
  const usageMutation = await waitForTurnFinalizerStep(
386
- recordCodexAccountUsageWithWakeTargets(
464
+ recordCodexAccountUsageForFinalization(
387
465
  db,
388
466
  input.workspaceId,
389
467
  providerTurn.effectiveCodexCredentialId,
390
468
  providerTurn.latestCodexUsage,
469
+ {
470
+ turnId: attempt.turnId,
471
+ sessionId: input.sessionId,
472
+ attemptId: input.attemptId,
473
+ executionGeneration: attempt.executionGeneration,
474
+ holderId: leases.codex.holderId,
475
+ generation: leases.codex.generation,
476
+ credentialVersion: providerTurn.effectiveCodexCredentialVersion,
477
+ },
391
478
  ).catch(() => null),
392
479
  finalizerSignal,
393
480
  );
@@ -450,25 +537,30 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
450
537
  );
451
538
  }
452
539
  }
453
- leases.xai.stopHeartbeat();
454
- if (
455
- leases.xai.held &&
456
- attempt.turnId &&
457
- leases.xai.subjectId &&
458
- leases.xai.holderId &&
459
- leases.xai.generation !== null
460
- ) {
461
- await waitForTurnFinalizerStep(
462
- releaseXaiCredentialLease(db, {
463
- workspaceId: input.workspaceId,
464
- subjectId: leases.xai.subjectId,
465
- turnId: attempt.turnId,
466
- holderId: leases.xai.holderId,
467
- generation: leases.xai.generation,
468
- }).catch(() => undefined),
469
- finalizerSignal,
470
- );
471
- leases.xai.held = false;
540
+ for (const [lease, release] of [
541
+ [leases.xai, releaseXaiCredentialLease],
542
+ [leases.claude, releaseClaudeCredentialLease],
543
+ ] as const) {
544
+ lease.stopHeartbeat();
545
+ if (
546
+ lease.held &&
547
+ attempt.turnId &&
548
+ lease.subjectId &&
549
+ lease.holderId &&
550
+ lease.generation !== null
551
+ ) {
552
+ await waitForTurnFinalizerStep(
553
+ release(db, {
554
+ workspaceId: input.workspaceId,
555
+ subjectId: lease.subjectId,
556
+ turnId: attempt.turnId,
557
+ holderId: lease.holderId,
558
+ generation: lease.generation,
559
+ }).catch(() => undefined),
560
+ finalizerSignal,
561
+ );
562
+ lease.held = false;
563
+ }
472
564
  }
473
565
  // Workbench v2 turn-end workspace capture — runs FIRST in
474
566
  // the turn-end finally, while the box is MAXIMALLY ALIVE. The agent's last
@@ -502,6 +594,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
502
594
  ) {
503
595
  // Review reads yield to queued work. Recovery snapshots have their own
504
596
  // physical-settlement requirement and are drained later in finalization.
597
+ monitor.enter("workspace_capture");
505
598
  sandboxState.turnEndCaptureInProgress = true;
506
599
  const captureSandbox = sandboxState.resolvedSandbox;
507
600
  const captureSession = sandboxState.setupBoxSession as ChannelASession;
@@ -549,6 +642,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
549
642
  },
550
643
  });
551
644
  }
645
+ monitor.enter("tool_close");
552
646
  eventing.toolPreparationClosing = true;
553
647
  if (eventing.toolPreparationReady) {
554
648
  await waitForTurnFinalizerStep(
@@ -569,9 +663,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
569
663
  finalizerSignal,
570
664
  );
571
665
  }
572
- if (eventing.heartbeatTimer) {
573
- clearInterval(eventing.heartbeatTimer);
574
- }
666
+ monitor.enter("sandbox_provisioning");
575
667
  if (sandboxState.turnSandboxProvisioner?.hasStarted()) {
576
668
  await waitForTurnFinalizerStep(
577
669
  sandboxState.turnSandboxProvisioner.waitForSettled(30_000),
@@ -599,12 +691,14 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
599
691
  if (sandboxState.leaseHeartbeatTimer) {
600
692
  stopLeaseHeartbeat();
601
693
  }
694
+ monitor.enter("sandbox_rotation");
602
695
  if (sandboxState.rotationPreemptionInFlight) {
603
696
  await sandboxState.rotationPreemptionInFlight.catch(() => undefined);
604
697
  }
605
698
  // No heartbeat can register another capture after this point. Join the
606
699
  // actual provider continuation before either starting a turn-end capture
607
700
  // or entering the proof-bearing holder release.
701
+ monitor.enter("workspace_snapshot");
608
702
  await drainInFlightWarmSnapshot();
609
703
  if (sandboxState.resolvedSandbox) {
610
704
  // TURN-END mid-session snapshot (sandbox-file-persistence): fold the
@@ -622,7 +716,13 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
622
716
  settledTurnId
623
717
  ) {
624
718
  const snapshot = maybePersistWarmWorkspaceSnapshot(
625
- { db, settings, objectStorage },
719
+ {
720
+ db,
721
+ settings,
722
+ objectStorage,
723
+ sandboxMetrics: runtimeMetricsHooksForObservability(observability),
724
+ observability,
725
+ },
626
726
  {
627
727
  accountId: input.accountId,
628
728
  workspaceId: input.workspaceId,
@@ -667,10 +767,13 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
667
767
  // Temporal cancellation: the eager listener may already have dropped
668
768
  // the holder, and this idempotent proof-bearing pass still must run.
669
769
  stopLeaseHeartbeat();
770
+ monitor.enter("sandbox_rotation");
670
771
  if (sandboxState.rotationPreemptionInFlight) {
671
772
  await sandboxState.rotationPreemptionInFlight.catch(() => undefined);
672
773
  }
774
+ monitor.enter("workspace_snapshot");
673
775
  await drainInFlightWarmSnapshot();
776
+ monitor.enter("sandbox_release");
674
777
  const sandboxReleaseTargets = new Set(sandboxState.lateSandboxesAwaitingWriterDrain);
675
778
  sandboxState.lateSandboxesAwaitingWriterDrain.clear();
676
779
  if (sandboxState.resolvedSandbox) {
@@ -229,6 +229,8 @@ export type ApplyTurnGitHubRepositoryBindingsInput = {
229
229
  workspaceId: string;
230
230
  sessionId: string;
231
231
  activeSandboxBackend: Settings["sandboxBackend"] | undefined;
232
+ /** Product-owned credentials take precedence over inferred platform bindings. */
233
+ hasCredentialProvider?: boolean;
232
234
  claimedTurnResources: readonly ResourceRef[];
233
235
  claimedRuntimeResources: readonly ResourceRef[];
234
236
  /** Publishes one attempt-fenced session event batch. */
@@ -258,7 +260,7 @@ export async function applyTurnGitHubRepositoryBindings(
258
260
  turnResources: [...input.claimedTurnResources],
259
261
  runtimeResources: [...input.claimedRuntimeResources],
260
262
  };
261
- if (input.activeSandboxBackend === "selfhosted") {
263
+ if (input.activeSandboxBackend === "selfhosted" || input.hasCredentialProvider) {
262
264
  return passthrough;
263
265
  }
264
266
  let resolved: TurnGitHubRepositoryBindings;