@opengeni/worker-bundle 2.0.1 → 2.0.3-canary.36199476632001

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 (63) hide show
  1. package/dist/activities/agent-turn/agent-build.d.ts +2 -0
  2. package/dist/activities/agent-turn/code-search.d.ts +60 -0
  3. package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
  4. package/dist/activities/agent-turn/errors.d.ts +12 -1
  5. package/dist/activities/agent-turn/failure-settlement.d.ts +1 -1
  6. package/dist/activities/agent-turn/governance-model.d.ts +2 -0
  7. package/dist/activities/agent-turn/history.d.ts +2 -0
  8. package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
  9. package/dist/activities/agent-turn/session-title.d.ts +32 -2
  10. package/dist/activities/agent-turn/tool-environment.d.ts +4 -0
  11. package/dist/activities/context-compaction.d.ts +10 -2
  12. package/dist/activities/knowledge-indexing.d.ts +19 -0
  13. package/dist/activities/retained-screenshots.d.ts +7 -2
  14. package/dist/activities/sandbox-lease.d.ts +3 -4
  15. package/dist/{activities-control-QA6OLRQY.js → activities-control-I72CRMAE.js} +283 -35
  16. package/dist/activities-control-I72CRMAE.js.map +1 -0
  17. package/dist/{activities-turn-YVVKCGAK.js → activities-turn-CSDKK4N3.js} +531 -77
  18. package/dist/activities-turn-CSDKK4N3.js.map +1 -0
  19. package/dist/artifact-outbox-entry.js +3 -0
  20. package/dist/artifact-outbox-entry.js.map +1 -1
  21. package/dist/{chunk-M3NVMF2V.js → chunk-7M6G2SCF.js} +172 -17
  22. package/dist/chunk-7M6G2SCF.js.map +1 -0
  23. package/dist/{chunk-I7HKKMUJ.js → chunk-DSPP6CZL.js} +48 -3
  24. package/dist/chunk-DSPP6CZL.js.map +1 -0
  25. package/dist/index.js +10 -5
  26. package/dist/index.js.map +1 -1
  27. package/dist/observability-metrics.d.ts +11 -1
  28. package/dist/sandbox-resume.d.ts +46 -0
  29. package/dist/workflow-bundle.js +80 -2
  30. package/dist/workflows/session.d.ts +30 -0
  31. package/package.json +20 -19
  32. package/src/activities/agent-turn/agent-build.ts +4 -0
  33. package/src/activities/agent-turn/code-search.ts +275 -0
  34. package/src/activities/agent-turn/codex-capacity.ts +37 -2
  35. package/src/activities/agent-turn/compaction-prep.ts +52 -16
  36. package/src/activities/agent-turn/errors.ts +67 -4
  37. package/src/activities/agent-turn/failure-settlement.ts +40 -8
  38. package/src/activities/agent-turn/governance-model.ts +17 -1
  39. package/src/activities/agent-turn/history.ts +14 -1
  40. package/src/activities/agent-turn/media-artifacts.ts +2 -1
  41. package/src/activities/agent-turn/run.ts +4 -0
  42. package/src/activities/agent-turn/sandbox-establish.ts +12 -1
  43. package/src/activities/agent-turn/sandbox-runtime.ts +25 -9
  44. package/src/activities/agent-turn/session-title.ts +73 -3
  45. package/src/activities/agent-turn/stream-attempt.ts +27 -15
  46. package/src/activities/agent-turn/tool-environment.ts +65 -0
  47. package/src/activities/context-compaction.ts +49 -14
  48. package/src/activities/knowledge-indexing.ts +240 -15
  49. package/src/activities/retained-screenshots.ts +82 -11
  50. package/src/activities/sandbox-lease.ts +141 -21
  51. package/src/activities/scheduled-tasks.ts +18 -2
  52. package/src/activity-services.ts +16 -4
  53. package/src/editable-artifact-hint-broker.ts +3 -0
  54. package/src/index.ts +2 -2
  55. package/src/observability-metrics.ts +70 -1
  56. package/src/personal-github-git-credentials.ts +2 -0
  57. package/src/sandbox-resume.ts +211 -5
  58. package/src/workflows/activities.ts +14 -1
  59. package/src/workflows/session.ts +102 -6
  60. package/dist/activities-control-QA6OLRQY.js.map +0 -1
  61. package/dist/activities-turn-YVVKCGAK.js.map +0 -1
  62. package/dist/chunk-I7HKKMUJ.js.map +0 -1
  63. package/dist/chunk-M3NVMF2V.js.map +0 -1
