@opengeni/worker-bundle 1.0.0-canary.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/activities/agent-run-admission.d.ts +3 -1
  2. package/dist/activities/agent-turn/admission.d.ts +24 -3
  3. package/dist/activities/agent-turn/agent-build.d.ts +4 -0
  4. package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
  5. package/dist/activities/agent-turn/code-search.d.ts +60 -0
  6. package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
  7. package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
  8. package/dist/activities/agent-turn/codex.d.ts +3 -2
  9. package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
  10. package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
  11. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  12. package/dist/activities/agent-turn/errors.d.ts +82 -11
  13. package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
  14. package/dist/activities/agent-turn/final-reply.d.ts +18 -0
  15. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  16. package/dist/activities/agent-turn/finalization.d.ts +4 -2
  17. package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
  18. package/dist/activities/agent-turn/governance-model.d.ts +9 -2
  19. package/dist/activities/agent-turn/history-sink.d.ts +2 -0
  20. package/dist/activities/agent-turn/history.d.ts +2 -0
  21. package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
  22. package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
  23. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  24. package/dist/activities/agent-turn/model-usage.d.ts +6 -0
  25. package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
  26. package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
  27. package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
  28. package/dist/activities/agent-turn/quiescence.d.ts +7 -7
  29. package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
  30. package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
  31. package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
  32. package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
  33. package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
  34. package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
  35. package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
  36. package/dist/activities/agent-turn/session-title.d.ts +34 -2
  37. package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
  38. package/dist/activities/agent-turn/skill-read.d.ts +70 -2
  39. package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
  40. package/dist/activities/agent-turn/skill-search.d.ts +3 -1
  41. package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
  42. package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
  43. package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
  44. package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
  45. package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
  46. package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
  47. package/dist/activities/agent-turn/turn-context.d.ts +29 -0
  48. package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
  49. package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
  50. package/dist/activities/automations.d.ts +3 -1
  51. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  52. package/dist/activities/codemode-dispatcher.d.ts +9 -2
  53. package/dist/activities/codex-capacity.d.ts +18 -2
  54. package/dist/activities/codex-rotation.d.ts +7 -1
  55. package/dist/activities/context-compaction.d.ts +16 -2
  56. package/dist/activities/gmail-files.d.ts +32 -0
  57. package/dist/activities/goals.d.ts +8 -0
  58. package/dist/activities/knowledge-indexing.d.ts +19 -0
  59. package/dist/activities/mcp-account-routes.d.ts +3 -0
  60. package/dist/activities/mcp-credentials.d.ts +12 -13
  61. package/dist/activities/mcp-operation-authority.d.ts +0 -5
  62. package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
  63. package/dist/activities/open-suffix-resume.d.ts +13 -0
  64. package/dist/activities/parent-wake.d.ts +6 -12
  65. package/dist/activities/retained-screenshots.d.ts +7 -2
  66. package/dist/activities/rig-verification.d.ts +2 -3
  67. package/dist/activities/run-credential-renewal.d.ts +1 -0
  68. package/dist/activities/run-credentials.d.ts +15 -3
  69. package/dist/activities/sandbox-images.d.ts +1 -1
  70. package/dist/activities/sandbox-lease.d.ts +47 -9
  71. package/dist/activities/scheduled-tasks.d.ts +4 -35
  72. package/dist/activities/session-state.d.ts +70 -3
  73. package/dist/activities/types.d.ts +55 -6
  74. package/dist/activities/workspace-capture.d.ts +2 -0
  75. package/dist/activities/workspace-credential-provider.d.ts +46 -0
  76. package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
  77. package/dist/activities-control-BNPTRTOE.js.map +1 -0
  78. package/dist/activities-control.d.ts +182 -4
  79. package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
  80. package/dist/activities-turn-5LGZOSVU.js.map +1 -0
  81. package/dist/activities.d.ts +182 -4
  82. package/dist/artifact-materializer-entry.js +5 -2
  83. package/dist/artifact-materializer-entry.js.map +1 -1
  84. package/dist/artifact-outbox-entry.js +5 -2
  85. package/dist/artifact-outbox-entry.js.map +1 -1
  86. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  87. package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
  88. package/dist/chunk-3ZTRWDMN.js.map +1 -0
  89. package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
  90. package/dist/chunk-4ER3YXOT.js.map +1 -0
  91. package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
  92. package/dist/chunk-LJX7Z2KP.js.map +1 -0
  93. package/dist/editable-artifact-materializer-service.d.ts +2 -2
  94. package/dist/editable-artifact-outbox-service.d.ts +2 -2
  95. package/dist/host-export-pump.d.ts +3 -1
  96. package/dist/host-export.d.ts +1 -1
  97. package/dist/host-export.js +1 -1
  98. package/dist/http.d.ts +1 -1
  99. package/dist/index.d.ts +95 -3
  100. package/dist/index.js +137 -43
  101. package/dist/index.js.map +1 -1
  102. package/dist/observability-metrics.d.ts +118 -8
  103. package/dist/retained-process-diagnostics.d.ts +4 -0
  104. package/dist/retained-process-retry.d.ts +7 -0
  105. package/dist/sandbox-resume.d.ts +55 -13
  106. package/dist/sandbox-routing.d.ts +5 -3
  107. package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
  108. package/dist/session-event-fanout.d.ts +6 -0
  109. package/dist/warm-capture-holder.d.ts +3 -0
  110. package/dist/worker-service-lifecycle.d.ts +12 -0
  111. package/dist/workflow-bundle.js +273 -41
  112. package/dist/workflows/activities.d.ts +1 -1
  113. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  114. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  115. package/dist/workflows/session.d.ts +36 -0
  116. package/dist/workflows.d.ts +1 -0
  117. package/package.json +21 -19
  118. package/src/activities/agent-run-admission.ts +17 -1
  119. package/src/activities/agent-turn/admission.ts +66 -3
  120. package/src/activities/agent-turn/agent-build.ts +84 -10
  121. package/src/activities/agent-turn/claim.ts +186 -43
  122. package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
  123. package/src/activities/agent-turn/code-search.ts +275 -0
  124. package/src/activities/agent-turn/codex-capacity.ts +172 -8
  125. package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
  126. package/src/activities/agent-turn/codex.ts +13 -5
  127. package/src/activities/agent-turn/compaction-prep.ts +170 -102
  128. package/src/activities/agent-turn/credential-leases.ts +122 -216
  129. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  130. package/src/activities/agent-turn/errors.ts +647 -48
  131. package/src/activities/agent-turn/failure-settlement.ts +682 -130
  132. package/src/activities/agent-turn/final-reply.ts +46 -0
  133. package/src/activities/agent-turn/finalization-monitor.ts +120 -0
  134. package/src/activities/agent-turn/finalization.ts +146 -43
  135. package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
  136. package/src/activities/agent-turn/governance-model.ts +69 -28
  137. package/src/activities/agent-turn/history-sink.ts +16 -2
  138. package/src/activities/agent-turn/history.ts +38 -11
  139. package/src/activities/agent-turn/input-wait-reply.ts +103 -0
  140. package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
  141. package/src/activities/agent-turn/media-artifacts.ts +2 -1
  142. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  143. package/src/activities/agent-turn/model-usage.ts +64 -3
  144. package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
  145. package/src/activities/agent-turn/optional-repositories.ts +224 -0
  146. package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
  147. package/src/activities/agent-turn/quiescence.ts +10 -7
  148. package/src/activities/agent-turn/recovery-warning.ts +16 -0
  149. package/src/activities/agent-turn/refresh-credentials.ts +44 -0
  150. package/src/activities/agent-turn/run-credentials.ts +116 -55
  151. package/src/activities/agent-turn/run.ts +320 -93
  152. package/src/activities/agent-turn/sandbox-establish.ts +84 -8
  153. package/src/activities/agent-turn/sandbox-provision.ts +3 -1
  154. package/src/activities/agent-turn/sandbox-route.ts +4 -21
  155. package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
  156. package/src/activities/agent-turn/session-title.ts +81 -5
  157. package/src/activities/agent-turn/skill-checkout.ts +111 -24
  158. package/src/activities/agent-turn/skill-install.ts +1 -1
  159. package/src/activities/agent-turn/skill-read.ts +374 -28
  160. package/src/activities/agent-turn/skill-remove.ts +46 -0
  161. package/src/activities/agent-turn/skill-save.ts +1 -1
  162. package/src/activities/agent-turn/skill-search.ts +10 -1
  163. package/src/activities/agent-turn/skill-selection.ts +4 -0
  164. package/src/activities/agent-turn/skill-tools.ts +112 -18
  165. package/src/activities/agent-turn/skill-transfer.ts +68 -23
  166. package/src/activities/agent-turn/stream-attempt.ts +313 -77
  167. package/src/activities/agent-turn/subscription-lease.ts +115 -0
  168. package/src/activities/agent-turn/tool-environment.ts +316 -167
  169. package/src/activities/agent-turn/tool-policy.ts +80 -1
  170. package/src/activities/agent-turn/turn-context.ts +35 -0
  171. package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
  172. package/src/activities/agent-turn/xai-capacity.ts +94 -51
  173. package/src/activities/automations.ts +76 -4
  174. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  175. package/src/activities/capabilities.ts +108 -11
  176. package/src/activities/codemode-dispatcher.ts +36 -8
  177. package/src/activities/codex-capacity.ts +60 -25
  178. package/src/activities/codex-rotation.ts +39 -17
  179. package/src/activities/context-compaction.ts +75 -18
  180. package/src/activities/gmail-files.ts +88 -0
  181. package/src/activities/goals.ts +116 -121
  182. package/src/activities/google-drive-publication.ts +1 -3
  183. package/src/activities/image-generation-operation.ts +1 -1
  184. package/src/activities/knowledge-indexing.ts +269 -15
  185. package/src/activities/knowledge-source-sync.ts +10 -0
  186. package/src/activities/mcp-account-routes.ts +17 -0
  187. package/src/activities/mcp-credentials.ts +83 -194
  188. package/src/activities/mcp-operation-authority.ts +16 -41
  189. package/src/activities/model-call-fact-reconciler.ts +112 -0
  190. package/src/activities/open-suffix-resume.ts +15 -2
  191. package/src/activities/parent-wake.ts +46 -21
  192. package/src/activities/retained-screenshots.ts +82 -11
  193. package/src/activities/rig-verification.ts +38 -37
  194. package/src/activities/run-credential-renewal.ts +13 -1
  195. package/src/activities/run-credentials.ts +59 -17
  196. package/src/activities/run-input.ts +25 -16
  197. package/src/activities/sandbox-images.ts +0 -1
  198. package/src/activities/sandbox-lease.ts +798 -82
  199. package/src/activities/scheduled-tasks.ts +550 -178
  200. package/src/activities/session-state.ts +245 -6
  201. package/src/activities/site-auth-maintenance.ts +3 -0
  202. package/src/activities/types.ts +72 -5
  203. package/src/activities/workflow-wake.ts +2 -17
  204. package/src/activities/workspace-capture.ts +13 -2
  205. package/src/activities/workspace-credential-provider.ts +283 -0
  206. package/src/activities-control.ts +4 -0
  207. package/src/activities-turn.ts +5 -0
  208. package/src/activities.ts +13 -0
  209. package/src/activity-services.ts +16 -4
  210. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  211. package/src/editable-artifact-hint-broker.ts +3 -0
  212. package/src/editable-artifact-materializer-service.ts +7 -3
  213. package/src/editable-artifact-outbox-service.ts +4 -4
  214. package/src/github-rest-mcp.ts +1 -2
  215. package/src/host-export-pump.ts +26 -4
  216. package/src/host-export.ts +3 -0
  217. package/src/http.ts +4 -2
  218. package/src/index.ts +99 -37
  219. package/src/observability-metrics.ts +510 -5
  220. package/src/personal-github-git-credentials.ts +2 -1
  221. package/src/pr-review-credentials.ts +1 -3
  222. package/src/retained-process-diagnostics.ts +50 -0
  223. package/src/retained-process-retry.ts +36 -0
  224. package/src/sandbox-resume.ts +388 -101
  225. package/src/sandbox-routing.ts +150 -25
  226. package/src/sandbox-snapshot-diagnostics.ts +112 -0
  227. package/src/session-event-fanout.ts +23 -0
  228. package/src/warm-capture-holder.ts +15 -0
  229. package/src/worker-service-lifecycle.ts +53 -0
  230. package/src/workflows/activities.ts +16 -1
  231. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  232. package/src/workflows/file-upload-reaper.ts +2 -0
  233. package/src/workflows/scheduled-tasks.ts +26 -13
  234. package/src/workflows/session.ts +242 -22
  235. package/src/workflows.ts +4 -0
  236. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  237. package/dist/activities/packs.d.ts +0 -55
  238. package/dist/activities-control-HMFGSKC5.js.map +0 -1
  239. package/dist/activities-turn-AP67JADS.js.map +0 -1
  240. package/dist/chunk-A4XQWVZK.js.map +0 -1
  241. package/dist/chunk-CYITU5UH.js.map +0 -1
  242. package/dist/chunk-RAMNYSAW.js.map +0 -1
  243. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
  244. package/src/activities/packs.ts +0 -166
