@opengeni/worker-bundle 1.2.1-canary.37222965366001 → 1.3.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.
package/dist/index.js CHANGED
@@ -839,7 +839,7 @@ function temporalWorkflowExecutionNotFound(error) {
839
839
  return isGrpcServiceError(error) && error.code === 5;
840
840
  }
841
841
  async function createDefaultWorkerActivities(role, dependencies = {}) {
842
- return role === "control" ? (await import("./activities-control-QSDPXLLD.js")).createControlActivities(dependencies) : (await import("./activities-turn-BMY3CDKL.js")).createTurnActivities(dependencies);
842
+ return role === "control" ? (await import("./activities-control-PEXBWKQJ.js")).createControlActivities(dependencies) : (await import("./activities-turn-W27A2SBO.js")).createTurnActivities(dependencies);
843
843
  }
844
844
  function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
845
845
  const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/worker-bundle",
3
- "version": "1.2.1-canary.37222965366001",
3
+ "version": "1.3.0",
4
4
  "description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -54,26 +54,26 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@llamaindex/liteparse": "2.14.2",
57
- "@opengeni/agent-proto": "1.2.1-canary.37222965366001",
58
- "@opengeni/capabilities": "1.2.1-canary.37222965366001",
59
- "@opengeni/codemode": "1.2.1-canary.37222965366001",
60
- "@opengeni/codex": "1.2.1-canary.37222965366001",
61
- "@opengeni/config": "1.2.1-canary.37222965366001",
62
- "@opengeni/contracts": "1.2.1-canary.37222965366001",
63
- "@opengeni/core": "1.2.1-canary.37222965366001",
64
- "@opengeni/db": "1.2.1-canary.37222965366001",
65
- "@opengeni/documents": "1.2.1-canary.37222965366001",
66
- "@opengeni/events": "1.2.1-canary.37222965366001",
67
- "@opengeni/github": "1.2.1-canary.37222965366001",
68
- "@opengeni/interaction": "1.2.1-canary.37222965366001",
69
- "@opengeni/jev": "1.2.1-canary.37222965366001",
70
- "@opengeni/network": "1.2.1-canary.37222965366001",
71
- "@opengeni/observability": "1.2.1-canary.37222965366001",
72
- "@opengeni/runtime": "1.2.1-canary.37222965366001",
73
- "@opengeni/sdk": "1.2.1-canary.37222965366001",
74
- "@opengeni/storage": "1.2.1-canary.37222965366001",
75
- "@opengeni/tool-gateway": "1.2.1-canary.37222965366001",
76
- "@opengeni/xai-subscription": "1.2.1-canary.37222965366001",
57
+ "@opengeni/agent-proto": "1.3.0",
58
+ "@opengeni/capabilities": "1.3.0",
59
+ "@opengeni/codemode": "1.3.0",
60
+ "@opengeni/codex": "1.3.0",
61
+ "@opengeni/config": "1.3.0",
62
+ "@opengeni/contracts": "1.3.0",
63
+ "@opengeni/core": "1.3.0",
64
+ "@opengeni/db": "1.3.0",
65
+ "@opengeni/documents": "1.3.0",
66
+ "@opengeni/events": "1.3.0",
67
+ "@opengeni/github": "1.3.0",
68
+ "@opengeni/interaction": "1.3.0",
69
+ "@opengeni/jev": "1.3.0",
70
+ "@opengeni/network": "1.3.0",
71
+ "@opengeni/observability": "1.3.0",
72
+ "@opengeni/runtime": "1.3.0",
73
+ "@opengeni/sdk": "1.3.0",
74
+ "@opengeni/storage": "1.3.0",
75
+ "@opengeni/tool-gateway": "1.3.0",
76
+ "@opengeni/xai-subscription": "1.3.0",
77
77
  "@temporalio/activity": "^1.17.0",
78
78
  "@temporalio/client": "^1.17.0",
79
79
  "@temporalio/worker": "^1.17.0",
@@ -757,8 +757,8 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
757
757
  // tool. Gateway Responses routes likewise expose ordinary function
758
758
  // tools, not OpenAI-hosted sandbox tools. Tell buildAgent to use
759
759
  // function apply_patch and wrap successful view_image results as
760
- // typed input_image content. Chat wires have no proven typed image
761
- // result transport and therefore receive no view_image tool.
760
+ // typed input_image content. The Chat adapter projects tool images
761
+ // into a labelled image envelope after the paired tool results.
762
762
  structuredToolTransport: structuredToolTransportForTurn(resolvedModel),
