@opengeni/worker-bundle 1.0.0-canary.0 → 1.0.1-canary.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 (26) hide show
  1. package/dist/activities/agent-turn/errors.d.ts +6 -0
  2. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  3. package/dist/activities/agent-turn/finalization.d.ts +1 -1
  4. package/dist/activities/agent-turn/quiescence.d.ts +1 -0
  5. package/dist/activities/agent-turn/turn-context.d.ts +2 -0
  6. package/dist/{activities-control-HMFGSKC5.js → activities-control-O5U6OLT2.js} +4 -19
  7. package/dist/{activities-control-HMFGSKC5.js.map → activities-control-O5U6OLT2.js.map} +1 -1
  8. package/dist/{activities-turn-AP67JADS.js → activities-turn-OAPNO2IH.js} +167 -116
  9. package/dist/activities-turn-OAPNO2IH.js.map +1 -0
  10. package/dist/{chunk-CYITU5UH.js → chunk-GENYJQ5B.js} +18 -1
  11. package/dist/{chunk-CYITU5UH.js.map → chunk-GENYJQ5B.js.map} +1 -1
  12. package/dist/index.js +1 -1
  13. package/package.json +19 -19
  14. package/src/activities/agent-turn/claim.ts +1 -0
  15. package/src/activities/agent-turn/errors.ts +21 -0
  16. package/src/activities/agent-turn/finalization-monitor.ts +121 -0
  17. package/src/activities/agent-turn/finalization.ts +52 -17
  18. package/src/activities/agent-turn/quiescence.ts +3 -0
  19. package/src/activities/agent-turn/run.ts +1 -39
  20. package/src/activities/agent-turn/skill-selection.ts +1 -0
  21. package/src/activities/agent-turn/tool-environment.ts +10 -2
  22. package/src/activities/agent-turn/turn-context.ts +3 -0
  23. package/src/activities/workflow-wake.ts +2 -17
  24. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  25. package/dist/activities-turn-AP67JADS.js.map +0 -1
  26. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
package/dist/index.js CHANGED
@@ -784,7 +784,7 @@ function temporalWorkflowExecutionNotFound(error) {
784
784
  return isGrpcServiceError(error) && error.code === 5;
785
785
  }
786
786
  async function createDefaultWorkerActivities(role, dependencies = {}) {
787
- return role === "control" ? (await import("./activities-control-HMFGSKC5.js")).createControlActivities(dependencies) : (await import("./activities-turn-AP67JADS.js")).createTurnActivities(dependencies);
787
+ return role === "control" ? (await import("./activities-control-O5U6OLT2.js")).createControlActivities(dependencies) : (await import("./activities-turn-OAPNO2IH.js")).createTurnActivities(dependencies);
788
788
  }
789
789
  function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
790
790
  const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/worker-bundle",
3
- "version": "1.0.0-canary.0",
3
+ "version": "1.0.1-canary.0",
4
4
  "description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -54,24 +54,24 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@llamaindex/liteparse": "2.14.2",
