@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
@@ -24,6 +24,7 @@ import {
24
24
  compactionFailureReasonFromError,
25
25
  compactionFailureTurnEventPayload,
26
26
  computeIdleDelayMs,
27
+ createFreshSandboxReadinessReplacementBudget,
27
28
  createKnowledgeSourceSyncActivities,
28
29
  createModelHistoryAttachmentProjector,
29
30
  createRuntimeBatcher,
@@ -86,13 +87,14 @@ import {
86
87
  withFirstPartyTools,
87
88
  wrapLazyTurnBoxWithRouting,
88
89
  wrapTurnBoxWithRouting
89
- } from "./chunk-M3NVMF2V.js";
90
+ } from "./chunk-7M6G2SCF.js";
90
91
  import {
91
92
  StreamTimingMetrics,
92
93
  makeMachineOpObserver,
93
94
  modelCallAccountContext,
94
95
  modelRequestLifecycleMetricsFor,
95
96
  recordBatchFlush,
97
+ recordCodeSearchCall,
96
98
  recordCompanyBrainContributions,
97
99
  recordContextCompaction,
98
100
  recordContextCompactionStarted,
@@ -113,7 +115,7 @@ import {
113
115
  recordTurnWorkerPreparationTotal,
114
116
  runtimeMetricsHooksForObservability,
115
117
  turnLifecycleMetricsFor
116
- } from "./chunk-I7HKKMUJ.js";
118
+ } from "./chunk-DSPP6CZL.js";
117
119
  import {
118
120
  __export
119
121
  } from "./chunk-PZ5AY32C.js";
@@ -1445,11 +1447,13 @@ import {
1445
1447
  COMPUTER_SCREENSHOT_RETENTION_MS,
1446
1448
  COMPUTER_SCREENSHOT_WORKSPACE_QUOTA_BYTES,
1447
1449
  RetainedArtifactMetadataSchema,
1448
- retainedScreenshotReferenceFromFile
1450
+ retainedScreenshotReferenceFromFile,
1451
+ retainedSessionScreenshotKindFromObjectKey
1449
1452
  } from "@opengeni/contracts";
1450
1453
  import {
1451
1454
  RetainedScreenshotQuotaExceededError,
1452
1455
  getRetainedScreenshotArtifact,
1456
+ getRetainedScreenshotArtifactForToolCall,
1453
1457
  isDatabasePersistenceFailure,
1454
1458
  isSessionEventPersistenceError,
1455
1459
  prepareRetainedScreenshotArtifact,
@@ -1554,13 +1558,16 @@ function toolOutputContainsInlineImage(output) {
1554
1558
  return output.some((entry) => {
1555
1559
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
1556
1560
  const record7 = entry;
1557
- return record7.type === "input_image" && sdkImageSourceContainsInlineImage(record7.image ?? record7.image_url ?? record7.imageUrl) || record7.type === "image" && decodeInlineMcpImage(record7.data, record7.mimeType) !== null;
1561
+ return record7.type === "input_image" && sdkImageSourceContainsInlineImage(record7.image ?? record7.image_url ?? record7.imageUrl) || record7.type === "image" && isDeclaredInlineMcpImage(record7);
1558
1562
  });
1559
1563
  }
1560
1564
  if (!output || typeof output !== "object") return false;
1561
1565
  const record6 = output;
1562
1566
  if (Array.isArray(record6.content)) return toolOutputContainsInlineImage(record6.content);
1563
- return record6.type === "image" && sdkImageSourceContainsInlineImage(record6.image);
1567
+ return record6.type === "image" && (sdkImageSourceContainsInlineImage(record6.image) || isDeclaredInlineMcpImage(record6));
1568
+ }
1569
+ function isDeclaredInlineMcpImage(record6) {
1570
+ return typeof record6.data === "string" && typeof record6.mimeType === "string" && record6.mimeType.toLowerCase().startsWith("image/");
1564
1571
  }