763
763
  ...(promptCacheKey ? { promptCacheKey } : {}),
764
764
  }
@@ -7,6 +7,7 @@ import {
7
7
  getHumanInputResumeForEvent,
8
8
  getSessionHumanInputRequest,
9
9
  getWorkspace,
10
+ listTurnCodemodeApprovals,
10
11
  recordUsageEvent,
11
12
  registerPendingSessionToolCall,
12
13
  recordPendingSessionToolCallResult,
@@ -77,6 +78,7 @@ import {
77
78
  generatedImageFromSdkEvent,
78
79
  isCompletedGeneratedImageSdkEvent,
79
80
  } from "../generated-images";
81
+ import { programmaticApproval, programmaticContinuationNote } from "../programmatic-approvals";
80
82
  import { ToolResultSpill } from "./tool-result-spill";
81
83
  import { ownedTurnSandboxForAgent } from "./turn-sandbox-access";
82
84
  import { createTurnCredentialLeases } from "./credential-leases";
@@ -449,6 +451,8 @@ export async function runTurnStreamAttempt(
449
451
  await parallelSessionTitle?.cancel();
450
452
  };
451
453
  let runInput: Awaited<ReturnType<typeof turnInput>>["input"] | null = null;
454
+ let codemodeContinuationNote: string | undefined;
455
+ let programmaticApprovalAcknowledged = false;
452
456
  const prepareRunAttemptInput = async () => {
453
457
  const historyPreparationStartedAt = performance.now();
454
458
  let historyPreparationOutcome: "completed" | "failed" = "completed";
@@ -461,6 +465,8 @@ export async function runTurnStreamAttempt(
461
465
  subjectId: fileAuthoritySubjectId,
462
466
  },
463
467
  recovering: turn.executionGeneration > 1,
468
+ programmaticApprovalAcknowledged,
469
+ ...(codemodeContinuationNote ? { codemodeContinuationNote } : {}),
464
470
  ...(unavailableSandboxFilesNote ? { unavailableSandboxFilesNote } : {}),
465
471
  ...(runCredentialsNote ? { runCredentialsNote } : {}),
466
472
  ...(mcpAvailabilityNote ? { mcpAvailabilityNote } : {}),
@@ -1656,10 +1662,21 @@ export async function runTurnStreamAttempt(
1656
1662
  });
1657
1663
  }
1658
1664
  }