57
- "@opengeni/agent-proto": "^0.6.0-canary.3",
58
- "@opengeni/capabilities": "^0.3.3-canary.15",
59
- "@opengeni/codemode": "^0.5.8-canary.0",
60
- "@opengeni/codex": "^0.2.23-canary.0",
61
- "@opengeni/config": "^1.2.1-canary.0",
62
- "@opengeni/contracts": "^4.0.0-canary.0",
63
- "@opengeni/core": "^3.0.0-canary.0",
64
- "@opengeni/db": "^5.0.0-canary.0",
65
- "@opengeni/documents": "^0.8.29-canary.0",
66
- "@opengeni/events": "^0.4.27-canary.0",
67
- "@opengeni/github": "^0.7.12-canary.0",
68
- "@opengeni/network": "^0.3.1-canary.15",
69
- "@opengeni/observability": "^0.8.28-canary.0",
70
- "@opengeni/runtime": "^3.0.0-canary.0",
71
- "@opengeni/sdk": "^6.0.0-canary.0",
72
- "@opengeni/storage": "^0.2.129-canary.0",
73
- "@opengeni/tool-gateway": "^0.1.9-canary.0",
74
- "@opengeni/xai-subscription": "^0.1.4-canary.15",
57
+ "@opengeni/agent-proto": "^0.6.0-canary.5",
58
+ "@opengeni/capabilities": "^0.3.4-canary.0",
59
+ "@opengeni/codemode": "^0.5.9-canary.0",
60
+ "@opengeni/codex": "^0.2.23-canary.2",
61
+ "@opengeni/config": "^1.2.2-canary.0",
62
+ "@opengeni/contracts": "^4.1.0-canary.0",
63
+ "@opengeni/core": "^3.0.1-canary.0",
64
+ "@opengeni/db": "^5.0.1-canary.0",
65
+ "@opengeni/documents": "^0.8.30-canary.0",
66
+ "@opengeni/events": "^0.4.28-canary.0",
67
+ "@opengeni/github": "^0.7.13-canary.0",
68
+ "@opengeni/network": "^0.3.1-canary.17",
69
+ "@opengeni/observability": "^0.8.29-canary.0",
70
+ "@opengeni/runtime": "^3.0.1-canary.0",
71
+ "@opengeni/sdk": "^6.1.0-canary.0",
72
+ "@opengeni/storage": "^0.2.130-canary.0",
73
+ "@opengeni/tool-gateway": "^0.1.10-canary.0",
74
+ "@opengeni/xai-subscription": "^0.1.4-canary.17",
75
75
  "@temporalio/activity": "^1.17.0",
76
76
  "@temporalio/client": "^1.17.0",
77
77
  "@temporalio/worker": "^1.17.0",
@@ -387,6 +387,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
387
387
  turnId: attempt.turnId,
388
388
  opAcks: {},
389
389
  };
390
+ eventing.heartbeatDetails = heartbeatDetails;
390
391
  const opJournal = makeTurnOpJournal(activityContext, heartbeatDetails);
391
392
  eventing.heartbeatTimer = startActivityHeartbeat(activityContext, heartbeatDetails);
392
393
  let producerSeq = 0;
@@ -15,6 +15,9 @@ import {
15
15
  isMcpTransportConnectivityError,
16
16
  isModalTaskExecStartDnsResolutionError,
17
17
  RoutingWorkspaceRootChangedError,
18
+ SandboxMaterializationVerificationError,
19
+ materializationVerificationDiagnostic,
20
+ type MaterializationVerificationDiagnostic,
18
21
  SelfhostedWorkspaceRootChangedError,
19
22
  UNKNOWN_MODEL_FINISH_REASON_CODE,
20
23
  } from "@opengeni/runtime";
@@ -851,6 +854,15 @@ function findPostgresDriverError(error: unknown): Record<string, unknown> | null
851
854
  export function agentRunFailurePayload(
852
855
  error: unknown,
853
856
  options: { isCodexTurn?: boolean } = {},
857
+ ): ReturnType<typeof baseAgentRunFailurePayload> {
858
+ const failure = baseAgentRunFailurePayload(error, options);
859
+ const diagnostic = materializationVerificationDiagnostic(error);
860
+ return diagnostic ? { ...failure, materializationDiagnostic: diagnostic } : failure;
861
+ }
862
+
863
+ function baseAgentRunFailurePayload(
864
+ error: unknown,
865
+ options: { isCodexTurn?: boolean } = {},
854
866
  ): {
855
867
  error: string;
856
868
  code?: string;
@@ -870,7 +882,16 @@ export function agentRunFailurePayload(
870
882
  database?: Record<string, string>;
871
883
  historyPersistenceStage?: MandatoryHistoryPersistenceStage;
872
884
  mcpTransportDiagnostic?: McpTransportRequestFailureDiagnostic;
885
+ materializationDiagnostic?: MaterializationVerificationDiagnostic;
873
886
  } {
887
+ if (error instanceof SandboxMaterializationVerificationError) {
888
+ return {
889
+ error: error.message,
890
+ code: error.code,
891
+ retryable: false,
892
+ materializationDiagnostic: error.diagnostic,
893
+ };
894
+ }
874
895
  if (error instanceof RetainedAttachmentTransportLimitError) {
875
896
  return { error: error.message, code: "retained_attachment_transport_limit", retryable: false };
876
897
  }
@@ -0,0 +1,121 @@
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
+ terminateWorker: () => 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.terminateWorker,
97
+ });
98
+ // Record the precursor while the process remains scrapeable. A counter
99
+ // increment immediately before process.exit can never reach Prometheus.
100
+ slowTimer = setTimeout(
101
+ () =>
102
+ diagnose(() => {
103
+ observability.incrementCounter({ ...SLOW_STAGES, labels: { stage: next } });
104
+ observability.warn("turn finalization is taking longer than expected", {
105
+ surface: "turn_finalization",
106
+ outcome: "slow",
107
+ reason: next,
108
+ });
109
+ }),
110
+ input.slowAfterMs ?? 30_000,
111
+ );
112
+ slowTimer.unref?.();
113
+ diagnose(() => input.heartbeat(details));
114
+ },
115
+ stop() {
116
+ if (stopped) return;
117
+ stopped = true;
118
+ leave();
119
+ },
120
+ };
121
+ }
@@ -13,7 +13,8 @@ import { clearRunCredentialsForAttempt } from "@opengeni/runtime";
13
13
  import { fetchXaiSubscriptionQuota } from "@opengeni/xai-subscription";