@@ -0,0 +1,275 @@
1
+ import type { AttemptToolDefinition } from "@opengeni/codemode";
2
+ import { usableJevApiKey, type Settings } from "@opengeni/config";
3
+ import {
4
+ CODE_SEARCH_TOOL_DESCRIPTION,
5
+ CODE_SEARCH_TOOL_NAME,
6
+ CodeSearchArgumentError,
7
+ CodeSearchRipgrepMissingError,
8
+ CodeSearchWorkspaceError,
9
+ JevCircuitBreaker,
10
+ JevClient,
11
+ JevRequestError,
12
+ JevUnavailableError,
13
+ codeSearchInputSchema,
14
+ parseCodeSearchArguments,
15
+ renderCodeSearchError,
16
+ runCodeSearch,
17
+ type CodeSearchWorkspace,
18
+ type JevCircuitLease,
19
+ } from "@opengeni/jev";
20
+ import type { Observability } from "@opengeni/observability";
21
+ import {
22
+ ChannelANotFoundError,
23
+ ChannelAUnavailableError,
24
+ ChannelAUnsupportedError,
25
+ ChannelAValidationError,
26
+ isWindowsConnectedMachinePath,
27
+ type SandboxChannelAService,
28
+ } from "@opengeni/runtime/sandbox";
29
+ import { recordCodeSearchCall, type CodeSearchCallOutcome } from "../../observability-metrics";
30
+
31
+ /**
32
+ * One breaker per worker process. Repeated Jev outages (including an exhausted
33
+ * account) make `code_search` calls on this worker fail at once for a cooldown
34
+ * instead of each running a full retry cycle. The breaker never changes which
35
+ * tools a turn is offered: the tool list and instructions are the start of the
36
+ * model's cached prompt, and sessions move between workers whose breakers
37
+ * disagree.
38
+ */
39
+ export const codeSearchCircuitBreaker = new JevCircuitBreaker();
40
+
41
+ /** Ripgrep output kept on the box per call before framing. */
42
+ const CODE_SEARCH_RIPGREP_MAX_BYTES = 32 * 1024 * 1024;
43
+
44
+ type CodeSearchChannel = Pick<
45
+ SandboxChannelAService,
46
+ "codeSearchRipgrep" | "codeSearchPathKinds" | "fsRead"
47
+ >;
48
+
49
+ /** Adapt the turn's sandbox or Connected Machine to the code search engine. */
50
+ export function codeSearchWorkspaceFromChannel(channel: CodeSearchChannel): CodeSearchWorkspace {
51
+ return {
52
+ ripgrep: async (args, options) => {
53
+ options.signal?.throwIfAborted();
54
+ const outcome = await channel.codeSearchRipgrep(args, {
55
+ timeoutMs: options.timeoutMs,
56
+ maxBytes: CODE_SEARCH_RIPGREP_MAX_BYTES,
57
+ });
58
+ if (!outcome.available) throw new CodeSearchRipgrepMissingError();
59
+ return {
60
+ stdout: outcome.stdout,
61
+ exitCode: outcome.exitCode,
62
+ truncated: outcome.truncated,
63
+ timedOut: outcome.timedOut,
64
+ };
65
+ },
66
+ readText: async (path, options) => {
67
+ options.signal?.throwIfAborted();
68
+ try {
69
+ const read = await channel.fsRead({ path, encoding: "utf8", maxBytes: options.maxBytes });
70
+ return { text: read.content, truncated: read.truncated, binary: read.isBinary };
71
+ } catch (error) {
72
+ if (error instanceof ChannelANotFoundError) return null;
73
+ throw error;
74
+ }
75
+ },
76
+ pathKinds: async (paths, options) => {
77
+ options.signal?.throwIfAborted();
78
+ return await channel.codeSearchPathKinds(paths);
79
+ },
80
+ };
81
+ }
82
+
83
+ /** Jev work done by one completed `code_search` call, for per-workspace usage records. */
84
+ export type CodeSearchUsage = {
85
+ operationId: string;
86
+ jevRequests: number;
87
+ jevInputTokens: number;
88
+ jevCostUsd: number;
89
+ };
90
+
91
+ function textResult(text: string, isError: boolean) {
92
+ return { isError, content: [{ type: "text" as const, text }] };
93
+ }
94
+
95
+ /**
96
+ * The model-facing `code_search` tool. Jev scores candidates inside the worker;
97
+ * the sandbox only runs read-only ripgrep and file reads, so the Jev key never
98
+ * leaves this process. A Jev failure is reported to the model instead of
99
+ * degrading to keyword-only ranking, which lowered answer quality in testing.
100
+ */
101
+ export function createCodeSearchAttemptToolDefinition(input: {
102
+ settings: Pick<Settings, "jevApiKey" | "jevBaseUrl" | "jevModel" | "jevRequestTimeoutMs">;
103
+ apiKey: string;
104
+ workspace: () => Promise<CodeSearchWorkspace>;
105
+ observability: Observability;
106
+ /** Records Jev usage against the workspace. Failures are logged, never surfaced. */
107
+ recordUsage?: (usage: CodeSearchUsage) => Promise<void>;
108
+ breaker?: JevCircuitBreaker;
109
+ fetch?: typeof fetch;
110
+ }): AttemptToolDefinition {
111
+ const breaker = input.breaker ?? codeSearchCircuitBreaker;
112
+ const jev = new JevClient({
113
+ apiKey: input.apiKey,
114
+ baseUrl: input.settings.jevBaseUrl,
115
+ model: input.settings.jevModel,
116
+ timeoutMs: input.settings.jevRequestTimeoutMs,
117
+ ...(input.fetch ? { fetch: input.fetch } : {}),
118
+ });
119
+ return {
120
+ identity: { serverId: "opengeni", toolName: CODE_SEARCH_TOOL_NAME },
121
+ modelName: CODE_SEARCH_TOOL_NAME,
122
+ codemodePath: ["opengeni", CODE_SEARCH_TOOL_NAME],
123
+ title: "Search code",
124
+ description: CODE_SEARCH_TOOL_DESCRIPTION,
125
+ inputSchema: codeSearchInputSchema,
126
+ annotations: {
127
+ title: "Search code",
128
+ readOnlyHint: true,
129
+ destructiveHint: false,
130
+ idempotentHint: true,
131
+ openWorldHint: false,
132
+ },
133
+ source: "opengeni",
134
+ approval: "none",
135
+ execute: async (args, context) => {
136
+ const startedAt = performance.now();
137
+ let outcome: CodeSearchCallOutcome = "failed";
138
+ let jevRequests = 0;
139
+ let jevCostUsd = 0;
140
+ // The breaker lease (the single trial while half-open) is settled exactly
141
+ // once: by what Jev did, or released when Jev was never judged. Settling
142
+ // takes it, so the finally block releases only a lease still held.
143
+ let lease: JevCircuitLease | null = null;
144
+ try {
145
+ const request = parseCodeSearchArguments(args);
146
+ lease = breaker.tryAcquire(Date.now());
147
+ if (!lease) {
148
+ outcome = "breaker_open";
149
+ return textResult(
150
+ renderCodeSearchError(
151
+ new JevUnavailableError("Jev is not responding; calls are paused for a few minutes"),
152
+ ),
153
+ true,
154
+ );
155
+ }
156
+ const workspace = await input.workspace();
157
+ const result = await runCodeSearch({
158
+ ...request,
159
+ workspace,
160
+ jev,
161
+ ...(context.signal ? { signal: context.signal } : {}),
162
+ });
163
+ const held = lease;
164
+ lease = null;
165
+ // A pack whose final status check hit an outage still counts against Jev.
166
+ if (result.statusCheckError instanceof JevUnavailableError) {
167
+ breaker.recordFailure(result.statusCheckError, Date.now(), held);
168
+ } else if (result.stats.jev.requests > 0) {
169
+ breaker.recordSuccess(held);
170
+ } else {
171
+ breaker.release(held);
172
+ }
173
+ outcome = "completed";
174
+ jevRequests = result.stats.jev.requests;
175
+ jevCostUsd = result.stats.jev.costUsd;
176
+ if (input.recordUsage && jevRequests > 0) {
177
+ await input
178
+ .recordUsage({
179
+ operationId: context.operationId,
180
+ jevRequests,
181
+ jevInputTokens: result.stats.jev.inputTokens,
182
+ jevCostUsd,
183
+ })
184
+ .catch((error: unknown) => {
185
+ input.observability.warn("code_search usage record failed", {
186
+ error: error instanceof Error ? error.message : String(error),
187
+ });
188
+ });
189
+ }
190
+ return textResult(result.text, false);
191
+ } catch (error) {
192
+ if (context.signal?.aborted) {
193
+ outcome = "cancelled";
194
+ throw error;
195
+ }
196
+ if (error instanceof CodeSearchArgumentError) {
197
+ outcome = "invalid_arguments";
198
+ return textResult(error.message, true);
199
+ }
200
+ if (error instanceof JevUnavailableError) {
201
+ const held = lease;
202
+ lease = null;
203
+ breaker.recordFailure(error, Date.now(), held);
204
+ outcome = "jev_unavailable";
205
+ return textResult(renderCodeSearchError(error), true);
206
+ }
207
+ if (error instanceof JevRequestError) {
208
+ outcome = "jev_rejected";
209
+ return textResult(renderCodeSearchError(error), true);
210
+ }
211
+ if (error instanceof CodeSearchWorkspaceError) {
212
+ outcome = "workspace_unavailable";
213
+ return textResult(renderCodeSearchError(error), true);
214
+ }
215
+ if (
216
+ error instanceof ChannelAUnavailableError ||
217
+ error instanceof ChannelAUnsupportedError ||
218
+ error instanceof ChannelAValidationError
219
+ ) {
220
+ outcome = "workspace_unavailable";
221
+ return textResult(
222
+ renderCodeSearchError(new CodeSearchWorkspaceError(error.message)),
223
+ true,
224
+ );
225
+ }
226
+ throw error;
227
+ } finally {
228
+ if (lease) breaker.release(lease);
229
+ recordCodeSearchCall(input.observability, {
230
+ outcome,
231
+ durationSeconds: (performance.now() - startedAt) / 1_000,
232
+ jevRequests,
233
+ jevCostUsd,
234
+ });
235
+ }
236
+ },
237
+ };
238
+ }
239
+
240
+ /**
241
+ * The `code_search` definition for one turn, or none. It is offered only when
242
+ * the session's frozen decision and the deployment enable it, a usable Jev key
243
+ * exists, and the turn has compute that can run its POSIX shell commands (not
244
+ * a Windows Connected Machine). Every input is durable, so the tool list stays
245
+ * the same from turn to turn and on every worker. Transient Jev health never
246
+ * hides the tool; the breaker only refuses calls.
247
+ */
248
+ export function codeSearchToolDefinitions(input: {
249
+ enabled: boolean;
250
+ settings: Pick<Settings, "jevApiKey" | "jevBaseUrl" | "jevModel" | "jevRequestTimeoutMs">;
251
+ backend: Settings["sandboxBackend"];
252
+ /** The turn's Connected Machine workspace root, when a machine is primary. */
253
+ machineWorkspaceRoot?: string | null;
254
+ observability: Observability;
255
+ workspace: () => Promise<CodeSearchWorkspace>;
256
+ recordUsage?: (usage: CodeSearchUsage) => Promise<void>;
257
+ breaker?: JevCircuitBreaker;
258
+ }): AttemptToolDefinition[] {
259
+ const apiKey = usableJevApiKey(input.settings);
260
+ const breaker = input.breaker ?? codeSearchCircuitBreaker;
261
+ if (!input.enabled || !apiKey || input.backend === "none") return [];
262
+ if (input.machineWorkspaceRoot && isWindowsConnectedMachinePath(input.machineWorkspaceRoot)) {
263
+ return [];
264
+ }
265
+ return [
266
+ createCodeSearchAttemptToolDefinition({
267
+ settings: input.settings,
268
+ apiKey,
269
+ workspace: input.workspace,
270
+ observability: input.observability,
271
+ ...(input.recordUsage ? { recordUsage: input.recordUsage } : {}),
272
+ breaker,
273
+ }),
274
+ ];
275
+ }
@@ -2,6 +2,7 @@ import {
2
2
  connectionModelAllowed,
3
3
  getSessionGoal,
4
4
  acquireCodexCredentialLease,
5
+ recheckCodexCredentialPlan,
5
6
  CodexCredentialLeaseAttemptFencedError,
6
7
  CodexCredentialFailoverExhaustedError,
7
8
  CODEX_CREDENTIAL_LEASE_TTL_MS,
@@ -14,7 +15,7 @@ import {
14
15
  type CodexCredentialLeaseSelectionContext,
15
16
  } from "@opengeni/db";
16
17
  import { type Settings } from "@opengeni/config";
17
- import { CodexReloginRequired } from "@opengeni/codex";
18
+ import { CodexReloginRequired, codexPlanKey } from "@opengeni/codex";
18
19
  import { publishDurableSessionEvents } from "@opengeni/events";
19
20
  import {
20
21
  authoritativeCodexCapacityResetAt,
@@ -43,8 +44,15 @@ import { recordTurnStartupPhase } from "../../observability-metrics";
43
44
  import { createTurnCredentialLeases } from "./credential-leases";
44
45
  import { deliverFailedChildTurnToParent } from "../parent-wake";
45
46
  import { randomUUID } from "node:crypto";
47
+ import { createLogThrottle, type LogThrottle } from "@opengeni/observability";
46
48
 
47
49
  import { refreshCappedCodexUsageRows } from "./codex";
50
+ import {
51
+ CodexPlanEntitlementError,
52
+ codexAccountDisplayLabel,
53
+ codexPlanEntitlementAdmissionBlock,
54
+ codexPlanEntitlementFailurePayload,
55
+ } from "./codex-plan-entitlement";
48
56
  import { codexUsageLimitFailurePayload, CODEX_USAGE_LIMIT_MAX_RESUME_MS } from "./errors";
49
57
  import type { ClaimTurnOk } from "./claim";
50
58
  import type {
@@ -56,6 +64,39 @@ import type {
56
64
  TurnControlState,
57
65
  } from "./turn-context";
58
66
 
67
+ /** The eligible-pool gauge and `opengeni_codex_pool_low_total` stay per turn;
68
+ * the warning line is the first observation per workspace pool depth, then at
69
+ * most one per interval with the count it hid. The public log projection drops
70
+ * the identifiers and counts, so the closed `reason` keeps the depth visible. */
71
+ export const CODEX_POOL_LOW_WARNING_INTERVAL_MS = 10 * 60_000;
72
+ const codexPoolLowWarningThrottle = createLogThrottle({
73
+ intervalMs: CODEX_POOL_LOW_WARNING_INTERVAL_MS,
74
+ maxKeys: 1_024,
75
+ });
76
+
77
+ export function warnCodexPoolLow(
78
+ observability: Pick<ActivityServices["observability"], "warn">,
79
+ input: {
80
+ workspaceKey: string;
81
+ workspaceId: string;
82
+ eligibleCount: number;
83
+ connectedCount: number;
84
+ depth: "zero" | "one";
85
+ },
86
+ throttle: LogThrottle = codexPoolLowWarningThrottle,
87
+ ): void {
88
+ const admission = throttle.admit(`${input.workspaceKey}:${input.depth}`);
89
+ if (!admission) return;
90
+ observability.warn("Codex eligible credential pool is low", {
91
+ workspaceId: input.workspaceId,
92
+ eligibleCount: input.eligibleCount,
93
+ connectedCount: input.connectedCount,
94
+ depth: input.depth,
95
+ reason: input.depth === "zero" ? "eligible_pool_zero" : "eligible_pool_one",
96
+ ...(admission.suppressedCount > 0 ? { suppressedCount: admission.suppressedCount } : {}),
97
+ });
98
+ }
99
+
59
100
  export type CapacityPhaseDeps = {
60
101
  input: RunAgentTurnInput;
61
102
  settings: Settings;
@@ -142,7 +183,13 @@ export async function selectCodexTurnCapacity(
142
183
  )
143
184
  throw new Error("This model is disabled for the pinned Codex subscription");
144
185
  return selectCodexCredentialLeaseForTurn({
145
- context: { ...context, accounts: allowed },
186
+ // The accepted product model also scopes proven plan entitlement:
187
+ // an account whose current plan excludes it is not a candidate.
188
+ context: {
189
+ ...context,
190
+ accounts: allowed,
191
+ modelId: deps.turnExecutionPolicy.productModelId,
192
+ },
146
193
  sessionId: input.sessionId,
147
194
  sessionPinnedCredentialId: lockedSessionCodexState.pinnedCredentialId,
148
195
  sessionPinSource: lockedSessionCodexState.pinSource,
@@ -173,10 +220,17 @@ export async function selectCodexTurnCapacity(
173
220
  );
174
221
  if (leased.decision.kind === "allCapped") {
175
222
  // Bounded self-heal of stale usage cache, then ONE new atomic selection.
176
- await refreshCappedCodexUsageRows(db, settings, input.workspaceId, leased.accounts, {
177
- signalCodexCapacityWorkflow,
178
- wakeSessionWorkflow,
179
- });
223
+ await refreshCappedCodexUsageRows(
224
+ db,
225
+ settings,
226
+ input.workspaceId,
227
+ leased.accounts,
228
+ {
229
+ signalCodexCapacityWorkflow,
230
+ wakeSessionWorkflow,
231
+ },
232
+ turn.id,
233
+ );
180
234
  leaseAcquisitionStartedAtMs = performance.now();
181
235
  leased = await acquireCodexCredentialLease(
182
236
  db,
@@ -197,6 +251,99 @@ export async function selectCodexTurnCapacity(
197
251
  selectForTurn,
198
252
  );
199
253
  }
254
+ // No account can serve the model because the only candidates' CURRENT
255
+ // plans were proven not to include it. Re-read those plans once (an
256
+ // upgrade may not have been observed yet), then either select again or
257
+ // fail with typed copy instead of an indefinite capacity wait.
258
+ const productModelId = deps.turnExecutionPolicy.productModelId;
259
+ const planBlock = (current: typeof leased) =>
260
+ codexPlanEntitlementAdmissionBlock({
261
+ accounts: current.accounts,
262
+ modelId: productModelId,
263
+ credentialId: current.credentialId,
264
+ rotationEnabled: current.rotationEnabled,
265
+ activeCredentialId: current.activeCredentialId,
266
+ pinnedCredentialId: current.sessionCodexState.pinnedCredentialId,
267
+ pinSource: current.sessionCodexState.pinSource,
268
+ now: new Date(),
269
+ });
270
+ const blocked = planBlock(leased);
271
+ if (blocked) {
272
+ const rechecks = await Promise.all(
273
+ blocked.slice(0, 4).map((account) =>
274
+ recheckCodexCredentialPlan(db, settings, input.workspaceId, account.id, {
275
+ turnId,
276
+ purpose: "capacity_refresh",
277
+ }).catch(() => null),
278
+ ),
279
+ );
280
+ const planMoved = rechecks.some(
281
+ (recheck) =>
282
+ recheck?.planType != null &&
283
+ codexPlanKey(recheck.planType) !== codexPlanKey(recheck.previousPlanType),
284
+ );
285
+ if (planMoved) {
286
+ leaseAcquisitionStartedAtMs = performance.now();
287
+ leased = await acquireCodexCredentialLease(
288
+ db,
289
+ {
290
+ accountId: input.accountId,
291
+ workspaceId: input.workspaceId,
292
+ sessionId: input.sessionId,
293
+ turnId,
294
+ attemptId: input.attemptId,
295
+ executionGeneration: attempt.executionGeneration,
296
+ workflowId: input.workflowId,
297
+ workflowRunId: input.workflowRunId,
298
+ dispatchId,
299
+ expectedRedispatches: attempt.redispatchesAtDispatch,
300
+ holderId,
301
+ advanceActivePointer: true,
302
+ },
303
+ selectForTurn,
304
+ );
305
+ }
306
+ const stillBlocked = planMoved ? planBlock(leased) : blocked;
307
+ if (stillBlocked) {
308
+ const account = stillBlocked[0]!;
309
+ // Name the plan only when this admission just observed it.
310
+ const observedPlan =
311
+ rechecks[blocked.findIndex((candidate) => candidate.id === account.id)]?.planType ??
312
+ null;
313
+ const payload = codexPlanEntitlementFailurePayload({
314
+ accountLabel: stillBlocked.length === 1 ? codexAccountDisplayLabel(account) : null,
315
+ planType: observedPlan,
316
+ planChanged: false,
317
+ modelId: productModelId,
318
+ });
319
+ if (turn.source === "compaction") {
320
+ if (
321
+ !(await eventing.settle!({
322
+ events: [
323
+ {
324
+ type: "turn.cancelled",
325
+ payload: {
326
+ maintenance: "context_compaction",
327
+ reason: payload.code,
328
+ requestPreserved: true,
329
+ },
330
+ },
331
+ { type: "session.status.changed", payload: { status: "idle" } },
332
+ ],
333
+ turnStatus: "cancelled",
334
+ sessionStatus: "idle",
335
+ activeTurnId: null,
336
+ }))
337
+ ) {
338
+ return { exit: claimedResult({ status: "cancelled" }) };
339
+ }
340
+ control.turnMetricOutcome = "cancelled";
341
+ control.activityStatus = "idle";
342
+ return { exit: claimedResult({ status: "idle", deferredUntilWake: true }) };
343
+ }
344
+ throw new CodexPlanEntitlementError(payload);
345
+ }
346
+ }
200
347
  const lockedSessionCodexState = leased.sessionCodexState;
201
348
  providerTurn.codexPolicySnapshot = leased.codexPolicySnapshot;
202
349
  const sessionPin = lockedSessionCodexState.pinnedCredentialId;
@@ -276,6 +423,7 @@ export async function selectCodexTurnCapacity(
276
423
  }
277
424
  providerTurn.effectiveCodexCredentialId = leased.credentialId;
278
425
  providerTurn.codexCredentialFailoverLimit = leased.failoverLimit;
426
+ providerTurn.codexProductModelId = deps.turnExecutionPolicy.productModelId;
279
427
  leases.codex.generation = leased.generation;
280
428
  leases.codex.confirmedUntilMs =
281
429
  leased.leasedUntil && leaseAcquisitionStartedAtMs !== null
@@ -395,11 +543,12 @@ export async function selectCodexTurnCapacity(
395
543
  help: "Alert signal emitted when the eligible Codex pool is zero or one.",
396
544
  labels: { workspace_key: codexWorkspaceKey, depth: poolDepth },
397
545
  });
398
- observability.warn("Codex eligible credential pool is low", {
546
+ warnCodexPoolLow(observability, {
547
+ workspaceKey: codexWorkspaceKey,
399
548
  workspaceId: input.workspaceId,
400
549
  eligibleCount,
401
550
  connectedCount: leased.accounts.length,
402
- depth: poolDepth,
551
+ depth: poolDepth === "zero" ? "zero" : "one",
403
552
  });
404
553
  }
405
554
 
@@ -466,6 +615,11 @@ export async function selectCodexTurnCapacity(
466
615
  },
467
616
  },
468
617
  );
618
+ if (evaluated.action === "stopped") {
619
+ control.turnMetricOutcome = "failed";
620
+ control.activityStatus = evaluated.sessionStatus === "queued" ? "idle" : "failed";
621
+ return { exit: claimedResult({ status: control.activityStatus }) };
622
+ }
469
623
  if (evaluated.action === "resumed") {
470
624
  control.turnMetricOutcome = "recovering";
471
625
  control.activityStatus = "recovering";
@@ -583,6 +737,11 @@ export async function selectCodexTurnCapacity(
583
737
  },
584
738
  },
585
739
  );
740
+ if (evaluated.action === "stopped") {
741
+ control.turnMetricOutcome = "failed";
742
+ control.activityStatus = evaluated.sessionStatus === "queued" ? "idle" : "failed";
743
+ return { exit: claimedResult({ status: control.activityStatus }) };
744
+ }
586
745
  if (evaluated.action === "resumed") {
587
746
  control.turnMetricOutcome = "recovering";
588
747
  control.activityStatus = "recovering";
@@ -682,6 +841,11 @@ export async function selectCodexTurnCapacity(
682
841
  failurePayload,
683
842
  },
684
843
  );
844
+ if (evaluated.action === "stopped") {
845
+ control.turnMetricOutcome = "failed";
846
+ control.activityStatus = evaluated.sessionStatus === "queued" ? "idle" : "failed";
847
+ return { exit: claimedResult({ status: control.activityStatus }) };
848
+ }
685
849
  if (evaluated.action === "resumed") {
686
850
  control.turnMetricOutcome = "recovering";
687
851
  control.activityStatus = "recovering";