1565
1572
  function decodeInlineMcpImage(data, mimeType) {
1566
1573
  if (typeof data !== "string" || typeof mimeType !== "string" || !["image/png", "image/jpeg", "image/webp"].includes(mimeType)) {
@@ -1798,7 +1805,9 @@ function validateRetainableSessionImage(input, limits = {}) {
1798
1805
  };
1799
1806
  }
1800
1807
  function retainedScreenshotIdentity(input) {
1801
- const settlementKey = createHash3("sha256").update("opengeni:computer-screenshot:v1\0").update(input.sessionId).update("\0").update(input.turnId).update("\0").update(input.attemptId).update("\0").update(input.toolCallId).update("\0").update(input.toolOutputId).digest("hex");
1808
+ const settlementKey = createHash3("sha256").update(
1809
+ input.kind === "browser_screenshot" ? "opengeni:browser-screenshot:v1\0" : "opengeni:computer-screenshot:v1\0"
1810
+ ).update(input.sessionId).update("\0").update(input.turnId).update("\0").update(input.attemptId).update("\0").update(input.toolCallId).update("\0").update(input.toolOutputId).digest("hex");
1802
1811
  const uuidBytes = Uint8Array.from(Buffer.from(settlementKey.slice(0, 32), "hex"));
1803
1812
  uuidBytes[6] = uuidBytes[6] & 15 | 128;
1804
1813
  uuidBytes[8] = uuidBytes[8] & 63 | 128;
@@ -1812,13 +1821,14 @@ function unavailableRetainedSessionImage(input) {
1812
1821
  const identity = retainedScreenshotIdentity(input);
1813
1822
  return unavailable(identity.artifactId, input.reason);
1814
1823
  }
1815
- async function retainComputerScreenshot(input) {
1824
+ async function retainSessionScreenshot(input) {
1816
1825
  const identity = retainedScreenshotIdentity({
1817
1826
  sessionId: input.sessionId,
1818
1827
  turnId: input.turnId,
1819
1828
  attemptId: input.attemptId,
1820
1829
  toolCallId: input.output.callId,
1821
- toolOutputId: input.output.toolOutputId
1830
+ toolOutputId: input.output.toolOutputId,
1831
+ kind: input.kind
1822
1832
  });
1823
1833
  let screenshot;
1824
1834
  try {
@@ -1837,7 +1847,8 @@ async function retainComputerScreenshot(input) {
1837
1847
  const retentionExpiresAt = new Date(
1838
1848
  now.getTime() + (input.retentionMs ?? COMPUTER_SCREENSHOT_RETENTION_MS)
1839
1849
  );
1840
- const objectKey = `workspaces/${input.workspaceId}/files/${identity.artifactId}/retained/session-image.${screenshot.extension}`;
1850
+ const objectName = input.kind === "browser_screenshot" ? "browser-screenshot" : "session-image";
1851
+ const objectKey = `workspaces/${input.workspaceId}/files/${identity.artifactId}/retained/${objectName}.${screenshot.extension}`;
1841
1852
  let prepared;
1842
1853
  try {
1843
1854
  prepared = (await prepareRetainedScreenshotArtifact(input.db, {
@@ -1924,6 +1935,7 @@ async function retainComputerScreenshot(input) {
1924
1935
  return unavailable(identity.artifactId, "pending");
1925
1936
  }
1926
1937
  }
1938
+ var retainComputerScreenshot = retainSessionScreenshot;
1927
1939
  function compactRetainedScreenshotHistory(history, receiptsByCallId) {
1928
1940
  if (receiptsByCallId.size === 0) return history;
1929
1941
  return history.map((item) => {
@@ -2005,6 +2017,20 @@ async function materializeRetainedScreenshotHistory(input) {
2005
2017
  }
2006
2018
  async function materializeRetainedScreenshotHistoryWithCache(input, cache) {
2007
2019
  const now = input.now ?? /* @__PURE__ */ new Date();
2020
+ const receiptForMarker = async (marker, callId) => {
2021
+ if (!isRetainedImageMarker(marker)) return null;
2022
+ const receipt = retainedReceipt(marker.artifact);
2023
+ if (receipt) return receipt;
2024
+ if (!callId) throw new Error("Retained screenshot receipt has no tool call identity");
2025
+ const artifact = await getRetainedScreenshotArtifactForToolCall(
2026
+ input.db,
2027
+ input.workspaceId,
2028
+ input.sessionId,
2029
+ callId
2030
+ );
2031
+ if (!artifact) throw new Error("Retained screenshot receipt cannot be recovered");
2032
+ return reference(artifact);
2033
+ };
2008
2034
  const dataUrlForReceipt = async (receipt) => {
2009
2035
  let dataUrl = cache.get(receipt.artifactId);
2010
2036
  if (!dataUrl) {
@@ -2045,15 +2071,17 @@ async function materializeRetainedScreenshotHistoryWithCache(input, cache) {
2045
2071
  }
2046
2072
  return dataUrl;
2047
2073
  };
2048
- const materializeEntry = async (entry) => {
2049
- const receipt = retainedReceiptFromImageContent(entry);
2074
+ const materializeEntry = async (entry, callId) => {
2075
+ const image = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.image : null;
2076
+ const receipt = await receiptForMarker(image, callId);
2050
2077
  if (!receipt) return entry;
2051
2078
  const dataUrl = await dataUrlForReceipt(receipt);
2052
2079
  return { ...entry, image: dataUrl };
2053
2080
  };
2054
2081
  const materialized = [];
2055
2082
  for (const item of input.history) {
2056
- const directReceipt = retainedReceiptFromDirectOutput(item.output);
2083
+ const callId = historyCallId(item);
2084
+ const directReceipt = await receiptForMarker(item.output, callId);
2057
2085
  if (directReceipt) {
2058
2086
  materialized.push({
2059
2087
  ...item,
@@ -2070,7 +2098,7 @@ async function materializeRetainedScreenshotHistoryWithCache(input, cache) {
2070
2098
  let changed = false;
2071
2099
  const output = [];
2072
2100
  for (const entry of outputEntries) {
2073
- const next = await materializeEntry(entry);
2101
+ const next = await materializeEntry(entry, callId);
2074
2102
  changed ||= next !== entry;
2075
2103
  output.push(next);
2076
2104
  }
@@ -2124,12 +2152,15 @@ function reference(artifact) {
2124
2152
  if (!artifact.sessionId) {
2125
2153
  throw new Error(`Ready retained screenshot is detached: ${artifact.artifactId}`);
2126
2154
  }
2155
+ const kind = retainedSessionScreenshotKindFromObjectKey(artifact.file.objectKey);
2156
+ if (!kind) throw new Error(`Ready retained screenshot has unknown key: ${artifact.artifactId}`);
2127
2157
  const value = retainedScreenshotReferenceFromFile({
2128
2158
  ...artifact.file,
2129
2159
  sessionId: artifact.sessionId,
2130
2160
  width: artifact.width,
2131
2161
  height: artifact.height,
2132
- expiresAt: artifact.retentionExpiresAt.toISOString()
2162
+ expiresAt: artifact.retentionExpiresAt.toISOString(),
2163
+ kind
2133
2164
  });
2134
2165
  if (!value)
2135
2166
  throw new Error(`Ready retained screenshot receipt is invalid: ${artifact.artifactId}`);
@@ -2166,6 +2197,11 @@ function retainedReceiptFromImageContent(entry) {
2166
2197
  if (marker.type !== RETAINED_IMAGE_MARKER) return null;
2167
2198
  return retainedReceipt(marker.artifact);
2168
2199
  }
2200
+ function isRetainedImageMarker(value) {
2201
+ return Boolean(
2202
+ value && typeof value === "object" && !Array.isArray(value) && value.type === RETAINED_IMAGE_MARKER
2203
+ );
2204
+ }
2169
2205
  function retainedReceiptFromDirectOutput(output) {
2170
2206
  if (!output || typeof output !== "object" || Array.isArray(output)) return null;
2171
2207
  const marker = output;
@@ -2175,12 +2211,12 @@ function retainedReceiptFromDirectOutput(output) {
2175
2211
  function retainedReceipt(value) {
2176
2212
  const parsed = RetainedArtifactMetadataSchema.safeParse(value);
2177
2213
  if (!parsed.success) return null;
2178
- return parsed.data.available && parsed.data.kind !== "computer_screenshot" ? null : parsed.data;
2214
+ return parsed.data.available && parsed.data.kind !== "computer_screenshot" && parsed.data.kind !== "browser_screenshot" ? null : parsed.data;
2179
2215
  }
2180
2216
  function isInlineImageContent(entry) {
2181
2217
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
2182
2218
  const record6 = entry;
2183
- return record6.type === "input_image" && typeof record6.image === "string" && record6.image.startsWith("data:image/") || record6.type === "image" && decodeInlineMcpImage(record6.data, record6.mimeType) !== null;
2219
+ return record6.type === "input_image" && typeof record6.image === "string" && record6.image.startsWith("data:image/") || record6.type === "image" && isDeclaredInlineMcpImage(record6);
2184
2220
  }
2185
2221
  function historyCallId(item) {
2186
2222
  const value = item.callId ?? item.call_id;
@@ -2882,6 +2918,7 @@ var TurnMediaArtifacts = class {
2882
2918
  sandboxFileDownloadBackend;
2883
2919
  nativeImageGenerationRetention = null;
2884
2920
  retainedSessionImageCallIds = /* @__PURE__ */ new Set();
2921
+ retainedSessionImageKindsByCallId = /* @__PURE__ */ new Map();
2885
2922
  rememberGeneratedImageCreatedThisTurn = (receipt) => {
2886
2923
  this.generatedImageReceiptsCreatedThisTurn.set(receipt.artifact.artifactId, receipt);
2887
2924
  };
@@ -3217,7 +3254,10 @@ function pendingToolCallFromSdkEvent(event) {
3217
3254
  };
3218
3255
  }
3219
3256
  function toolCallProducesRetainableSessionImage(name) {
3220
- return name === "computer_screenshot" || name === "view_image" || name === "interaction__computer_observe";
3257
+ return name === "computer_screenshot" || name === "view_image" || name === "interaction__computer_observe" || retainableBrowserScreenshotToolCall(name);
3258
+ }
3259
+ function retainableBrowserScreenshotToolCall(name) {
3260
+ return name === "browser_screenshot" || name === "interaction__browser_screenshot" || name === "browser_observe" || name === "interaction__browser_observe" || name === "browser_act" || name === "interaction__browser_act";
3221
3261
  }
3222
3262
  function completedToolCallFromSdkEvent(event) {
3223
3263
  if (!event || typeof event !== "object") return null;
@@ -5110,7 +5150,8 @@ import {
5110
5150
  } from "@opengeni/db";
5111
5151
  import { publishDurableSessionEvents as publishDurableSessionEvents2 } from "@opengeni/events";
5112
5152
  import { maxTurnsExceededRunState } from "@opengeni/runtime";
5113
- import { CancelledFailure } from "@temporalio/activity";
5153
+ import { ApplicationFailure, CancelledFailure } from "@temporalio/activity";
5154
+ import { TurnExecutionPolicyDefinitionMismatchError } from "@opengeni/config";
5114
5155
  import {
5115
5156
  classifyCodexEncryptedArtifactRejection,
5116
5157
  classifyCodexUsageLimitError,
@@ -6853,10 +6894,11 @@ async function settleTurnFailure(deps) {
6853
6894
  return claimedResult({ status: "recovering" });
6854
6895
  }
6855
6896
  }
6856
- let failure = agentRunFailurePayload(error51, {
6897
+ const earlyDefinitionMismatch = error51 instanceof TurnExecutionPolicyDefinitionMismatchError && !attempt.modelRequestStarted && !eventing.turnStartedPublished && !!attempt.turnId && !!attempt.triggerEventId && attempt.executionGeneration > 0;
6898
+ let failure = earlyDefinitionMismatch ? { error: error51.message, code: error51.code, retryable: true } : agentRunFailurePayload(error51, {
6857
6899
  isCodexTurn: billingState.isCodexTurn
6858
6900
  });
6859
- if (failure.retryable && eventing.publish && attempt.turnId && eventing.turnStartedPublished) {
6901
+ if (attempt.turnId && (earlyDefinitionMismatch || failure.retryable && eventing.publish && eventing.turnStartedPublished)) {
6860
6902
  const nextProviderRecoveryCount = attempt.providerRecoveryCount + 1;
6861
6903
  const recoveryResult = providerRecoveryResult({
6862
6904
  failureCode: failure.code,
@@ -6865,8 +6907,10 @@ async function settleTurnFailure(deps) {
6865
6907
  });
6866
6908
  try {
6867
6909
  if (recoveryResult.status === "recovering") {
6868
- await flushRuntimeBatcher();
6869
- await historySink.reconcileConversationTruth({ requireDurable: true });
6910
+ if (!earlyDefinitionMismatch) {
6911
+ await flushRuntimeBatcher();
6912
+ await historySink.reconcileConversationTruth({ requireDurable: true });
6913
+ }
6870
6914
  const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
6871
6915
  sessionId: input.sessionId,
6872
6916
  turnId: attempt.turnId,
@@ -6894,6 +6938,16 @@ async function settleTurnFailure(deps) {
6894
6938
  return claimedResult(recoveryResult);
6895
6939
  }
6896
6940
  failure = providerRecoveryExhaustedFailure(failure, recoveryResult);
6941
+ if (earlyDefinitionMismatch) {
6942
+ control.activityStatus = "failed";
6943
+ control.turnMetricOutcome = "failed";
6944
+ control.activityError = error51;
6945
+ throw ApplicationFailure.create({
6946
+ message: `${error51.message}. Automatic same-turn configuration recovery exhausted after ${recoveryResult.providerRecoveryCount} retries.`,
6947
+ type: "TurnExecutionPolicyDefinitionMismatchError",
6948
+ nonRetryable: true
6949
+ });
6950
+ }
6897
6951
  } catch (recoveryError) {
6898
6952
  const escaped = recoveryResult.status === "recovering" ? escapedMcpTimeoutRecoveryFailure({
6899
6953
  failureCode: failure.code,
@@ -7017,7 +7071,8 @@ import {
7017
7071
  import {
7018
7072
  EmptyCompactionSummaryError,
7019
7073
  REMOTE_COMPACTION_V2_IMPLEMENTATION,
7020
- SUMMARY_BUFFER_TOKENS,
7074
+ compactionSummaryOutputTokens,
7075
+ buildSummaryItem,
7021
7076
  buildCompactionReplacementHistory,
7022
7077
  buildRemoteV2ReplacementHistory,
7023
7078
  compactionThresholdTokens,
@@ -7032,7 +7087,7 @@ import {
7032
7087
  } from "@opengeni/runtime";
7033
7088
  import { contextInputBudgetTokens } from "@opengeni/config";
7034
7089
  async function maybeCompactContext(db, settings, scope, lastInputTokens, summarize = (s, m) => summarizeForCompaction(s, m, {
7035
- maxOutputTokens: SUMMARY_BUFFER_TOKENS
7090
+ maxOutputTokens: compactionSummaryOutputTokens(s.contextWindowTokens)
7036
7091
  }), options = {}) {
7037
7092
  if (options.codexCompactionMode === "remote_v2" && options.isCodexSubscriptionTurn !== true) {
7038
7093
  throw new Error(
@@ -7263,12 +7318,23 @@ async function compactContextPortable(db, settings, scope, canonicalItems, items
7263
7318
  const summarized = await summarizeWithCodexOverflowTrimming(summarize, settings, items);
7264
7319
  const summaryBody = summarized.summaryBody;
7265
7320
  const retainedTokens = await retentionTokenCounts(canonicalItems, projectForWire);
7321
+ const outputReserve = compactionSummaryOutputTokens(settings.contextWindowTokens);
7322
+ const structuralBudget = Math.min(
7323
+ contextInputBudgetTokens(settings) || settings.contextWindowTokens - outputReserve,
7324
+ settings.contextWindowTokens - outputReserve
7325
+ );
7326
+ const prefixTokens = Math.max(0, Math.ceil(summarize.estimatePrefixTokens?.() ?? 0));
7327
+ const summaryTokens = estimateTokens([buildSummaryItem(summaryBody)]);
7266
7328
  const replacementHistory = buildCompactionReplacementHistory(
7267
7329
  canonicalItems,
7268
7330
  summaryBody,
7269
- (item) => retainedTokens.get(item) ?? estimateTokens([item])
7331
+ (item) => retainedTokens.get(item) ?? estimateTokens([item]),
7332
+ Math.min(outputReserve, Math.max(0, structuralBudget - prefixTokens - summaryTokens))
7270
7333
  );
7271
7334
  const estimatedTokensAfter = estimateTokens(await projectForWire(replacementHistory));
7335
+ if (estimatedTokensAfter + prefixTokens > structuralBudget) {
7336
+ return await settleSkippedAfterStart(db, scope, options, "replacement_exceeds_model_budget");
7337
+ }
7272
7338
  const replacementFingerprint = compactionReplacementFingerprint(replacementHistory);
7273
7339
  const previousReplacementFingerprint = latestCompactionReplacementFingerprint(canonicalItems);
7274
7340
  const summaryIndex = replacementHistory.length - (readReasoningConfiguration(replacementHistory.at(-1) ?? {}) ? 2 : 1);
@@ -7321,14 +7387,27 @@ async function compactContextPortable(db, settings, scope, canonicalItems, items
7321
7387
  };
7322
7388
  }
7323
7389
  async function summarizeWithCodexOverflowTrimming(summarize, settings, activeHistory) {
7324
- const summaryAwareBudget = Math.max(0, settings.contextWindowTokens - SUMMARY_BUFFER_TOKENS);
7390
+ const summaryAwareBudget = Math.max(
7391
+ 0,
7392
+ settings.contextWindowTokens - compactionSummaryOutputTokens(settings.contextWindowTokens)
7393
+ );
7325
7394
  const configuredInputBudget = contextInputBudgetTokens(settings);
7326
7395
  const structuralBudget = Math.min(
7327
7396
  configuredInputBudget > 0 ? configuredInputBudget : summaryAwareBudget,
7328
7397
  summaryAwareBudget
7329
7398
  );
7330
- const initialBudget = Math.floor(structuralBudget * 0.85);
7399
+ const prefixTokens = Math.max(0, Math.ceil(summarize.estimatePrefixTokens?.() ?? 0));
7400
+ const initialBudget = Math.max(0, structuralBudget - prefixTokens);
7331
7401
  let preparation = prepareCompactionPromptInput(activeHistory, initialBudget);
7402
+ const requireHistory = () => {
7403
+ if (activeHistory.length > 0 && preparation.input.length === 1) {
7404
+ throw new EmptyCompactionSummaryError({
7405
+ stage: "portable_input_budget",
7406
+ reason: "no_history_fit"
7407
+ });
7408
+ }
7409
+ };
7410
+ requireHistory();
7332
7411
  try {
7333
7412
  return {
7334
7413
  summaryBody: await summarize(settings, preparation.input),
@@ -7338,9 +7417,10 @@ async function summarizeWithCodexOverflowTrimming(summarize, settings, activeHis
7338
7417
  } catch (error51) {
7339
7418
  if (!isContextWindowExceeded(error51)) throw error51;
7340
7419
  const retryBudget = Math.floor(
7341
- Math.min(initialBudget * 0.5, preparation.estimatedInputTokens * 0.5)
7420
+ Math.min(initialBudget * 0.4, preparation.estimatedInputTokens * 0.4)
7342
7421
  );
7343
7422
  preparation = prepareCompactionPromptInput(activeHistory, retryBudget);
7423
+ requireHistory();
7344
7424
  return {
7345
7425
  summaryBody: await summarize(settings, preparation.input),
7346
7426
  preparation,
@@ -7708,9 +7788,11 @@ import {
7708
7788
 
7709
7789
  // src/activities/agent-turn/session-title.ts
7710
7790
  import { hasPermission } from "@opengeni/core";
7791
+ import { isManagedOpenRouterFreeRoute } from "@opengeni/config";
7711
7792
  import {
7712
7793
  AUTOMATIC_SESSION_TITLE_FALLBACK,
7713
- DEFAULT_FIRST_PARTY_MCP_PERMISSIONS
7794
+ DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
7795
+ ReasoningEffort
7714
7796
  } from "@opengeni/contracts";
7715
7797
  var SESSION_TITLE_MODEL_TOOL_NAME = "opengeni__set_session_title";
7716
7798
  var SESSION_TITLE_DESCRIPTION = "Set this session's display title to a concise 3-7 word topic label. Use a stable noun phrase about the actual task or subject, never a quote/prefix of a prompt, greeting, request boilerplate, URL, identifier, credential, token, or other sensitive value. Call once on a new session, then only when the topic materially changes. Never call it as routine setup after a continuation, resume, or interruption, or merely to reassert the same title. A human-set title cannot be replaced.";
@@ -7722,10 +7804,14 @@ function shouldRequestMissingSessionTitle(input) {
7722
7804
  const permissions = input.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS;
7723
7805
  return hasPermission([...permissions], "sessions:control");
7724
7806
  }
7807
+ function routeAllowsSessionTitleRequests(resolvedModel) {
7808
+ return !resolvedModel || !isManagedOpenRouterFreeRoute(resolvedModel);
7809
+ }
7725
7810
  function sessionTitleToolPlan(input) {
7726
7811
  const titleToolAvailable = input.shouldRequestTitle && input.tools.some((tool) => tool.kind === "mcp" && tool.id === "opengeni");
7727
- const generateTitleInParallel = titleToolAvailable && input.parallelGenerationAvailable;
7728
- const promoteTitleTool = titleToolAvailable && !generateTitleInParallel;
7812
+ const titleRequestAllowed = titleToolAvailable && input.routeAllowsTitleRequests;
7813
+ const generateTitleInParallel = titleRequestAllowed && input.parallelGenerationAvailable;
7814
+ const promoteTitleTool = titleRequestAllowed && !generateTitleInParallel;
7729
7815
  return {
7730
7816
  promoteTitleTool,
7731
7817
  generateTitleInParallel,
@@ -7734,6 +7820,31 @@ function sessionTitleToolPlan(input) {
7734
7820
  };
7735
7821
  }
7736
7822
  var PARALLEL_SESSION_TITLE_TIMEOUT_MS = 15e3;
7823
+ function sessionTitleReasoningEffort(capabilities) {
7824
+ const reasoning = capabilities?.reasoning;
7825
+ if (!reasoning?.runnable) return void 0;
7826
+ const order = ReasoningEffort.options;
7827
+ let lowest;
7828
+ for (const effort of reasoning.efforts) {
7829
+ if (!lowest || order.indexOf(effort) < order.indexOf(lowest)) lowest = effort;
7830
+ }
7831
+ return lowest;
7832
+ }
7833
+ function sessionTitleGenerationOptions(input) {
7834
+ const { resolvedModel, serviceTier } = input;
7835
+ const reasoningEffort = sessionTitleReasoningEffort(resolvedModel?.configured.capabilities);
7836
+ return {
7837
+ ...resolvedModel ? {
7838
+ client: resolvedModel.client,
7839
+ provider: resolvedModel.provider,
7840
+ model: resolvedModel.model
7841
+ } : {},
7842
+ modelName: input.modelName,
7843
+ ...serviceTier ? { serviceTier } : {},
7844
+ ...reasoningEffort ? { reasoningEffort } : {},
7845
+ signal: input.signal
7846
+ };
7847
+ }
7737
7848
  function startParallelSessionTitleGeneration(input) {
7738
7849
  const cancellationController = new AbortController();
7739
7850
  const timeoutSignal = AbortSignal.timeout(input.timeoutMs ?? PARALLEL_SESSION_TITLE_TIMEOUT_MS);
@@ -8695,6 +8806,12 @@ async function runTurnStreamAttempt(deps) {
8695
8806
  currentToolBatchCallIds.add(pendingToolCall.callId);
8696
8807
  if (toolCallProducesRetainableSessionImage(pendingToolCall.callName)) {
8697
8808
  media.retainedSessionImageCallIds.add(pendingToolCall.callId);
8809
+ if (retainableBrowserScreenshotToolCall(pendingToolCall.callName)) {
8810
+ media.retainedSessionImageKindsByCallId.set(
8811
+ pendingToolCall.callId,
8812
+ "browser_screenshot"
8813
+ );
8814
+ }
8698
8815
  }
8699
8816
  }
8700
8817
  const completedToolCall = completedToolCallFromSdkEvent(durableSdkEvent);
@@ -8708,7 +8825,8 @@ async function runTurnStreamAttempt(deps) {
8708
8825
  attemptId: input.attemptId,
8709
8826
  toolCallId: completedToolCall.callId,
8710
8827
  toolOutputId: completedToolCall.callId,
8711
- reason: "unsupported"
8828
+ reason: "unsupported",
8829
+ kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId)
8712
8830
  });
8713
8831
  media.retainedScreenshotReceiptsByCallId.set(
8714
8832
  completedToolCall.callId,
@@ -8722,13 +8840,14 @@ async function runTurnStreamAttempt(deps) {
8722
8840
  attemptId: input.attemptId,
8723
8841
  toolCallId: typedScreenshot.callId,
8724
8842
  toolOutputId: typedScreenshot.toolOutputId,
8725
- reason: "pending"
8843
+ reason: "pending",
8844
+ kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId)
8726
8845
  });
8727
8846
  media.retainedScreenshotReceiptsByCallId.set(
8728
8847
  completedToolCall.callId,
8729
8848
  retainedScreenshotMetadata
8730
8849
  );
8731
- retainedScreenshotMetadata = await retainComputerScreenshot({
8850
+ retainedScreenshotMetadata = await retainSessionScreenshot({
8732
8851
  db,
8733
8852
  objectStorage,
8734
8853
  accountId: input.accountId,
@@ -8736,7 +8855,8 @@ async function runTurnStreamAttempt(deps) {
8736
8855
  sessionId: input.sessionId,
8737
8856
  turnId: activeTurnId,
8738
8857
  attemptId: input.attemptId,
8739
- output: typedScreenshot
8858
+ output: typedScreenshot,
8859
+ kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId)
8740
8860
  });
8741
8861
  media.retainedScreenshotReceiptsByCallId.set(
8742
8862
  completedToolCall.callId,
@@ -8855,6 +8975,7 @@ async function runTurnStreamAttempt(deps) {
8855
8975
  }
8856
8976
  for (const callId of stableToolCallIdsToClear) {
8857
8977
  media.retainedSessionImageCallIds.delete(callId);
8978
+ media.retainedSessionImageKindsByCallId.delete(callId);
8858
8979
  }
8859
8980
  }
8860
8981
  }
@@ -9223,16 +9344,16 @@ async function runTurnStreamAttempt(deps) {
9223
9344
  parallelSessionTitle = startParallelSessionTitleGeneration({
9224
9345
  signal: runtimeCancellationSignal,
9225
9346
  generate: async (signal) => await withSessionTitleProviderRequestContext(
9226
- () => runtime.generateSessionTitle(runSettings, sessionTitlePrompt, {
9227
- ...resolvedModel ? {
9228
- client: resolvedModel.client,
9229
- provider: resolvedModel.provider,
9230
- model: resolvedModel.model
9231
- } : {},
9232
- modelName: turnExecutionPolicy.upstreamModelId,
9233
- ...serviceTier ? { serviceTier } : {},
9234
- signal
9235
- })
9347
+ () => runtime.generateSessionTitle(
9348
+ runSettings,
9349
+ sessionTitlePrompt,
9350
+ sessionTitleGenerationOptions({
9351
+ resolvedModel,
9352
+ modelName: turnExecutionPolicy.upstreamModelId,
9353
+ serviceTier,
9354
+ signal
9355
+ })
9356
+ )
9236
9357
  ),
9237
9358
  onError: (error51) => {
9238
9359
  observability.warn("parallel session title generation failed", {
@@ -10245,6 +10366,24 @@ function safeErrorName(error51) {
10245
10366
 
10246
10367
  // src/activities/agent-turn/codex-capacity.ts
10247
10368
  import { randomUUID as randomUUID2 } from "crypto";
10369
+ import { createLogThrottle } from "@opengeni/observability";
10370
+ var CODEX_POOL_LOW_WARNING_INTERVAL_MS = 10 * 6e4;
10371
+ var codexPoolLowWarningThrottle = createLogThrottle({
10372
+ intervalMs: CODEX_POOL_LOW_WARNING_INTERVAL_MS,
10373
+ maxKeys: 1024
10374
+ });
10375
+ function warnCodexPoolLow(observability, input, throttle = codexPoolLowWarningThrottle) {
10376
+ const admission = throttle.admit(`${input.workspaceKey}:${input.depth}`);
10377
+ if (!admission) return;
10378
+ observability.warn("Codex eligible credential pool is low", {
10379
+ workspaceId: input.workspaceId,
10380
+ eligibleCount: input.eligibleCount,
10381
+ connectedCount: input.connectedCount,
10382
+ depth: input.depth,
10383
+ reason: input.depth === "zero" ? "eligible_pool_zero" : "eligible_pool_one",
10384
+ ...admission.suppressedCount > 0 ? { suppressedCount: admission.suppressedCount } : {}
10385
+ });
10386
+ }
10248
10387
  async function selectCodexTurnCapacity(deps) {
10249
10388
  const {
10250
10389
  input,
@@ -10510,11 +10649,12 @@ async function selectCodexTurnCapacity(deps) {
10510
10649
  help: "Alert signal emitted when the eligible Codex pool is zero or one.",
10511
10650
  labels: { workspace_key: codexWorkspaceKey, depth: poolDepth }
10512
10651
  });
10513
- observability.warn("Codex eligible credential pool is low", {
10652
+ warnCodexPoolLow(observability, {
10653
+ workspaceKey: codexWorkspaceKey,
10514
10654
  workspaceId: input.workspaceId,
10515
10655
  eligibleCount,
10516
10656
  connectedCount: leased.accounts.length,
10517
- depth: poolDepth
10657
+ depth: poolDepth === "zero" ? "zero" : "one"
10518
10658
  });
10519
10659
  }
10520
10660
  if (providerTurn.effectiveCodexCredentialId === null && leased.accounts.length > 0 && (rotationDecision.kind === "allocatorDisabled" || leased.accounts.every((account) => !account.allocatorEnabled)) && turnId) {
@@ -10885,7 +11025,10 @@ import {
10885
11025
  hasActiveWorkspaceInstructionPolicy,
10886
11026
  renderWorkspaceGovernanceContext
10887
11027
  } from "@opengeni/runtime";
10888
- import { settingsWithResolvedModelContext } from "@opengeni/config";
11028
+ import {
11029
+ codeSearchDeploymentPolicy,
11030
+ settingsWithResolvedModelContext
11031
+ } from "@opengeni/config";
10889
11032
  import { projectReasoningConfigurations, supportsReasoningConfiguration } from "@opengeni/codex";
10890
11033
  import { resolveRigProviderImageForRun } from "@opengeni/core";
10891
11034
  import { retryWhileMissing as retryWhileMissing3 } from "@opengeni/storage";
@@ -10894,6 +11037,7 @@ import {
10894
11037
  evaluateWorkspaceModelPolicy,
10895
11038
  resolveWorkspaceAgentHumanInputEnabled as resolveWorkspaceAgentHumanInputEnabled2
10896
11039
  } from "@opengeni/contracts";
11040
+ import { codeSearchEnabledForTurn } from "@opengeni/contracts/code-search";
10897
11041
 
10898
11042
  // src/activities/agent-turn/tool-policy.ts
10899
11043
  import {
@@ -11266,6 +11410,11 @@ async function prepareGovernanceAndModel(deps) {
11266
11410
  workspaceRefs.rigVersionId = session.rigVersionId ?? "";
11267
11411
  if (!workspace) throw new Error(`Workspace not found: ${input.workspaceId}`);
11268
11412
  const agentHumanInputEnabled = resolveWorkspaceAgentHumanInputEnabled2(workspace.settings);
11413
+ const codeSearchEnabled = codeSearchEnabledForTurn(
11414
+ session.codeSearchEnabled,
11415
+ workspace.settings,
11416
+ codeSearchDeploymentPolicy(capabilitySettings)
11417
+ );
11269
11418
  const contextSelection = await resolveCompanyBrainContextSelection(db, governanceClaims);
11270
11419
  const workspaceAgentInstructions = contextSelection.legacyWorkspaceInstructions;
11271
11420
  const memoryPromptMode = contextSelection.receipt.memoryPromptMode;
@@ -11421,6 +11570,7 @@ async function prepareGovernanceAndModel(deps) {
11421
11570
  rigVersion,
11422
11571
  rigName,
11423
11572
  agentHumanInputEnabled,
11573
+ codeSearchEnabled,
11424
11574
  workspaceAgentInstructions,
11425
11575
  workspaceGovernance,
11426
11576
  structuredWorkspacePolicyActive,
@@ -11456,7 +11606,8 @@ import {
11456
11606
  compactionThresholdTokens as compactionThresholdTokens2,
11457
11607
  CompactionNeededError,
11458
11608
  compactionProviderRejection as compactionProviderRejection2,
11459
- SUMMARY_BUFFER_TOKENS as SUMMARY_BUFFER_TOKENS2
11609
+ estimateSerializedValueTokens,
11610
+ compactionSummaryOutputTokens as compactionSummaryOutputTokens2
11460
11611
  } from "@opengeni/runtime";
11461
11612
  async function prepareCompaction(deps) {
11462
11613
  const {
@@ -11495,6 +11646,11 @@ async function prepareCompaction(deps) {
11495
11646
  const remotePrefix = {
11496
11647
  agent: null
11497
11648
  };
11649
+ const portableResponsesNeedsAgentPrefix = resolvedModel?.provider.api === "responses" && !(billingState.isCodexTurn && session.codexCompactionMode === "remote_v2");
11650
+ const preparedPortableRequest = () => {
11651
+ if (!remotePrefix.agent) throw new Error("Compaction agent is unavailable");
11652
+ return preparedCompactionRequest(remotePrefix.agent);
11653
+ };
11498
11654
  const promptCacheKey = acceptsPromptCacheKeyForTurn(resolvedModel) ? input.sessionId : void 0;
11499
11655
  const compactionUsageState = createCompactionModelUsageEventState(claimedModelUsageSourceKeys);
11500
11656
  const recordCompactionUsage = async (usage) => {
@@ -11526,24 +11682,37 @@ async function prepareCompaction(deps) {
11526
11682
  contextContributions: eventing.companyBrainContextContributions
11527
11683
  });
11528
11684
  };
11529
- const compactionSummarizerFor = (systemInstructions) => resolvedModel ? (s, m) => withProviderRequestContext(
11530
- () => summarizeContextForCompaction(s, m, {
11531
- client: resolvedModel.client,
11532
- provider: resolvedModel.provider,
11533
- api: resolvedModel.provider.api,
11685
+ const compactionSummarizerFor = (systemInstructions) => {
11686
+ const summarize = resolvedModel ? (s, m) => withProviderRequestContext(
11687
+ () => summarizeContextForCompaction(s, m, {
11688
+ client: resolvedModel.client,
11689
+ provider: resolvedModel.provider,
11690
+ api: resolvedModel.provider.api,
11691
+ model: turnExecutionPolicy.upstreamModelId,
11692
+ maxOutputTokens: compactionSummaryOutputTokens2(s.contextWindowTokens),
11693
+ ...cancellationSignal ? { signal: cancellationSignal } : {},
11694
+ onUsage: recordCompactionUsage,
11695
+ ...systemInstructions ? { systemInstructions } : {},
11696
+ ...promptCacheKey ? { promptCacheKey } : {},
11697
+ ...portableResponsesNeedsAgentPrefix ? { preparedRequest: preparedPortableRequest() } : {}
11698
+ })
11699
+ ) : (s, m) => summarizeContextForCompaction(s, m, {
11534
11700
  model: turnExecutionPolicy.upstreamModelId,
11535
- maxOutputTokens: SUMMARY_BUFFER_TOKENS2,
11701
+ maxOutputTokens: compactionSummaryOutputTokens2(s.contextWindowTokens),
11702
+ ...cancellationSignal ? { signal: cancellationSignal } : {},
11536
11703
  onUsage: recordCompactionUsage,
11537
11704
  ...systemInstructions ? { systemInstructions } : {},
11538
11705
  ...promptCacheKey ? { promptCacheKey } : {}
11539
- })
11540
- ) : (s, m) => summarizeContextForCompaction(s, m, {
11541
- model: turnExecutionPolicy.upstreamModelId,
11542
- maxOutputTokens: SUMMARY_BUFFER_TOKENS2,
11543
- onUsage: recordCompactionUsage,
11544
- ...systemInstructions ? { systemInstructions } : {},
11545
- ...promptCacheKey ? { promptCacheKey } : {}
11546
- });
11706
+ });
11707
+ summarize.estimatePrefixTokens = () => {
11708
+ if (resolvedModel?.provider.api === "chat") {
11709
+ return estimateSerializedValueTokens(systemInstructions ?? "");
11710
+ }
11711
+ const prepared = portableResponsesNeedsAgentPrefix ? preparedPortableRequest() : null;
11712
+ return estimateSerializedValueTokens(prepared?.systemInstructions ?? systemInstructions ?? "") + (prepared ? estimateSerializedValueTokens(prepared.tools) : 0);
11713
+ };
11714
+ return summarize;
11715
+ };
11547
11716
  const remoteCompactionRequester = resolvedModel && billingState.isCodexTurn ? (s, m) => withCodexRemoteCompaction(async () => {
11548
11717
  if (!remotePrefix.agent) throw new Error("Compaction agent is unavailable");
11549
11718
  const preparedRequest = preparedCompactionRequest(remotePrefix.agent);
@@ -11606,7 +11775,7 @@ async function prepareCompaction(deps) {
11606
11775
  control.activityStatus = "idle";
11607
11776
  return claimedResult({ status: "idle" });
11608
11777
  };
11609
- if (compactionOnlyTurn && !remoteV2CompactionNeedsAgentPrefix) {
11778
+ if (compactionOnlyTurn && !remoteV2CompactionNeedsAgentPrefix && !portableResponsesNeedsAgentPrefix) {
11610
11779
  const compactionInstructions = appendWorkspaceMemory(
11611
11780
  appendSessionInstructions(
11612
11781
  appendWorkspaceGovernance(
@@ -11748,6 +11917,7 @@ async function runPostAgentCompaction(deps) {
11748
11917
  claimedResult,
11749
11918
  turn,
11750
11919
  session,
11920
+ resolvedModel,
11751
11921
  remotePrefix,
11752
11922
  remoteCompactionRequester,
11753
11923
  publishCompactionLiveEvents,
@@ -11761,16 +11931,17 @@ async function runPostAgentCompaction(deps) {
11761
11931
  agent
11762
11932
  } = deps;
11763
11933
  const agentInstructions = typeof agent.instructions === "string" ? agent.instructions : "";
11934
+ const preparedPortable = resolvedModel?.provider.api === "responses" && !(remoteCompactionRequester && session.codexCompactionMode === "remote_v2");
11764
11935
  const compactSummarizer = compactionSummarizerFor(
11765
11936
  agentInstructions.trim() ? agentInstructions : void 0
11766
11937
  );
11767
- if (remoteCompactionRequester) {
11938
+ if (remoteCompactionRequester || preparedPortable) {
11768
11939
  remotePrefix.agent = agent;
11769
11940
  }
11770
11941
  if (compactionOnlyTurn) {
11771
11942
  const requested = await isSessionCompactionRequested2(db, input.workspaceId, input.sessionId);
11772
11943
  let outcome = null;
11773
- if (requested && remoteCompactionRequester && session.codexCompactionMode === "remote_v2") {
11944
+ if (requested && (preparedPortable || remoteCompactionRequester && session.codexCompactionMode === "remote_v2")) {
11774
11945
  queuePreparedCompaction(
11775
11946
  agent,
11776
11947
  new CompactionNeededError({
@@ -11880,7 +12051,7 @@ async function runPostAgentCompaction(deps) {
11880
12051
  control.activityStatus = "idle";
11881
12052
  return { exit: claimedResult({ status: "idle" }) };
11882
12053
  }
11883
- if (remoteCompactionRequester && session.codexCompactionMode === "remote_v2") {
12054
+ if (preparedPortable || remoteCompactionRequester && session.codexCompactionMode === "remote_v2") {
11884
12055
  const forced = await isSessionCompactionRequested2(db, input.workspaceId, input.sessionId);
11885
12056
  const thresholdTokens = compactionThresholdTokens2(eventing.modelRunSettings);
11886
12057
  if (forced || (attempt.triggerType === "user.message" || attempt.triggerType === "system.update.delivered") && (session.lastInputTokens ?? 0) >= thresholdTokens) {
@@ -12004,7 +12175,7 @@ async function runPostAgentCompaction(deps) {
12004
12175
  import {
12005
12176
  advanceWorkspaceGeneration,
12006
12177
  verifyWorkspaceMutationSettlement,
12007
- heartbeatLeaseHolder,
12178
+ heartbeatLeaseHolderStatus,
12008
12179
  readLease as readLease2,
12009
12180
  accrueWarmSeconds,
12010
12181
  SandboxWorkspaceMutationFencedError
@@ -12312,21 +12483,29 @@ function createSandboxTurnRuntime(deps) {
12312
12483
  const heartbeatEpoch = sandbox.leaseEpoch;
12313
12484
  const heartbeatHolderId = sandboxState.sandboxHolderId;
12314
12485
  const heartbeatGroupId = sandboxState.sandboxGroupId;
12315
- const warmRate = sandboxWarmRateMicrosPerSecond(
12486
+ const warmRate = settings.sandboxWarmBillingMode === "usage_only" ? 0 : sandboxWarmRateMicrosPerSecond(
12316
12487
  settings,
12317
12488
  warmBackend ?? sandbox.established.backendId
12318
12489
  );
12319
12490
  sandboxState.leaseHeartbeatTimer = setInterval(() => {
12320
- void heartbeatLeaseHolder(db, {
12491
+ void heartbeatLeaseHolderStatus(db, {
12321
12492
  accountId: input.accountId,
12322
12493
  workspaceId: input.workspaceId,
12323
12494
  sandboxGroupId: heartbeatGroupId,
12324
12495
  kind: "turn",
12325
12496
  holderId: heartbeatHolderId,
12326
12497
  leaseTtlMs: settings.sandboxLeaseTtlMs,
12327
- expectedEpoch: heartbeatEpoch
12328
- }).then(async (alive) => {
12329
- if (alive) return;
12498
+ expectedEpoch: heartbeatEpoch,
12499
+ billingMode: settings.sandboxWarmBillingMode
12500
+ }).then(async (status) => {
12501
+ if (status.fence === "funding") {
12502
+ stopLeaseHeartbeat();
12503
+ sandboxRotationController.abort(
12504
+ new Error("Insufficient OpenGeni credits to extend paid sandbox compute")
12505
+ );
12506
+ return;
12507
+ }
12508
+ if (status.leaseExtended) return;
12330
12509
  const rotation = await beginRotationPreemption(sandbox, heartbeatEpoch, heartbeatGroupId);
12331
12510
  if (rotation === "not_rotating") {
12332
12511
  stopLeaseHeartbeat();
@@ -12338,8 +12517,13 @@ function createSandboxTurnRuntime(deps) {
12338
12517
  sandboxGroupId: heartbeatGroupId,
12339
12518
  expectedEpoch: heartbeatEpoch,
12340
12519
  warmRateMicrosPerSecond: warmRate,
12520
+ billingMode: settings.sandboxWarmBillingMode,
12341
12521
  subjectId: input.sessionId
12342
- }).then((result) => recordCreditMicros(observability, "usage", result.costMicros)).catch(() => void 0);
12522
+ }).then((result) => {
12523
+ if (settings.sandboxWarmBillingMode === "credits") {
12524
+ recordCreditMicros(observability, "usage", result.costMicros);
12525
+ }
12526
+ }).catch(() => void 0);
12343
12527
  const snapshotSession = sandboxState.setupBoxSession;
12344
12528
  const snapshotTurnId = attempt.turnId;
12345
12529
  if (snapshotSession && snapshotTurnId && shouldStartPeriodicWorkspaceSnapshot({
@@ -12678,6 +12862,7 @@ async function resolveSandboxRoute(deps) {
12678
12862
  };
12679
12863
  }
12680
12864
  async function establishTurnSandbox(deps) {
12865
+ const freshSandboxReadinessReplacementBudget = createFreshSandboxReadinessReplacementBudget();
12681
12866
  const {
12682
12867
  input,
12683
12868
  settings,
@@ -12913,6 +13098,8 @@ async function establishTurnSandbox(deps) {
12913
13098
  logicalFallbackSettings: logicalSandboxSettings,
12914
13099
  cancellationSignal: sandboxResumeSignal,
12915
13100
  sandboxMetrics: runtimeMetricsHooksForObservability(observability),
13101
+ observability,
13102
+ freshSandboxReadinessReplacementBudget,
12916
13103
  onSandboxLost: publishSandboxLost,
12917
13104
  objectStorage
12918
13105
  },
@@ -12984,6 +13171,8 @@ async function establishTurnSandbox(deps) {
12984
13171
  logicalFallbackSettings: logicalSandboxSettings,
12985
13172
  cancellationSignal: sandboxResumeSignal,
12986
13173
  sandboxMetrics: runtimeMetricsHooksForObservability(observability),
13174
+ observability,
13175
+ freshSandboxReadinessReplacementBudget,
12987
13176
  onSandboxLost: publishSandboxLost,
12988
13177
  objectStorage
12989
13178
  },
@@ -14696,6 +14885,7 @@ import {
14696
14885
  organizationModelProviderConnectionActiveForWorkspace,
14697
14886
  persistAttemptToolCatalog,
14698
14887
  prepareConnectorActionApproval,
14888
+ recordUsageEvent as recordUsageEvent4,
14699
14889
  previewConnectorActionApproval,
14700
14890
  namedSubjectHasLiveWorkspaceAuthority as namedSubjectHasLiveWorkspaceAuthority2,
14701
14891
  updateSessionTitleWithEvent as updateSessionTitleWithEvent2,
@@ -31415,6 +31605,205 @@ function createListModelsAttemptToolDefinition(input) {
31415
31605
  };
31416
31606
  }
31417
31607
 
31608
+ // src/activities/agent-turn/code-search.ts
31609
+ import { usableJevApiKey } from "@opengeni/config";
31610
+ import {
31611
+ CODE_SEARCH_TOOL_DESCRIPTION,
31612
+ CODE_SEARCH_TOOL_NAME,
31613
+ CodeSearchArgumentError,
31614
+ CodeSearchRipgrepMissingError,
31615
+ CodeSearchWorkspaceError,
31616
+ JevCircuitBreaker,
31617
+ JevClient,
31618
+ JevRequestError,
31619
+ JevUnavailableError,
31620
+ codeSearchInputSchema,
31621
+ parseCodeSearchArguments,
31622
+ renderCodeSearchError,
31623
+ runCodeSearch
31624
+ } from "@opengeni/jev";
31625
+ import {
31626
+ ChannelANotFoundError,
31627
+ ChannelAUnavailableError,
31628
+ ChannelAUnsupportedError,
31629
+ ChannelAValidationError,
31630
+ isWindowsConnectedMachinePath
31631
+ } from "@opengeni/runtime/sandbox";
31632
+ var codeSearchCircuitBreaker = new JevCircuitBreaker();
31633
+ var CODE_SEARCH_RIPGREP_MAX_BYTES = 32 * 1024 * 1024;
31634
+ function codeSearchWorkspaceFromChannel(channel) {
31635
+ return {
31636
+ ripgrep: async (args, options) => {
31637
+ options.signal?.throwIfAborted();
31638
+ const outcome = await channel.codeSearchRipgrep(args, {
31639
+ timeoutMs: options.timeoutMs,
31640
+ maxBytes: CODE_SEARCH_RIPGREP_MAX_BYTES
31641
+ });
31642
+ if (!outcome.available) throw new CodeSearchRipgrepMissingError();
31643
+ return {
31644
+ stdout: outcome.stdout,
31645
+ exitCode: outcome.exitCode,
31646
+ truncated: outcome.truncated,
31647
+ timedOut: outcome.timedOut
31648
+ };
31649
+ },
31650
+ readText: async (path, options) => {
31651
+ options.signal?.throwIfAborted();
31652
+ try {
31653
+ const read = await channel.fsRead({ path, encoding: "utf8", maxBytes: options.maxBytes });
31654
+ return { text: read.content, truncated: read.truncated, binary: read.isBinary };
31655
+ } catch (error51) {
31656
+ if (error51 instanceof ChannelANotFoundError) return null;
31657
+ throw error51;
31658
+ }
31659
+ },
31660
+ pathKinds: async (paths, options) => {
31661
+ options.signal?.throwIfAborted();
31662
+ return await channel.codeSearchPathKinds(paths);
31663
+ }
31664
+ };
31665
+ }
31666
+ function textResult(text, isError) {
31667
+ return { isError, content: [{ type: "text", text }] };
31668
+ }
31669
+ function createCodeSearchAttemptToolDefinition(input) {
31670
+ const breaker = input.breaker ?? codeSearchCircuitBreaker;
31671
+ const jev = new JevClient({
31672
+ apiKey: input.apiKey,
31673
+ baseUrl: input.settings.jevBaseUrl,
31674
+ model: input.settings.jevModel,
31675
+ timeoutMs: input.settings.jevRequestTimeoutMs,
31676
+ ...input.fetch ? { fetch: input.fetch } : {}
31677
+ });
31678
+ return {
31679
+ identity: { serverId: "opengeni", toolName: CODE_SEARCH_TOOL_NAME },
31680
+ modelName: CODE_SEARCH_TOOL_NAME,
31681
+ codemodePath: ["opengeni", CODE_SEARCH_TOOL_NAME],
31682
+ title: "Search code",
31683
+ description: CODE_SEARCH_TOOL_DESCRIPTION,
31684
+ inputSchema: codeSearchInputSchema,
31685
+ annotations: {
31686
+ title: "Search code",
31687
+ readOnlyHint: true,
31688
+ destructiveHint: false,
31689
+ idempotentHint: true,
31690
+ openWorldHint: false
31691
+ },
31692
+ source: "opengeni",
31693
+ approval: "none",
31694
+ execute: async (args, context) => {
31695
+ const startedAt = performance.now();
31696
+ let outcome = "failed";
31697
+ let jevRequests = 0;
31698
+ let jevCostUsd = 0;
31699
+ let lease = null;
31700
+ try {
31701
+ const request = parseCodeSearchArguments(args);
31702
+ lease = breaker.tryAcquire(Date.now());
31703
+ if (!lease) {
31704
+ outcome = "breaker_open";
31705
+ return textResult(
31706
+ renderCodeSearchError(
31707
+ new JevUnavailableError("Jev is not responding; calls are paused for a few minutes")
31708
+ ),
31709
+ true
31710
+ );
31711
+ }
31712
+ const workspace = await input.workspace();
31713
+ const result = await runCodeSearch({
31714
+ ...request,
31715
+ workspace,
31716
+ jev,
31717
+ ...context.signal ? { signal: context.signal } : {}
31718
+ });
31719
+ const held = lease;
31720
+ lease = null;
31721
+ if (result.statusCheckError instanceof JevUnavailableError) {
31722
+ breaker.recordFailure(result.statusCheckError, Date.now(), held);
31723
+ } else if (result.stats.jev.requests > 0) {
31724
+ breaker.recordSuccess(held);
31725
+ } else {
31726
+ breaker.release(held);
31727
+ }
31728
+ outcome = "completed";
31729
+ jevRequests = result.stats.jev.requests;
31730
+ jevCostUsd = result.stats.jev.costUsd;
31731
+ if (input.recordUsage && jevRequests > 0) {
31732
+ await input.recordUsage({
31733
+ operationId: context.operationId,
31734
+ jevRequests,
31735
+ jevInputTokens: result.stats.jev.inputTokens,
31736
+ jevCostUsd
31737
+ }).catch((error51) => {
31738
+ input.observability.warn("code_search usage record failed", {
31739
+ error: error51 instanceof Error ? error51.message : String(error51)
31740
+ });
31741
+ });
31742
+ }
31743
+ return textResult(result.text, false);
31744
+ } catch (error51) {
31745
+ if (context.signal?.aborted) {
31746
+ outcome = "cancelled";
31747
+ throw error51;
31748
+ }
31749
+ if (error51 instanceof CodeSearchArgumentError) {
31750
+ outcome = "invalid_arguments";
31751
+ return textResult(error51.message, true);
31752
+ }
31753
+ if (error51 instanceof JevUnavailableError) {
31754
+ const held = lease;
31755
+ lease = null;
31756
+ breaker.recordFailure(error51, Date.now(), held);
31757
+ outcome = "jev_unavailable";
31758
+ return textResult(renderCodeSearchError(error51), true);
31759
+ }
31760
+ if (error51 instanceof JevRequestError) {
31761
+ outcome = "jev_rejected";
31762
+ return textResult(renderCodeSearchError(error51), true);
31763
+ }
31764
+ if (error51 instanceof CodeSearchWorkspaceError) {
31765
+ outcome = "workspace_unavailable";
31766
+ return textResult(renderCodeSearchError(error51), true);
31767
+ }
31768
+ if (error51 instanceof ChannelAUnavailableError || error51 instanceof ChannelAUnsupportedError || error51 instanceof ChannelAValidationError) {
31769
+ outcome = "workspace_unavailable";
31770
+ return textResult(
31771
+ renderCodeSearchError(new CodeSearchWorkspaceError(error51.message)),
31772
+ true
31773
+ );
31774
+ }
31775
+ throw error51;
31776
+ } finally {
31777
+ if (lease) breaker.release(lease);
31778
+ recordCodeSearchCall(input.observability, {
31779
+ outcome,
31780
+ durationSeconds: (performance.now() - startedAt) / 1e3,
31781
+ jevRequests,
31782
+ jevCostUsd
31783
+ });
31784
+ }
31785
+ }
31786
+ };
31787
+ }
31788
+ function codeSearchToolDefinitions(input) {
31789
+ const apiKey = usableJevApiKey(input.settings);
31790
+ const breaker = input.breaker ?? codeSearchCircuitBreaker;
31791
+ if (!input.enabled || !apiKey || input.backend === "none") return [];
31792
+ if (input.machineWorkspaceRoot && isWindowsConnectedMachinePath(input.machineWorkspaceRoot)) {
31793
+ return [];
31794
+ }
31795
+ return [
31796
+ createCodeSearchAttemptToolDefinition({
31797
+ settings: input.settings,
31798
+ apiKey,
31799
+ workspace: input.workspace,
31800
+ observability: input.observability,
31801
+ ...input.recordUsage ? { recordUsage: input.recordUsage } : {},
31802
+ breaker
31803
+ })
31804
+ ];
31805
+ }
31806
+
31418
31807
  // src/activities/agent-turn/skill-tools.ts
31419
31808
  import { createHash as createHash12 } from "crypto";
31420
31809
  import {
@@ -32354,6 +32743,7 @@ async function prepareTurnToolRuntime(deps) {
32354
32743
  turnExecutionPolicy,
32355
32744
  trigger,
32356
32745
  runSettings: canonicalRunSettings,
32746
+ resolvedModel,
32357
32747
  lazyToolTransport,
32358
32748
  turnTools: canonicalTurnTools,
32359
32749
  sandboxArtifactRuntime,
@@ -32364,6 +32754,7 @@ async function prepareTurnToolRuntime(deps) {
32364
32754
  credentialSubjectId,
32365
32755
  interactionInterventionResume,
32366
32756
  runWorkspaceMutationForSandbox,
32757
+ codeSearchEnabled,
32367
32758
  throwIfWorkerShuttingDown,
32368
32759
  throwIfTurnCancelled
32369
32760
  } = deps;
@@ -32534,7 +32925,8 @@ async function prepareTurnToolRuntime(deps) {
32534
32925
  firstPartyMcpTools: selectedFirstPartyMcpTools,
32535
32926
  firstPartyMcpPermissions: effectiveFirstPartyPermissions
32536
32927
  }),
32537
- parallelGenerationAvailable: typeof runtime.generateSessionTitle === "function"
32928
+ parallelGenerationAvailable: typeof runtime.generateSessionTitle === "function",
32929
+ routeAllowsTitleRequests: routeAllowsSessionTitleRequests(resolvedModel)
32538
32930
  });
32539
32931
  const googleDrivePublicationAllowed = selectedFirstPartyMcpTools.includes("editable_artifact_export") && selectedFirstPartyMcpTools.includes("editable_artifact_export_status") && hasPermission2(
32540
32932
  [...effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2],
@@ -32694,6 +33086,60 @@ async function prepareTurnToolRuntime(deps) {
32694
33086
  return (await prepared.ready ?? prepared).attemptToolEnvironment;
32695
33087
  }
32696
33088
  });
33089
+ const codeSearchTools = codeSearchToolDefinitions({
33090
+ enabled: codeSearchEnabled,
33091
+ settings: runSettings,
33092
+ backend: activeSandboxBackend ?? groupBoxBackend,
33093
+ machineWorkspaceRoot: sandboxState.machinePrimarySession?.workspaceRoot ?? null,
33094
+ observability,
33095
+ // OpenGeni's Jev key pays for these calls whatever model billing the
33096
+ // workspace uses; record them per workspace so the cost stays visible.
33097
+ recordUsage: async (usage) => {
33098
+ const shared = {
33099
+ accountId: input.accountId,
33100
+ workspaceId: input.workspaceId,
33101
+ sourceResourceType: "code_search",
33102
+ sourceResourceId: usage.operationId,
33103
+ sessionId: input.sessionId,
33104
+ turnId: turn.id,
33105
+ turnAttemptId: input.attemptId
33106
+ };
33107
+ await recordUsageEvent4(db, {
33108
+ ...shared,
33109
+ eventType: "code_search.jev_input_tokens",
33110
+ quantity: usage.jevInputTokens,
33111
+ unit: "tokens",
33112
+ idempotencyKey: `usage:code_search.jev_input_tokens:${input.attemptId}:${usage.operationId}`
33113
+ });
33114
+ await recordUsageEvent4(db, {
33115
+ ...shared,
33116
+ eventType: "code_search.jev_cost",
33117
+ quantity: Math.round(usage.jevCostUsd * 1e6),
33118
+ unit: "usd_micros",
33119
+ idempotencyKey: `usage:code_search.jev_cost:${input.attemptId}:${usage.operationId}`
33120
+ });
33121
+ },
33122
+ workspace: async () => {
33123
+ throwIfWorkerShuttingDown();
33124
+ throwIfTurnCancelled();
33125
+ const access = await resolveTurnSandboxAccess(
33126
+ sandboxState,
33127
+ media.sdkOwnedSandboxSession,
33128
+ "code_search requires a sandbox or Connected Machine."
33129
+ );
33130
+ const machineRoot = sandboxState.machinePrimarySession?.workspaceRoot;
33131
+ const runAs = sandboxRunAs4(runSettings);
33132
+ return codeSearchWorkspaceFromChannel(
33133
+ new SandboxChannelAService3({
33134
+ session: access.session,
33135
+ workspaceRoot: machineRoot ?? "/workspace",
33136
+ ...machineRoot ? { providerPathMode: "workspace-relative" } : {},
33137
+ leaseEpoch: access.leaseEpoch,
33138
+ ...runAs ? { runAs } : {}
33139
+ })
33140
+ );
33141
+ }
33142
+ });
32697
33143
  const attemptToolDefinitions = [
32698
33144
  ...operationReadStore ? [
32699
33145
  createOperationReadAttemptToolDefinition({
@@ -32823,7 +33269,8 @@ async function prepareTurnToolRuntime(deps) {
32823
33269
  subjectLabel: "OpenGeni worker",
32824
33270
  ...interactionInterventionResume ? { interventionResume: interactionInterventionResume } : {}
32825
33271
  }),
32826
- ...googleDrivePublicationTool && googleDrivePublicationAllowed ? [googleDrivePublicationTool] : []
33272
+ ...googleDrivePublicationTool && googleDrivePublicationAllowed ? [googleDrivePublicationTool] : [],
33273
+ ...codeSearchTools
32827
33274
  ];
32828
33275
  recordTurnStartupPhase(observability, {
32829
33276
  phase: "tool_context_preparation",
@@ -33012,6 +33459,7 @@ async function prepareTurnToolRuntime(deps) {
33012
33459
  ...titleToolPlan.preparationIndependentToolNames,
33013
33460
  "skill_read"
33014
33461
  ],
33462
+ codeSearchAvailable: codeSearchTools.length > 0,
33015
33463
  skillCatalog
33016
33464
  };
33017
33465
  }
@@ -34430,6 +34878,7 @@ async function buildTurnAgent(deps) {
34430
34878
  connectorActionPolicy,
34431
34879
  trigger,
34432
34880
  preparationIndependentToolNames,
34881
+ codeSearchAvailable,
34433
34882
  videoGenerationAcceptancesByCallId,
34434
34883
  activeSandboxBackend,
34435
34884
  groupBoxBackend,
@@ -34836,6 +35285,7 @@ async function buildTurnAgent(deps) {
34836
35285
  ...activeSandboxBackend !== "selfhosted" && sandboxGitCredentialBindings ? { gitCredentialBindings: sandboxGitCredentialBindings } : {},
34837
35286
  ...activeSandboxBackend !== "selfhosted" && !sandboxGitTokens && sandboxGitToken ? { gitTokenSeed: sandboxGitToken } : {},
34838
35287
  ...sandboxCodemodeToken ? { codemodeAvailable: true } : {},
35288
+ ...codeSearchAvailable ? { codeSearchAvailable: true } : {},
34839
35289
  // Managed boxes receive the bearer through their protected per-session
34840
35290
  // token file. Connected Machines use transient per-exec delivery above,
34841
35291
  // so they must not run the file-seeding lifecycle hook.
@@ -35320,6 +35770,7 @@ function createRunAgentTurnActivity(services) {
35320
35770
  rigVersion,
35321
35771
  rigName,
35322
35772
  agentHumanInputEnabled,
35773
+ codeSearchEnabled,
35323
35774
  workspaceAgentInstructions,
35324
35775
  workspaceGovernance,
35325
35776
  structuredWorkspacePolicyActive,
@@ -35953,6 +36404,7 @@ These are publication receipts for the human's earlier answer. Do not call the r
35953
36404
  turnExecutionPolicy,
35954
36405
  trigger,
35955
36406
  runSettings,
36407
+ resolvedModel,
35956
36408
  lazyToolTransport,
35957
36409
  turnTools,
35958
36410
  connectionScope,
@@ -35964,6 +36416,7 @@ These are publication receipts for the human's earlier answer. Do not call the r
35964
36416
  credentialSubjectId,
35965
36417
  interactionInterventionResume,
35966
36418
  runWorkspaceMutationForSandbox,
36419
+ codeSearchEnabled,
35967
36420
  throwIfWorkerShuttingDown,
35968
36421
  throwIfTurnCancelled
35969
36422
  });
@@ -36026,6 +36479,7 @@ These are publication receipts for the human's earlier answer. Do not call the r
36026
36479
  connectorActionPolicy,
36027
36480
  trigger,
36028
36481
  preparationIndependentToolNames,
36482
+ codeSearchAvailable: toolRuntime.codeSearchAvailable,
36029
36483
  videoGenerationAcceptancesByCallId,
36030
36484
  activeSandboxBackend,
36031
36485
  groupBoxBackend,
@@ -37533,4 +37987,4 @@ export {
37533
37987
  createTurnActivities,
37534
37988
  createTurnActivitiesFromServices
37535
37989
  };
37536
- //# sourceMappingURL=activities-turn-YVVKCGAK.js.map
37990
+ //# sourceMappingURL=activities-turn-CSDKK4N3.js.map