14
14
  import type { Settings } from "@opengeni/config";
15
15
  import { signalCodexCapacityWakeTargets } from "../codex-capacity";
16
- import type { currentActivityContext } from "../streaming";
16
+ import { startActivityHeartbeat, type currentActivityContext } from "../streaming";
17
+ import { startTurnFinalizationMonitor } from "./finalization-monitor";
17
18
  import type { CodemodeTokenRenewalController } from "../codemode-token-renewal";
18
19
  import type { RunCredentialRenewalController } from "../run-credential-renewal";
19
20
  import type {
@@ -43,7 +44,6 @@ import { safeErrorDiagnostic, safeErrorForTelemetry } from "./errors";
43
44
  import {
44
45
  assertPhysicalToolQuiescenceForCancellation,
45
46
  assertSessionAttemptQuiescenceRecoveryDurable,
46
- armTurnQuiescenceWatchdog,
47
47
  clearAttemptCredentialsWithSettledFence,
48
48
  drainAttemptOwnedSandboxWriters,
49
49
  persistOrSignalSessionAttemptQuiescence,
@@ -99,6 +99,42 @@ export type TurnFinalizationDeps = {
99
99
  };
100
100
 
101
101
  export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<void> {
102
+ const details = deps.eventing.heartbeatDetails ?? {
103
+ sessionId: deps.input.sessionId,
104
+ turnId: deps.attempt.turnId,
105
+ opAcks: {},
106
+ };
107
+ deps.eventing.heartbeatDetails = details;
108
+ // Keep one heartbeat owner through all physical cleanup, with the same op
109
+ // acknowledgements used during execution. It must no longer say "running".
110
+ if (deps.eventing.heartbeatTimer) clearInterval(deps.eventing.heartbeatTimer);
111
+ deps.eventing.heartbeatTimer = startActivityHeartbeat(deps.activityContext, details);
112
+ const monitor = startTurnFinalizationMonitor({
113
+ observability: deps.observability,
114
+ details,
115
+ heartbeat: (value) => {
116
+ try {
117
+ deps.activityContext?.heartbeat({ ...value, at: new Date().toISOString() });
118
+ } catch {
119
+ // A closed Temporal transport is not proof of physical quiescence.
120
+ }
121
+ },
122
+ terminateWorker: () => process.exit(1),
123
+ });
124
+ try {
125
+ monitor.enter("tool_writers");
126
+ await finalizeTurnAttemptSteps(deps, monitor);
127
+ } finally {
128
+ monitor.stop();
129
+ if (deps.eventing.heartbeatTimer) clearInterval(deps.eventing.heartbeatTimer);
130
+ deps.eventing.heartbeatTimer = undefined;
131
+ }
132
+ }
133
+
134
+ async function finalizeTurnAttemptSteps(
135
+ deps: TurnFinalizationDeps,
136
+ monitor: ReturnType<typeof startTurnFinalizationMonitor>,
137
+ ): Promise<void> {
102
138
  const {
103
139
  input,
104
140
  settings,
@@ -142,17 +178,6 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
142
178
  let finalizationError: unknown;
143
179
  let physicalToolQuiescenceConfirmed = !control.acknowledgeQuiescence;
144
180
  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
181
  const finalizerSignal = turnFinalizerCancellationSignal(
157
182
  cancellationSignal,
158
183
  control.activityStatus,
@@ -200,6 +225,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
200
225
  // and durably settled before workspace capture. Only receipt
201
226
  // publication remains conditional on acknowledgeQuiescence.
202
227
  toolCancellationFence,
228
+ onStage: monitor.enter,
203
229
  cancellationReason: cancellationSignal?.reason ?? new Error("TURN_ATTEMPT_FINALIZED"),
204
230
  gitCredentialRenewals: gitRenewalsToStop,
205
231
  codemodeTokenRenewal: codemodeRenewalToStop,
@@ -211,6 +237,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
211
237
  // attempt's generation before publishing physical quiescence. A failure
212
238
  // deliberately keeps the receipt closed.
213
239
  if (credentialSessionToClear) {
240
+ monitor.enter("credential_cleanup");
214
241
  const clearAttemptCredentials = async (): Promise<void> =>
215
242
  await clearRunCredentialsForAttempt(credentialSessionToClear, {
216
243
  sessionId: input.sessionId,
@@ -258,6 +285,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
258
285
  // and before lease, cache, recording, or provider cleanup. Its
259
286
  // transaction also enqueues the exact workflow wake that will admit
260
287
  // the replacement; Temporal activity terminalization does neither.
288
+ monitor.enter("quiescence_receipt");
261
289
  const proof: SessionAttemptQuiescenceProof = {
262
290
  accountId: input.accountId,
263
291
  workspaceId: input.workspaceId,
@@ -304,6 +332,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
304
332
  signalProof: signalSessionAttemptQuiesced,
305
333
  heartbeat: (deliveryAttempt, retryMs) => {
306
334
  activityContext?.heartbeat({
335
+ ...eventing.heartbeatDetails,
307
336
  phase: "quiescence-proof-delivery",
308
337
  sessionId: input.sessionId,
309
338
  attemptId: input.attemptId,
@@ -336,7 +365,6 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
336
365
  },
337
366
  });
338
367
  quiescenceReceiptOrProofDurable = true;
339
- disarmQuiescenceWatchdog();
340
368
  if (recoveryMode === "signal") {
341
369
  observability.info("agent turn quiescence proof handed to workflow recovery", {
342
370
  "opengeni.session_id": input.sessionId,
@@ -351,6 +379,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
351
379
  // the turn. Sync observer → buffer → single awaited append here (no unawaited
352
380
  // DB write inside the activity). Scoped to this turn; skipped if no turnId
353
381
  // (the op ran under a turn, so on the normal path turnId is set).
382
+ monitor.enter("event_flush");
354
383
  const machineOpEvents = machineOpObserver.drainEvents();
355
384
  if (machineOpEvents.length > 0 && attempt.turnId && attempt.executionGeneration > 0) {
356
385
  await waitForTurnFinalizerStep(
@@ -377,6 +406,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
377
406
  // Multi-account P4: flush the serving account's free per-turn caches ONCE,
378
407
  // best-effort (same discipline as today's usage write). Both writers skip
379
408
  // version/updatedAt, so neither can race the token-refresh CAS.
409
+ monitor.enter("provider_leases");
380
410
  if (providerTurn.effectiveCodexCredentialId) {
381
411
  // Part A: the latest scraped usage-header snapshot → the P2 usage cache. A
382
412
  // full both-windows snapshot (parseCodexUsageHeaders gates on both), so this
@@ -502,6 +532,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
502
532
  ) {
503
533
  // Review reads yield to queued work. Recovery snapshots have their own
504
534
  // physical-settlement requirement and are drained later in finalization.
535
+ monitor.enter("workspace_capture");
505
536
  sandboxState.turnEndCaptureInProgress = true;
506
537
  const captureSandbox = sandboxState.resolvedSandbox;
507
538
  const captureSession = sandboxState.setupBoxSession as ChannelASession;
@@ -549,6 +580,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
549
580
  },
550
581
  });
551
582
  }
583
+ monitor.enter("tool_close");
552
584
  eventing.toolPreparationClosing = true;
553
585
  if (eventing.toolPreparationReady) {
554
586
  await waitForTurnFinalizerStep(
@@ -569,9 +601,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
569
601
  finalizerSignal,
570
602
  );
571
603
  }
572
- if (eventing.heartbeatTimer) {
573
- clearInterval(eventing.heartbeatTimer);
574
- }
604
+ monitor.enter("sandbox_provisioning");
575
605
  if (sandboxState.turnSandboxProvisioner?.hasStarted()) {
576
606
  await waitForTurnFinalizerStep(
577
607
  sandboxState.turnSandboxProvisioner.waitForSettled(30_000),
@@ -599,12 +629,14 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
599
629
  if (sandboxState.leaseHeartbeatTimer) {
600
630
  stopLeaseHeartbeat();
601
631
  }
632
+ monitor.enter("sandbox_rotation");
602
633
  if (sandboxState.rotationPreemptionInFlight) {
603
634
  await sandboxState.rotationPreemptionInFlight.catch(() => undefined);
604
635
  }
605
636
  // No heartbeat can register another capture after this point. Join the
606
637
  // actual provider continuation before either starting a turn-end capture
607
638
  // or entering the proof-bearing holder release.
639
+ monitor.enter("workspace_snapshot");
608
640
  await drainInFlightWarmSnapshot();
609
641
  if (sandboxState.resolvedSandbox) {
610
642
  // TURN-END mid-session snapshot (sandbox-file-persistence): fold the
@@ -667,10 +699,13 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
667
699
  // Temporal cancellation: the eager listener may already have dropped
668
700
  // the holder, and this idempotent proof-bearing pass still must run.
669
701
  stopLeaseHeartbeat();
702
+ monitor.enter("sandbox_rotation");
670
703
  if (sandboxState.rotationPreemptionInFlight) {
671
704
  await sandboxState.rotationPreemptionInFlight.catch(() => undefined);
672
705
  }
706
+ monitor.enter("workspace_snapshot");
673
707
  await drainInFlightWarmSnapshot();
708
+ monitor.enter("sandbox_release");
674
709
  const sandboxReleaseTargets = new Set(sandboxState.lateSandboxesAwaitingWriterDrain);
675
710
  sandboxState.lateSandboxesAwaitingWriterDrain.clear();
676
711
  if (sandboxState.resolvedSandbox) {
@@ -198,13 +198,16 @@ export async function drainAttemptOwnedSandboxWriters(input: {
198
198
  gitCredentialRenewals: readonly Pick<GitCredentialRenewalController, "stop">[];
199
199
  codemodeTokenRenewal: Pick<CodemodeTokenRenewalController, "stop"> | null;
200
200
  runCredentialRenewal: Pick<RunCredentialRenewalController, "stop"> | null;
201
+ onStage?: (stage: "tool_writers" | "credential_renewals") => void;
201
202
  }): Promise<void> {
203
+ input.onStage?.("tool_writers");
202
204
  if (input.toolCancellationFence) {
203
205
  input.toolCancellationFence.cancel(
204
206
  input.cancellationReason ?? new Error("TURN_ATTEMPT_FENCED"),
205
207
  );
206
208
  await input.toolCancellationFence.waitForQuiescence();
207
209
  }
210
+ input.onStage?.("credential_renewals");
208
211
  await Promise.all(input.gitCredentialRenewals.map(async (renewal) => await renewal.stop()));
209
212
  await input.codemodeTokenRenewal?.stop();
210
213
  await input.runCredentialRenewal?.stop();
@@ -1,5 +1,4 @@
1
1
  import { createKnowledgeSourceSyncActivities } from "../knowledge-source-sync";
2
- import { prepareTurnKnowledgeSources } from "./knowledge-sources";
3
2
  import {
4
3
  assertModelConnectionAllowsTurn,
5
4
  freezeAgentLearningPolicy,
@@ -1030,44 +1029,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1030
1029
  knowledgeRecoveryNote =
1031
1030
  "[OpenGeni confirmation recovery unavailable] Do not assume an earlier remember confirmation was published. Inspect the current Knowledge or instruction review.";
1032
1031
  }
1033
- const attachmentPreparationNote = await prepareTurnKnowledgeSources({
1034
- settings: runSettings,
1035
- scope: {
1036
- accountId: input.accountId,
1037
- workspaceId: input.workspaceId,
1038
- sessionId: input.sessionId,
1039
- turnId: turn.id,
1040
- attemptId: input.attemptId,
1041
- executionGeneration: attempt.executionGeneration,
1042
- },
1043
- // Only attachments on this accepted turn are new source inputs.
1044
- // Session-level file receipts remain historical context and must
1045
- // never trigger metadata reads, downloads, or re-preparation.
1046
- resources: turn.resources,
1047
- selectedTools: session.firstPartyMcpTools,
1048
- permissions: session.firstPartyMcpPermissions,
1049
- learningMode: learning.effective.knowledge,
1050
- signal: cancellationSignal,
1051
- onOutcome: async (outcome) => {
1052
- if (outcome.status === "disabled") return;
1053
- await eventing.publish!(
1054
- [
1055
- {
1056
- type:
1057
- outcome.status === "failed"
1058
- ? "knowledge.source.failed"
1059
- : "knowledge.source.prepared",
1060
- payload: outcome,
1061
- },
1062
- ],
1063
- true,
1064
- );
1065
- },
1066
- });
1067
-
1068
- const knowledgeSourcePreparationNote =
1069
- [knowledgeRecoveryNote, attachmentPreparationNote].filter(Boolean).join("\n\n") ||
1070
- undefined;
1032
+ const knowledgeSourcePreparationNote = knowledgeRecoveryNote;
1071
1033
 
1072
1034
  const sandboxRoute = await resolveSandboxRoute({
1073
1035
  input,
@@ -14,6 +14,7 @@ export function configuredBundledSkillNames(context: BundledSkillConfiguration):
14
14
  const tools = new Set(context.firstPartyTools);
15
15
  const artifacts = () => tools.has("editable_artifact_list") && tools.has("editable_artifact_get");
16
16
  const definitions = [
17
+ { name: "opengeni-help", include: () => true },
17
18
  { name: "opengeni-visualize", include: () => true },
18
19
  { name: "document-parsing", include: () => true },
19
20
  { name: "opengeni-skills", include: () => true },
@@ -51,7 +51,7 @@ import { buildCodexTokenResolver } from "../codex-auth";
51
51
  import { CODEX_CLIENT_VERSION } from "@opengeni/codex";
52
52
  import { mergeResourceRefs } from "../common";
53
53
  import {
54
- defaultSessionMcpServerIds,
54
+ workspaceSessionToolPolicyDefaultServerIds,
55
55
  loadRigDefaultVariableSetEnvironment,
56
56
  mergeRigDefaultVariableSetEnvironment,
57
57
  buildApiIntegrationMcpServers,
@@ -212,7 +212,15 @@ export async function prepareTurnToolPolicy(deps: PrepareTurnToolPolicyDeps) {
212
212
  ? scheduledEffectiveMcpServerIds.filter((id) => currentMcpServerIds.has(id))
213
213
  : [...currentMcpServerIds],
214
214
  defaultMcpServerIds:
215
- scheduledEffectiveMcpServerIds ?? defaultSessionMcpServerIds(capabilitySettings.mcpServers),
215
+ scheduledEffectiveMcpServerIds ??
216
+ (session.toolPolicy.mode === "workspace_default"
217
+ ? await workspaceSessionToolPolicyDefaultServerIds(
218
+ db,
219
+ input.workspaceId,
220
+ capabilitySettings,
221
+ fileAuthoritySubjectId ?? undefined,
222
+ )
223
+ : []),
216
224
  });
217
225
  const mcpAvailabilityNote = unavailableMcpOperationalContext({
218
226
  droppedIds: resolvedToolPolicy.effectivePolicy.droppedIds,
@@ -1,3 +1,4 @@
1
+ import type { TurnHeartbeatDetails } from "../../op-journal";
1
2
  import type { Settings } from "@opengeni/config";
2
3
  import type { CodexUsageHeaderSnapshot } from "@opengeni/codex";
3
4
  import type { AppendEventInput, ApplySessionTurnSettlementInput } from "@opengeni/db";
@@ -123,6 +124,7 @@ export type RenewalState = {
123
124
  };
124
125
 
125
126
  export type EventingState = {
127
+ heartbeatDetails: TurnHeartbeatDetails | null;
126
128
  heartbeatTimer: ReturnType<typeof startActivityHeartbeat> | undefined;
127
129
  batcher: ReturnType<typeof createRuntimeBatcher> | null;
128
130
  preparedTools: Awaited<ReturnType<OpenGeniRuntime["prepareTools"]>> | null;
@@ -254,6 +256,7 @@ export function createTurnContext(input: {
254
256
  publishedRunCredentialNotices: new Set(),
255
257
  },
256
258
  eventing: {
259
+ heartbeatDetails: null,
257
260
  heartbeatTimer: undefined,
258
261
  batcher: null,
259
262
  preparedTools: null,
@@ -6,6 +6,7 @@ import {
6
6
  type Database,
7
7
  } from "@opengeni/db";
8
8
  import { publishDurableWorkspaceControlEvent } from "@opengeni/events";
9
+ import { recordWorkflowWakeReconciliation } from "@opengeni/observability";
9
10
  import type { ControlActivityServices } from "./types";
10
11
  import {
11
12
  reconcileAutomaticSessionTitleFanout,
@@ -115,23 +116,7 @@ export function createWorkflowWakeActivities(services: () => Promise<ControlActi
115
116
  pendingAdmissionBlockers,
116
117
  exhaustedBatchLimit,
117
118
  };
118
- if (claimed > 0) {
119
- service.observability.info("session workflow wake delivery reconciled", {
120
- claimed,
121
- signaled,
122
- delivered,
123
- pendingAdmission,
124
- unconfirmed,
125
- failed,
126
- exhaustedBatchLimit,
127
- ...Object.fromEntries(
128
- Object.entries(pendingAdmissionBlockers).map(([blocker, count]) => [
129
- `pendingAdmissionBlockers.${blocker}`,
130
- count,
131
- ]),
132
- ),
133
- });
134
- }
119
+ recordWorkflowWakeReconciliation(service.observability, result);
135
120
  return result;
136
121
  },
137
122
  };
@@ -1,20 +0,0 @@
1
- import { type FirstPartyMcpToolName, type Permission, type ResourceRef, type KnowledgeFilePreparationResult } from "@opengeni/contracts";
2
- import { type Settings } from "@opengeni/config";
3
- import { createFirstPartyAttemptClient } from "@opengeni/runtime";
4
- export type KnowledgeSourcePreparationOutcome = KnowledgeFilePreparationResult | {
5
- status: "failed";
6
- fileId: string;
7
- message: string;
8
- };
9
- /** Accepted chat/schedule resources use ordinary agent authority and policy. */
10
- export declare function prepareTurnKnowledgeSources(input: {
11
- settings: Settings;
12
- scope: Parameters<typeof createFirstPartyAttemptClient>[0]["scope"];
13
- resources: readonly ResourceRef[];
14
- selectedTools?: readonly FirstPartyMcpToolName[] | undefined;
15
- permissions?: readonly Permission[] | null | undefined;
16
- learningMode: "automatic" | "review_first" | "off";
17
- signal?: AbortSignal | undefined;
18
- onOutcome: (outcome: KnowledgeSourcePreparationOutcome) => Promise<void>;
19
- prepare?: (fileId: string) => Promise<KnowledgeFilePreparationResult>;
20
- }): Promise<string | undefined>;