1659
- if (eventing.stream.interruptions.length > 0) {
1665
+ const programmaticPending = (
1666
+ await listTurnCodemodeApprovals(db, {
1667
+ accountId: input.accountId,
1668
+ workspaceId: input.workspaceId,
1669
+ sessionId: input.sessionId,
1670
+ turnId: activeTurnId,
1671
+ })
1672
+ )
1673
+ .filter((operation) => operation.state === "waiting_for_approval")
1674
+ .map(programmaticApproval);
1675
+ if (eventing.stream.interruptions.length > 0 || programmaticPending.length > 0) {
1660
1676
  await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
1661
1677
  await historySink.reconcileConversationTruth({ requireDurable: true });
1662
- const approvals = runtime.serializeApprovals(eventing.stream.interruptions);
1678
+ const sdkApprovals = runtime.serializeApprovals(eventing.stream.interruptions);
1679
+ const approvals = [...sdkApprovals, ...programmaticPending];
1663
1680
  const humanInputInterruptions =
1664
1681
  runtime.serializeHumanInputRequests?.(eventing.stream.interruptions) ?? [];
1665
1682
  const interactionInterventionInterruptions =
@@ -1739,28 +1756,34 @@ export async function runTurnStreamAttempt(
1739
1756
  const interruptionCallIds = interruptionCallIdsFromPause({
1740
1757
  humanInputRequests,
1741
1758
  interactionInterventionRequests,
1742
- pendingApprovals,
1759
+ pendingApprovals: [
1760
+ ...sdkApprovals,
1761
+ ...interactionInterventionInterruptions.map((interruption) => interruption.approval),
1762
+ ],
1743
1763
  });
1744
1764
  assertOpenSuffixResumable(suffixMembers, interruptionCallIds);
1745
1765
  const suffixByCallId = new Map(suffixMembers.map((member) => [member.callId, member]));
1746
- const attached = await attachOpenSuffixToPendingToolCalls(db, {
1747
- accountId: input.accountId,
1748
- workspaceId: input.workspaceId,
1749
- sessionId: input.sessionId,
1750
- turnId: activeTurnId,
1751
- executionGeneration: attempt.executionGeneration,
1752
- attemptId: input.attemptId,
1753
- members: interruptionCallIds.map((callId) => {
1754
- const member = suffixByCallId.get(callId)!;
1755
- return {
1756
- callId,
1757
- interruptionKind: interruptionKindForCallItem(
1758
- member.callItem as Record<string, unknown>,
1759
- ),
1760
- reasoningItems: member.reasoningItems as Array<Record<string, unknown>>,
1761
- };
1762
- }),
1763
- });
1766
+ const attached =
1767
+ interruptionCallIds.length === 0
1768
+ ? { accepted: true }
1769
+ : await attachOpenSuffixToPendingToolCalls(db, {
1770
+ accountId: input.accountId,
1771
+ workspaceId: input.workspaceId,
1772
+ sessionId: input.sessionId,
1773
+ turnId: activeTurnId,
1774
+ executionGeneration: attempt.executionGeneration,
1775
+ attemptId: input.attemptId,
1776
+ members: interruptionCallIds.map((callId) => {
1777
+ const member = suffixByCallId.get(callId)!;
1778
+ return {
1779
+ callId,
1780
+ interruptionKind: interruptionKindForCallItem(
1781
+ member.callItem as Record<string, unknown>,
1782
+ ),
1783
+ reasoningItems: member.reasoningItems as Array<Record<string, unknown>>,
1784
+ };
1785
+ }),
1786
+ });
1764
1787
  if (!attached.accepted) {
1765
1788
  return claimedResult({ status: "cancelled" });
1766
1789
  }
@@ -1935,7 +1958,29 @@ export async function runTurnStreamAttempt(
1935
1958
  return claimedResult({ status: "idle" });
1936
1959
  };
1937
1960
 
1961
+ // Recover committed waits/results before any model dispatch, including crash replacement.
1962
+ // A new attempt uses its own signed caller; origin provenance never changes.
1963
+ await eventing.toolPreparationReady;
1964
+ const codemodeOperations =
1965
+ (await eventing.codemodeDispatcher?.resumeApproved(
1966
+ `sandbox:${input.attemptId}`,
1967
+ trigger.type === "user.approvalDecision"
1968
+ ? (trigger.payload as { approvalId: string }).approvalId
1969
+ : undefined,
1970
+ )) ?? [];
1971
+ codemodeContinuationNote = programmaticContinuationNote(codemodeOperations);
1972
+ programmaticApprovalAcknowledged =
1973
+ trigger.type === "user.approvalDecision" &&
1974
+ codemodeOperations.some(
1975
+ (operation) =>
1976
+ operation.operationId === (trigger.payload as { approvalId?: unknown }).approvalId,
1977
+ );
1978
+ const pendingProgrammaticApprovals = codemodeOperations
1979
+ .filter((operation) => operation.state === "waiting_for_approval")
1980
+ .map(programmaticApproval);
1938
1981
  const openSuffixResume = await settleOpenSuffixResumeIfNeeded({
1982
+ additionalApprovals: pendingProgrammaticApprovals,
1983
+ programmaticApprovalAcknowledged,
1939
1984
  db,
1940
1985
  agent,
1941
1986
  accountId: input.accountId,
@@ -1234,6 +1234,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
1234
1234
  {},
1235
1235
  (name) => mcpToolDisplayMetadata(tools.mcpServers, name),
1236
1236
  (entry) => toolFamilyForCatalogIdentity(entry, runSettings.mcpServers),
1237
+ tools.inputWaitYield,
1237
1238
  );
1238
1239
  eventing.codemodeDispatcher.start();
1239
1240
  };
@@ -294,12 +294,8 @@ export function modelAttachmentInputPolicyForTurn(
294
294
  };
295
295
  } | null,
296
296
  ): ModelAttachmentInputPolicy {
297
- const typedTransport =
298
- resolvedModel === null ||
299
- resolvedModel.provider.api === "responses" ||
300
- resolvedModel.provider.api === "anthropic-messages";
301
297
  return {
302
- supportsImageInput: typedTransport && modelSupportsImageInputForTurn(resolvedModel),
298
+ supportsImageInput: modelSupportsImageInputForTurn(resolvedModel),
303
299
  inputFileMediaTypes: [],
304
300
  };
305
301
  }
@@ -1,5 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { bindMcpTelemetry, measureMcpPhase, withMcpCallIdentity } from "@opengeni/observability";
2
+ import {
3
+ bindMcpTelemetry,
4
+ measureMcpPhase,
5
+ withMcpCallIdentity,
6
+ recordToolApproval,
7
+ } from "@opengeni/observability";
3
8
  import {
4
9
  AttemptToolApprovalRequiredError,
5
10
  AttemptToolCatalogStaleError,
@@ -15,13 +20,18 @@ import {
15
20
  CODEMODE_CLAIM_LEASE_MS,
16
21
  CODEMODE_CLAIM_HEARTBEAT_MS,
17
22
  CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT,
18
- type CodemodeOperation,
23
+ CodemodeOperation,
19
24
  type SessionEvent,
20
25
  type ToolDisplayMetadata,
21
26
  type ToolFamily,
22
27
  } from "@opengeni/contracts";
23
28
  import {
24
29
  cancelQueuedCodemodeOperationsForAttempt,
30
+ waitForCodemodeApproval,
31
+ bindCodemodePreparation,
32
+ listTurnCodemodeApprovals,
33
+ adoptCodemodeApprovalWithOutput,
34
+ readTurnCodemodeOperation,
25
35
  claimCodemodeOperation,
26
36
  failCodemodeOperation,
27
37
  getSessionEventByClientEventId,
@@ -76,6 +86,7 @@ export class CodemodeAttemptDispatcher {
76
86
  private readonly toolDisplayMetadata?: (modelName: string) => ToolDisplayMetadata | undefined,
77
87
  /** Content-free analytics family for a catalog entry; event enrichment only. */
78
88
  private readonly toolFamily?: (entry: AttemptToolCatalogEntry) => ToolFamily | null,
89
+ private readonly approvalWait?: { beginWait(): (accepted: boolean) => void },
79
90
  ) {
80
91
  if (
81
92
  environment.catalog.accountId !== scope.accountId ||
@@ -179,6 +190,94 @@ export class CodemodeAttemptDispatcher {
179
190
  );
180
191
  }
181
192
 
193
+ async resumeApproved(
194
+ callerSubjectId: string,
195
+ decisionOperationId?: string,
196
+ ): Promise<CodemodeOperation[]> {
197
+ const operations = await listTurnCodemodeApprovals(this.db, this.scope);
198
+ if (
199
+ decisionOperationId &&
200
+ // Model-tool approval IDs are opaque strings. Only programmatic UUIDs
201
+ // can address the Codemode operation journal.
202
+ CodemodeOperation.shape.operationId.safeParse(decisionOperationId).success &&
203
+ !operations.some((operation) => operation.operationId === decisionOperationId)
204
+ ) {
205
+ const decided = await readTurnCodemodeOperation(this.db, {
206
+ ...this.scope,
207
+ callerSubjectId,
208
+ operationId: decisionOperationId,
209
+ });
210
+ if (decided) operations.push(decided);
211
+ }
212
+ for (const operation of operations) {
213
+ if (!["waiting_for_approval", "queued", "running"].includes(operation.state)) continue;
214
+ let effectDigest: string | null = null;
215
+ try {
216
+ effectDigest = this.environment.effectDigest(operation.identity);
217
+ } catch {
218
+ /* Removed tool: stale. */
219
+ }
220
+ const continuation = await adoptCodemodeApprovalWithOutput(this.db, {
221
+ ...this.scope,
222
+ operationId: operation.operationId,
223
+ callerSubjectId,
224
+ catalogDigest: this.environment.catalog.digest,
225
+ effectDigest,
226
+ });
227
+ await publishDurableSessionEvents(
228
+ this.bus,
229
+ this.scope.workspaceId,
230
+ this.scope.sessionId,
231
+ continuation.events,
232
+ );
233
+ const adopted = continuation.operation;
234
+ if (adopted && adopted.state !== operation.state) {
235
+ const outcome =
236
+ adopted.state === "queued"
237
+ ? "resumed"
238
+ : adopted.state === "outcome_unknown"
239
+ ? "unknown"
240
+ : adopted.errorCode === "approval_stale"
241
+ ? "stale"
242
+ : "rejected";
243
+ recordToolApproval(
244
+ outcome,
245
+ "continuation",
246
+ operation.state === "waiting_for_approval"
247
+ ? Math.max(0, Date.now() - Date.parse(operation.updatedAt))
248
+ : undefined,
249
+ );
250
+ }
251
+ if (adopted?.state !== "queued") continue;
252
+ const claim = await claimCodemodeOperation(this.db, {
253
+ ...this.scope,
254
+ operationId: adopted.operationId,
255
+ claimId: randomUUID(),
256
+ catalogDigest: this.environment.catalog.digest,
257
+ claimLeaseMs: this.claimLeaseMs,
258
+ });
259
+ if (claim.status === "claimed") {
260
+ const execution = this.execute(claim.operation, claim.claimId, true);
261
+ this.inFlight.set(adopted.operationId, execution);
262
+ try {
263
+ await execution;
264
+ } finally {
265
+ this.inFlight.delete(adopted.operationId);
266
+ }
267
+ }
268
+ }
269
+ const receipts: CodemodeOperation[] = [];
270
+ for (const operation of operations) {
271
+ const receipt = await readTurnCodemodeOperation(this.db, {
272
+ ...this.scope,
273
+ callerSubjectId,
274
+ operationId: operation.operationId,
275
+ });
276
+ if (receipt) receipts.push(receipt);
277
+ }
278
+ return receipts;
279
+ }
280
+
182
281
  async close(reason = "Execution attempt ended"): Promise<void> {
183
282
  if (this.closing) {
184
283
  await Promise.allSettled([...this.inFlight.values()]);
@@ -277,18 +376,56 @@ export class CodemodeAttemptDispatcher {
277
376
  }
278
377
 
279
378
  let preparedCall: Awaited<ReturnType<AttemptToolEnvironment["prepareCall"]>>;
379
+ let releaseWait: ((accepted: boolean) => void) | undefined;
280
380
  try {
381
+ if (operation.durableApproval) {
382
+ if (!this.approvalWait) throw new Error("Durable approval yield is unavailable");
383
+ releaseWait = this.approvalWait.beginWait();
384
+ if (
385
+ !(await bindCodemodePreparation(this.db, {
386
+ ...this.scope,
387
+ operationId: operation.operationId,
388
+ claimId,
389
+ effectDigest: this.environment.effectDigest(operation.identity),
390
+ }))
391
+ )
392
+ throw new Error("Operation preparation is stale");
393
+ }
281
394
  preparedCall = await this.environment.prepareCall(
282
395
  {
283
396
  operationId: operation.operationId,
284
- catalogDigest: operation.catalogDigest,
397
+ catalogDigest: this.environment.catalog.digest,
285
398
  identity: operation.identity,
286
399
  arguments: operation.arguments,
287
- caller: operation.caller,
400
+ caller: {
401
+ kind: "codemode",
402
+ subjectId:
403
+ operation.caller.subjectId === `sandbox:${operation.attemptId}`
404
+ ? `sandbox:${this.scope.attemptId}`
405
+ : operation.caller.subjectId,
406
+ },
407
+ },
408
+ {
409
+ signal,
410
+ ...(operation.durableApproval ? { transportMeta: { durableApproval: true } } : {}),
288
411
  },
289
- { signal },
290
412
  );
413
+ if (preparedCall.waitingForApproval) {
414
+ const waiting = await waitForCodemodeApproval(this.db, {
415
+ ...this.scope,
416
+ operationId: operation.operationId,
417
+ claimId,
418
+ ...preparedCall.waitingForApproval,
419
+ effectDigest: preparedCall.effectDigest!,
420
+ });
421
+ if (!waiting) throw new Error("Approval could not be bound to the operation");
422
+ recordToolApproval("waiting");
423
+ releaseWait?.(true);
424
+ return;
425
+ }
426
+ releaseWait?.(false);
291
427
  } catch (error) {
428
+ releaseWait?.(false);
292
429
  await this.settleWithOutput(operation, claimId, {
293
430
  state: "failed",
294
431
  errorCode: errorCode(error, signal, false),
@@ -448,6 +585,7 @@ export class CodemodeAttemptDispatcher {
448
585
  settlement,
449
586
  });
450
587
  if (!result.committed || result.events.length === 0) return;
588
+ recordToolApproval(settlement.state === "outcome_unknown" ? "unknown" : settlement.state);
451
589
  await publishDurableSessionEvents(
452
590
  this.bus,
453
591
  this.scope.workspaceId,
@@ -490,8 +628,8 @@ function assertMatchingCodemodeToolCallCreated(
490
628
  if (
491
629
  event.type !== "agent.toolCall.created" ||
492
630
  event.turnId !== scope.turnId ||
493
- event.turnGeneration !== scope.executionGeneration ||
494
- event.turnAttemptId !== scope.attemptId ||
631
+ event.turnGeneration !== operation.executionGeneration ||
632
+ event.turnAttemptId !== operation.attemptId ||
495
633
  event.turnAssociation !== "current" ||
496
634
  payload?.id !== operation.operationId ||
497
635
  payload?.name !== entry.modelName ||