@@ -39,10 +39,10 @@ import {
39
39
  countSandboxLeasesByLiveness,
40
40
  deferRetainedProcessReconciliation,
41
41
  forceDrainOverLimitViewerOnlyBoxes,
42
- getBillingBalance,
43
42
  listCreditBalancesByAccount,
44
43
  listLegacyModalCheckpointSlots,
45
44
  listLiveModalSandboxLeaseAttributions,
45
+ markWarmBillingStopCutoff,
46
46
  listMeterableWarmLeases,
47
47
  listSandboxViewerForceDrainWorkspaceIds,
48
48
  markSandboxCheckpointArtifactDeletePending,
@@ -186,8 +186,7 @@ export type ReapSandboxLeasesResult = {
186
186
  skipped: number;
187
187
  /** Warm viewer-only leases that accrued warm-seconds this tick (P2.1). */
188
188
  metered: number;
189
- /** Viewer-only boxes force-drained because their workspace is over a limit
190
- * (0 balance / over the warm cap) — turn-held boxes are never drained (P2.1). */
189
+ /** Holderless boxes accelerated to draining by the optional warm cap. */
191
190
  forceDrained: number;
192
191
  /** Provider-side Modal orphan sandboxes terminated by the defensive sweep. */
193
192
  modalOrphansTerminated: number;
@@ -256,6 +255,7 @@ export type TerminateBoxFn = (
256
255
  diskBackedArchives?: boolean,
257
256
  releaseFailedCapture?: () => Promise<void>,
258
257
  workspaceId?: string,
258
+ beforeProviderStop?: () => Promise<void>,
259
259
  ) => Promise<boolean | ProviderTerminationOutcome>;
260
260
 
261
261
  export type SweepModalOrphansFn = (
@@ -482,7 +482,14 @@ export function createSandboxLeaseActivities(
482
482
  options: SandboxLeaseActivityOptions = {},
483
483
  ) {
484
484
  const terminateBox: TerminateBoxFn =
485
- options.terminateBox ??
485
+ (options.terminateBox
486
+ ? async (...args: Parameters<TerminateBoxFn>) => {
487
+ // The injected test seam represents the stop itself; the real
488
+ // implementation invokes this only after capture has succeeded.
489
+ await args[10]?.();
490
+ return options.terminateBox!(...args);
491
+ }
492
+ : null) ??
486
493
  (async (
487
494
  settings,
488
495
  lease,
@@ -494,6 +501,7 @@ export function createSandboxLeaseActivities(
494
501
  diskBackedArchives,
495
502
  releaseFailedCapture,
496
503
  workspaceId,
504
+ beforeProviderStop,
497
505
  ) =>
498
506
  await terminateProviderBox(
499
507
  settings,
@@ -508,6 +516,7 @@ export function createSandboxLeaseActivities(
508
516
  diskBackedArchives,
509
517
  releaseFailedCapture,
510
518
  workspaceId,
519
+ beforeProviderStop,
511
520
  ));
512
521
  const sweepModalOrphans: SweepModalOrphansFn =
513
522
  options.sweepModalOrphans ?? sweepModalOrphansForConfiguredBackend;
@@ -735,8 +744,7 @@ export function createSandboxLeaseActivities(
735
744
  metered = meterResult.accrued;
736
745
  const forceDrainWorkspaceIds = new Set<string>();
737
746
  if (
738
- settings.billingMode === "stripe" ||
739
- settings.usageLimitsMode === "managed" ||
747
+ settings.sandboxWarmBillingMode === "credits" ||
740
748
  settings.sandboxMaxWarmSecondsPerWorkspace > 0
741
749
  ) {
742
750
  for (const workspaceId of meterResult.workspaceIds) {
@@ -1507,6 +1515,25 @@ async function reconcileTerminalRetainedProcesses(
1507
1515
  }
1508
1516
  }
1509
1517
 
1518
+ if (
1519
+ observation?.status === "deferred" &&
1520
+ process.providerBackend === "modal" &&
1521
+ !process.providerBindingKey &&
1522
+ !process.deadlineCancellationRequestedAt
1523
+ ) {
1524
+ // An unbound historical command cannot be signalled safely. Still
1525
+ // record deadline intent when its lease matches: a failed observer
1526
+ // must not block capture until the provider expires.
1527
+ try {
1528
+ await requestRetainedProcessDeadlineCancellation(db, processScope);
1529
+ } catch (error) {
1530
+ observability.warn("sandbox reaper: legacy deadline cancellation intent failed", {
1531
+ processId: process.id,
1532
+ error: error instanceof Error ? error.message : String(error),
1533
+ });
1534
+ }
1535
+ }
1536
+
1510
1537
  if (observation === null) {
1511
1538
  const lease = await readLease(db, process.workspaceId, process.sandboxGroupId).catch(
1512
1539
  () => null,
@@ -1583,11 +1610,19 @@ async function reconcileTerminalRetainedProcesses(
1583
1610
  );
1584
1611
  supervisionMetric("cancellation_intent");
1585
1612
  }
1586
- observation = await probe(
1613
+ const legacyDeadlineStop =
1614
+ lease!.rotationReason === "provider_deadline" &&
1615
+ !supervised &&
1616
+ !process.deadlineCancellationRequestedAt;
1617
+ const probeOutcome = await probe(
1587
1618
  settings,
1588
1619
  lease!,
1589
1620
  process,
1590
- claim.ownerState === "background_stopping" ? "cancel" : "observe",
1621
+ (claim.ownerState === "background_stopping" &&
1622
+ !process.deadlineCancellationRequestedAt) ||
1623
+ legacyDeadlineStop
1624
+ ? "cancel"
1625
+ : "observe",
1591
1626
  async (result, chunkId, stream, streamFidelity) => {
1592
1627
  if (
1593
1628
  typeof result !== "string" ||
@@ -1610,7 +1645,20 @@ async function reconcileTerminalRetainedProcesses(
1610
1645
  .catch(() => undefined);
1611
1646
  },
1612
1647
  commandPersistence,
1648
+ ).then(
1649
+ (value) => ({ ok: true as const, value }),
1650
+ (error: unknown) => ({ ok: false as const, error }),
1613
1651
  );
1652
+ // Legacy PTY input reserves Ctrl-C before the cancellation fence.
1653
+ // Non-PTY has no command signal in the Modal SDK: probe it without
1654
+ // injecting Ctrl-C as data, then fence and save after the grace.
1655
+ if (
1656
+ legacyDeadlineStop &&
1657
+ !(await requestRetainedProcessDeadlineCancellation(db, processScope))
1658
+ )
1659
+ throw new Error("Deadline cancellation no longer owns its rotating lease");
1660
+ if (!probeOutcome.ok) throw probeOutcome.error;
1661
+ observation = probeOutcome.value;
1614
1662
  if (supervised) {
1615
1663
  supervisionMetric(
1616
1664
  (await commandPersistence.loadSupervisionReceipt())
@@ -1899,7 +1947,13 @@ export async function probeRetainedProcessAtProvider(
1899
1947
  if (pending) {
1900
1948
  if (!captureOutput) throw new Error("Pending command output requires its persistence callback");
1901
1949
  await captureRetainedProbeOutput(process.id, pending.result, captureOutput);
1902
- return classifyRetainedProcessPollResult(pending.result, process.providerSessionId);
1950
+ const pendingObservation = classifyRetainedProcessPollResult(
1951
+ pending.result,
1952
+ process.providerSessionId,
1953
+ );
1954
+ // Capture of a previous page must not consume the only deadline stop
1955
+ // attempt. Terminal proof needs no further provider call.
1956
+ if (mode !== "cancel" || pendingObservation.status === "proved") return pendingObservation;
1903
1957
  }
1904
1958
  const envelopeBackend = (lease.resumeState as { backendId?: unknown }).backendId;
1905
1959
  if (
@@ -1996,6 +2050,7 @@ export async function probeRetainedProcessAtProvider(
1996
2050
  if (typeof session.writeStdin !== "function") {
1997
2051
  return { status: "deferred", reason: "backend_unsupported" };
1998
2052
  }
2053
+ let legacyPty = false;
1999
2054
  if (process.providerBackend === "modal") {
2000
2055
  let liveBinding: Awaited<ReturnType<typeof resolveModalCheckpointProviderBindingForSession>>;
2001
2056
  try {
@@ -2027,6 +2082,7 @@ export async function probeRetainedProcessAtProvider(
2027
2082
  if (!command || !providerPersistence || !session.bindProviderCommand)
2028
2083
  return { status: "deferred", reason: "process_observation_unavailable" };
2029
2084
  session.bindProviderCommand(process.providerSessionId, command, providerPersistence);
2085
+ legacyPty = command.pty === true;
2030
2086
  }
2031
2087
  const capturePage = async (value: unknown): Promise<void> => {
2032
2088
  if (!captureOutput) {
@@ -2062,7 +2118,14 @@ export async function probeRetainedProcessAtProvider(
2062
2118
  result = await withRetainedProcessProbeTimeout(
2063
2119
  session.writeStdin({
2064
2120
  sessionId: process.providerSessionId,
2065
- chars: mode === "cancel" && !supervisedCancelled ? "\u0003" : "",
2121
+ // Ctrl-C is a signal only through a PTY. For a non-PTY command it is
2122
+ // ordinary stdin data and can corrupt the command's input.
2123
+ chars:
2124
+ mode === "cancel" &&
2125
+ !supervisedCancelled &&
2126
+ (process.providerBackend !== "modal" || legacyPty)
2127
+ ? "\u0003"
2128
+ : "",
2066
2129
  yieldTimeMs: 1_000,
2067
2130
  maxOutputTokens: 2_000,
2068
2131
  }),
@@ -2092,6 +2155,9 @@ export async function probeRetainedProcessAtProvider(
2092
2155
  observation.status === "deferred" &&
2093
2156
  observation.reason === "provider_running" &&
2094
2157
  lease.rotationRequestedAt !== null &&
2158
+ mode !== "cancel" &&
2159
+ !process.cancellationRequestedAt &&
2160
+ legacyPty &&
2095
2161
  !(await providerPersistence
2096
2162
  ?.load()
2097
2163
  .then((command) => command?.kind === "modal-router-v1" && command.supervision))
@@ -2197,19 +2263,25 @@ async function accrueWarmTick(
2197
2263
  }
2198
2264
  await forEachWithConcurrency(leases, SANDBOX_MAINTENANCE_ITEM_CONCURRENCY, async (lease) => {
2199
2265
  try {
2200
- const rate = sandboxWarmRateMicrosPerSecond(settings, lease.backend);
2266
+ const rate =
2267
+ settings.sandboxWarmBillingMode === "usage_only"
2268
+ ? 0
2269
+ : sandboxWarmRateMicrosPerSecond(settings, lease.backend);
2201
2270
  const result = await accrueWarmSeconds(db, {
2202
2271
  accountId: lease.accountId,
2203
2272
  workspaceId: lease.workspaceId,
2204
2273
  sandboxGroupId: lease.sandboxGroupId,
2205
2274
  expectedEpoch: lease.leaseEpoch,
2206
2275
  warmRateMicrosPerSecond: rate,
2276
+ billingMode: settings.sandboxWarmBillingMode,
2207
2277
  subjectId: lease.sandboxGroupId,
2208
2278
  });
2209
2279
  if (result.accrued) {
2210
2280
  accrued += 1;
2211
2281
  }
2212
- recordCreditMicros(observability, "usage", result.costMicros);
2282
+ if (result.costMicros > 0 && settings.sandboxWarmBillingMode === "credits") {
2283
+ recordCreditMicros(observability, "usage", result.costMicros);
2284
+ }
2213
2285
  } catch (error) {
2214
2286
  observability.warn("sandbox reaper: warm-seconds accrual failed for lease", {
2215
2287
  workspaceId: lease.workspaceId,
@@ -2325,8 +2397,8 @@ async function refreshSandboxInventoryGauge(
2325
2397
 
2326
2398
  /**
2327
2399
  * The per-workspace warm-cap + force-drain pass (P2.1). For each workspace with a
2328
- * live warm box, under the usage lock: if it is at 0 balance (when a billing /
2329
- * managed mode is on) or over its warm cap, force-drain its VIEWER-ONLY boxes
2400
+ * live warm box, under the usage lock: if it is over its optional warm cap,
2401
+ * accelerate only holderless boxes to draining
2330
2402
  * (guarded turn_holders=0 — a paying turn is never killed). Returns the count of
2331
2403
  * viewer-only boxes force-drained. Per-workspace best-effort.
2332
2404
  */
@@ -2336,8 +2408,7 @@ async function forceDrainOverLimitWorkspaces(
2336
2408
  workspaceIds: Set<string>,
2337
2409
  observability: ActivityServices["observability"],
2338
2410
  ): Promise<number> {
2339
- const enforceBalance =
2340
- settings.billingMode === "stripe" || settings.usageLimitsMode === "managed";
2411
+ const enforceBalance = false;
2341
2412
  const cap = settings.sandboxMaxWarmSecondsPerWorkspace;
2342
2413
  let forceDrained = 0;
2343
2414
  await forEachWithConcurrency(
@@ -2345,13 +2416,8 @@ async function forceDrainOverLimitWorkspaces(
2345
2416
  SANDBOX_MAINTENANCE_ITEM_CONCURRENCY,
2346
2417
  async (workspaceId) => {
2347
2418
  try {
2348
- const { accountId } = await rlsContextForWorkspace(db, workspaceId);
2349
- const balance = enforceBalance
2350
- ? await getBillingBalance(db, accountId)
2351
- : ({ balanceMicros: 1 } as { balanceMicros: number });
2352
2419
  const result = await forceDrainOverLimitViewerOnlyBoxes(db, {
2353
2420
  workspaceId,
2354
- balanceMicros: balance.balanceMicros,
2355
2421
  enforceBalance,
2356
2422
  maxWarmSecondsPerWorkspace: cap,
2357
2423
  idleGraceMs: settings.sandboxIdleGraceMs,
@@ -2939,6 +3005,20 @@ async function terminateDrainableBox(
2939
3005
  ) {
2940
3006
  return false;
2941
3007
  }
3008
+ // Stamp the charge horizon after verified capture and immediately before
3009
+ // provider stop. Capture can fail and release its claim, allowing re-arm.
3010
+ // A retry after stop success cannot charge post-termination wall time.
3011
+ let stopCutoffMarked = false;
3012
+ const beforeProviderStop = async (): Promise<void> => {
3013
+ const cutoff = await markWarmBillingStopCutoff(db, {
3014
+ accountId,
3015
+ workspaceId: row.workspaceId,
3016
+ sandboxGroupId: row.sandboxGroupId,
3017
+ expectedEpoch: row.leaseEpoch,
3018
+ });
3019
+ if (!cutoff) throw new Error("sandbox stop cutoff was fenced before provider termination");
3020
+ stopCutoffMarked = true;
3021
+ };
2942
3022
  const termination: ProviderTerminationOutcome | boolean = providerMissingBeforeCapture
2943
3023
  ? { terminated: true, providerMissingBeforeCapture: true }
2944
3024
  : await terminateBox(
@@ -2962,12 +3042,49 @@ async function terminateDrainableBox(
2962
3042
  });
2963
3043
  },
2964
3044
  row.workspaceId,
3045
+ beforeProviderStop,
2965
3046
  );
2966
3047
  const terminated = typeof termination === "boolean" ? termination : termination.terminated;
2967
3048
  if (!terminated) {
2968
3049
  return false;
2969
3050
  }
2970
3051
 
3052
+ // Self-hosted/none and an unassigned lease have no provider stop callback;
3053
+ // finish their usage at logical detach without touching customer compute.
3054
+ // A managed instance that was stopped without its callback must fail closed.
3055
+ const providerMissingDuringStop =
3056
+ providerMissingBeforeCapture ||
3057
+ (typeof termination !== "boolean" && termination.providerMissingBeforeCapture);
3058
+ if (!stopCutoffMarked && !providerMissingDuringStop) {
3059
+ if (managedProvider && lease.instanceId) {
3060
+ throw new Error("managed sandbox stopped without a warm usage cutoff");
3061
+ }
3062
+ await beforeProviderStop();
3063
+ }
3064
+
3065
+ // Stop was confirmed but the lease is not cold yet. This final epoch-fenced
3066
+ // tick settles the interval since the last heartbeat/reaper sweep, including
3067
+ // a box drained before its first periodic tick. If settlement fails, keep
3068
+ // the draining receipt for retry instead of silently losing the charge.
3069
+ const finalRate =
3070
+ settings.sandboxWarmBillingMode === "usage_only"
3071
+ ? 0
3072
+ : sandboxWarmRateMicrosPerSecond(settings, backend);
3073
+ if (!providerMissingDuringStop) {
3074
+ const finalTick = await accrueWarmSeconds(db, {
3075
+ accountId,
3076
+ workspaceId: row.workspaceId,
3077
+ sandboxGroupId: row.sandboxGroupId,
3078
+ expectedEpoch: row.leaseEpoch,
3079
+ warmRateMicrosPerSecond: finalRate,
3080
+ billingMode: settings.sandboxWarmBillingMode,
3081
+ finalDrain: true,
3082
+ });
3083
+ if (finalTick.costMicros > 0 && settings.sandboxWarmBillingMode === "credits") {
3084
+ recordCreditMicros(observability, "usage", finalTick.costMicros);
3085
+ }
3086
+ }
3087
+
2971
3088
  const providerMissing =
2972
3089
  providerMissingBeforeCapture ||
2973
3090
  (typeof termination === "boolean" ? false : termination.providerMissingBeforeCapture);
@@ -3097,6 +3214,7 @@ export async function terminateProviderBox(
3097
3214
  diskBackedArchives = false,
3098
3215
  releaseFailedCapture?: () => Promise<void>,
3099
3216
  workspaceId?: string,
3217
+ beforeProviderStop?: () => Promise<void>,
3100
3218
  ): Promise<ProviderTerminationOutcome> {
3101
3219
  const durableBackendId = (lease.resumeBackendId ?? lease.backend) as string;
3102
3220
  const backend = sandboxBackendForSdkBackendId(durableBackendId) ?? durableBackendId;
@@ -3196,6 +3314,7 @@ export async function terminateProviderBox(
3196
3314
  return { terminated: false, providerMissingBeforeCapture: false };
3197
3315
  }
3198
3316
  try {
3317
+ await beforeProviderStop?.();
3199
3318
  await terminateModalById(settings, lease.instanceId);
3200
3319
  } catch (error) {
3201
3320
  if (!isProviderSandboxNotFoundError("modal", error)) {
@@ -3439,6 +3558,7 @@ export async function terminateProviderBox(
3439
3558
  throw new Error(`sandbox backend ${backend} returned no terminable session state`);
3440
3559
  }
3441
3560
  prepareProviderForTeardownAfterCapture(backend, session);
3561
+ await beforeProviderStop?.();
3442
3562
  await terminateManagedSandboxSession(client, sessionState, session);
3443
3563
  return { terminated: true, providerMissingBeforeCapture: false };
3444
3564
  } catch (error) {
@@ -20,6 +20,7 @@ import {
20
20
  openGeniSlackBotMetadata,
21
21
  requireOpenGeniSlackBotConnection,
22
22
  resolveWorkspaceCatalogSettings,
23
+ resolveScheduledTaskDefaultModel,
23
24
  resolveSessionToolPolicy,
24
25
  workspaceCustomModelReference,
25
26
  lockActiveCustomModelForAdmission,
@@ -438,8 +439,23 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
438
439
  )
439
440
  : null;
440
441
  const settings = await settingsForTask(task, targetSessionExecutionBase?.model);
441
- const model = task.agentConfig.model ?? settings.openaiModel;
442
- const reasoningEffort = task.agentConfig.reasoningEffort ?? settings.openaiReasoningEffort;
442
+ // A task that names no model resolves the new-chat default at each fresh
443
+ // occurrence (saved workspace default, then a usable connected
444
+ // subscription under the task's frozen SuperGrok authority, then the
445
+ // credits default while the organization holds a credit balance, then the
446
+ // deployment default). A frozen SuperGrok pool that is gone only means
447
+ // SuperGrok is not ready. The accepted execution freezes the result, so
448
+ // retries and recovery never resolve again. Existing-session runs keep
449
+ // that session's model.
450
+ const resolvedDefault =
451
+ task.agentConfig.model || targetSessionExecutionBase
452
+ ? null
453
+ : await resolveScheduledTaskDefaultModel(db, settings, task);
454
+ const model = task.agentConfig.model ?? resolvedDefault?.model ?? settings.openaiModel;
455
+ const reasoningEffort =
456
+ task.agentConfig.reasoningEffort ??
457
+ resolvedDefault?.reasoningEffort ??
458
+ settings.openaiReasoningEffort;
443
459
  let sandboxBackend = task.agentConfig.sandboxBackend ?? settings.sandboxBackend;
444
460
  let sandboxOs: "linux" | "macos" | "windows" = "linux";
445
461
  const taskTools = withFirstPartyTools(settings, task.agentConfig.tools);
@@ -1,11 +1,20 @@
1
- import { dbSearchPath, getSettings, resolveNatsControlPlaneAuth } from "@opengeni/config";
2
- import { configureChildLifecycleNotices, createDb } from "@opengeni/db";
1
+ import {
2
+ codeSearchDeploymentPolicy,
3
+ dbSearchPath,
4
+ getSettings,
5
+ resolveNatsControlPlaneAuth,
6
+ } from "@opengeni/config";
7
+ import {
8
+ configureChildLifecycleNotices,
9
+ configureCodeSearchDeploymentPolicy,
10
+ createDb,
11
+ } from "@opengeni/db";
3
12
  import { createNatsEventBus } from "@opengeni/events";
4
13
  import { createObservability } from "@opengeni/observability";
5
14
  import { resolveCatalogSettings } from "@opengeni/core";
6
15
  import { createObjectStorage } from "@opengeni/storage";
7
16
  import type { ActivityDependencies, SharedActivityServices } from "./activities/types";
8
- import { observabilityEventLogger } from "./observability-metrics";
17
+ import { observabilityEventBusOptions } from "./observability-metrics";
9
18
  import { buildPersonalGitHubGitCredentials } from "./personal-github-git-credentials";
10
19
  import { createStandaloneConnectionCredentialsPort } from "./pr-review-credentials";
11
20
 
@@ -40,6 +49,9 @@ export function createSharedActivityServices(
40
49
  // Child lifecycle notice producers are process-global in @opengeni/db;
41
50
  // install the boot-validated rollout flag once for this worker.
42
51
  configureChildLifecycleNotices({ enabled: settings.childLifecycleNoticesEnabled });
52
+ // Child, scheduled and automation sessions created here freeze their
53
+ // code_search decision from the same deployment policy as the API.
54
+ configureCodeSearchDeploymentPolicy(codeSearchDeploymentPolicy(settings));
43
55
  return {
44
56
  settings: resolvedSettings,
45
57
  catalogSourceSettings: settings,
@@ -51,7 +63,7 @@ export function createSharedActivityServices(
51
63
  controlPlaneAuth
52
64
  ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password }
53
65
  : undefined,
54
- { logger: observabilityEventLogger(observability) },
66
+ observabilityEventBusOptions(observability),
55
67
  )),
56
68
  objectStorage: dependencies.objectStorage ?? createObjectStorage(settings),
57
69
  observability,
@@ -161,6 +161,9 @@ export async function connectEditableArtifactHintBroker(
161
161
  name,
162
162
  reconnect: true,
163
163
  maxReconnectAttempts: -1,
164
+ // Same policy as the event bus: a broker or auth-callout restart must not
165
+ // permanently close this connection after two identical auth errors.
166
+ ignoreAuthErrorAbort: true,
164
167
  reconnectTimeWait: 2_000,
165
168
  reconnectJitter: 1_000,
166
169
  reconnectJitterTLS: 1_000,
package/src/index.ts CHANGED
@@ -61,7 +61,7 @@ import {
61
61
  initializeContextCompactionMetrics,
62
62
  initializeWorkerOutcomeMetrics,
63
63
  normalizeTurnTaskQueueStats,
64
- observabilityEventLogger,
64
+ observabilityEventBusOptions,
65
65
  startContextCompactionPendingMonitor,
66
66
  startSessionRecoveryMonitor,
67
67
  startTurnCapacityMonitor,
@@ -1173,7 +1173,7 @@ export async function startWorker() {
1173
1173
  controlPlaneAuth
1174
1174
  ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password }
1175
1175
  : undefined,
1176
- { logger: observabilityEventLogger(observability) },
1176
+ observabilityEventBusOptions(observability),
1177
1177
  ),
1178
1178
  { ...retryOptions, onRetry },
1179
1179
  );
@@ -2,7 +2,11 @@ import { createHash } from "node:crypto";
2
2
  import { errorCodeToJSON } from "@opengeni/agent-proto";
3
3
  import { SandboxBackend, type SessionEventType } from "@opengeni/contracts";
4
4
  import type { SessionEventAppendPhaseObservation } from "@opengeni/db";
5
- import type { EventLogger } from "@opengeni/events";
5
+ import {
6
+ natsSubscriptionTerminationCounter,
7
+ type EventBusOptions,
8
+ type EventLogger,
9
+ } from "@opengeni/events";
6
10
  import type { Attributes, AttributeValue, Observability } from "@opengeni/observability";
7
11
  import type { CompanyBrainContributionReceipt } from "./model-context-contributions";
8
12
  import {
@@ -80,6 +84,16 @@ const CONTEXT_COMPACTIONS_METRIC = {
80
84
  help: "Total completed context compactions, by trigger.",
81
85
  } as const;
82
86
 
87
+ /** Logger plus the closed-label subscription-termination counter for NATS connections. */
88
+ export function observabilityEventBusOptions(
89
+ observability: Observability,
90
+ ): Pick<EventBusOptions, "logger" | "onSubscriptionTerminated"> {
91
+ return {
92
+ logger: observabilityEventLogger(observability),
93
+ onSubscriptionTerminated: natsSubscriptionTerminationCounter(observability),
94
+ };
95
+ }
96
+
83
97
  export function observabilityEventLogger(observability: Observability): EventLogger {
84
98
  return {
85
99
  debug: (message, attributes) => observability.debug(message, eventAttributes(attributes)),
@@ -156,6 +170,13 @@ export function runtimeMetricsHooksForObservability(
156
170
  labels: { backend, stage },
157
171
  });
158
172
  },
173
+ onSandboxReadinessReplacement: ({ backend, outcome }) => {
174
+ observability.incrementCounter({
175
+ name: "opengeni_sandbox_readiness_replacements_total",
176
+ help: "Fresh sandbox command-readiness replacement decisions by backend and outcome.",
177
+ labels: { backend, outcome },
178
+ });
179
+ },
159
180
  onSandboxProviderApiThrottle: ({ backend, operation }) => {
160
181
  observability.incrementCounter({
161
182
  name: "opengeni_sandbox_provider_api_throttles_total",
@@ -2264,3 +2285,51 @@ export function modelCallAccountContext(input: {
2264
2285
  accountChangedFromPrevCall,
2265
2286
  };
2266
2287
  }
2288
+
2289
+ export type CodeSearchCallOutcome =
2290
+ | "completed"
2291
+ | "jev_unavailable"
2292
+ | "jev_rejected"
2293
+ | "workspace_unavailable"
2294
+ | "invalid_arguments"
2295
+ | "breaker_open"
2296
+ | "cancelled"
2297
+ | "failed";
2298
+
2299
+ /** One `code_search` tool call: outcome, wall time and the Jev work it used. */
2300
+ export function recordCodeSearchCall(
2301
+ observability: Observability,
2302
+ input: {
2303
+ outcome: CodeSearchCallOutcome;
2304
+ durationSeconds: number;
2305
+ jevRequests: number;
2306
+ jevCostUsd: number;
2307
+ },
2308
+ ): void {
2309
+ observability.incrementCounter({
2310
+ name: "opengeni_code_search_calls_total",
2311
+ help: "Jev-backed code_search tool calls by outcome.",
2312
+ labels: { outcome: input.outcome },
2313
+ });
2314
+ observability.observeHistogram({
2315
+ name: "opengeni_code_search_duration_seconds",
2316
+ help: "Wall time of one code_search tool call.",
2317
+ buckets: [0.5, 1, 2, 4, 8, 15, 30, 60],
2318
+ labels: { outcome: input.outcome },
2319
+ value: Math.max(0, input.durationSeconds),
2320
+ });
2321
+ if (input.jevRequests > 0) {
2322
+ observability.incrementCounter({
2323
+ name: "opengeni_code_search_jev_requests_total",
2324
+ help: "Jev requests made by code_search.",
2325
+ amount: input.jevRequests,
2326
+ });
2327
+ }
2328
+ if (input.jevCostUsd > 0) {
2329
+ observability.incrementCounter({
2330
+ name: "opengeni_code_search_jev_cost_micro_usd_total",
2331
+ help: "Estimated Jev list-price cost of code_search, in micro-USD.",
2332
+ amount: Math.round(input.jevCostUsd * 1_000_000),
2333
+ });
2334
+ }
2335
+ }
@@ -295,6 +295,8 @@ export function personalGitBrokerOrigin(settings: Settings): string | null {
295
295
  (url.hostname === "host.docker.internal" ||
296
296
  url.hostname === cachedDockerBridgeGateway ||
297
297
  /^[a-z][a-z0-9-]{0,62}$/iu.test(url.hostname));
298
+ // The `local` and `test` environments accept any origin, which covers the
299
+ // local launcher's Linux Docker sandbox route on the Compose network gateway.
298
300
  const privateExecutionOrigin =
299
301
  (url.protocol === "http:" &&
300
302
  (url.hostname === "127.0.0.1" || url.hostname === "localhost" || dockerPrivateOrigin)) ||