@opengeni/worker-bundle 1.2.1-canary.37222177070001 → 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.
@@ -1,5 +1,5 @@
1
1
  import { type AttemptToolCatalogEntry, type AttemptToolEnvironment } from "@opengeni/codemode";
2
- import { type ToolDisplayMetadata, type ToolFamily } from "@opengeni/contracts";
2
+ import { CodemodeOperation, type ToolDisplayMetadata, type ToolFamily } from "@opengeni/contracts";
3
3
  import { type Database } from "@opengeni/db";
4
4
  import { type EventBus } from "@opengeni/events";
5
5
  export type CodemodeDispatcherScope = {
@@ -29,6 +29,7 @@ export declare class CodemodeAttemptDispatcher {
29
29
  private readonly toolDisplayMetadata?;
30
30
  /** Content-free analytics family for a catalog entry; event enrichment only. */
31
31
  private readonly toolFamily?;
32
+ private readonly approvalWait?;
32
33
  private readonly stopController;
33
34
  private readonly inFlight;
34
35
  private pendingClaims;
@@ -38,8 +39,11 @@ export declare class CodemodeAttemptDispatcher {
38
39
  private readonly claimHeartbeatMs;
39
40
  constructor(db: Database, bus: EventBus, environment: AttemptToolEnvironment, scope: CodemodeDispatcherScope, turnSignal?: AbortSignal | undefined, maxConcurrentCalls?: number, timings?: CodemodeDispatcherTimings, toolDisplayMetadata?: ((modelName: string) => ToolDisplayMetadata | undefined) | undefined,
40
41
  /** Content-free analytics family for a catalog entry; event enrichment only. */
41
- toolFamily?: ((entry: AttemptToolCatalogEntry) => ToolFamily | null) | undefined);
42
+ toolFamily?: ((entry: AttemptToolCatalogEntry) => ToolFamily | null) | undefined, approvalWait?: {
43
+ beginWait(): (accepted: boolean) => void;
44
+ } | undefined);
42
45
  start(): void;
46
+ resumeApproved(callerSubjectId: string, decisionOperationId?: string): Promise<CodemodeOperation[]>;
43
47
  close(reason?: string): Promise<void>;
44
48
  private execute;
45
49
  private executeClaimed;
@@ -54,6 +54,8 @@ export declare function resultItemForOpenSuffixMember(input: {
54
54
  eventOutput: unknown;
55
55
  }>;
56
56
  export declare function settleOpenSuffixResumeIfNeeded(input: {
57
+ additionalApprovals?: unknown[];
58
+ programmaticApprovalAcknowledged?: boolean;
57
59
  db: Database;
58
60
  agent: Agent<any, any>;
59
61
  accountId: string;
@@ -0,0 +1,10 @@
1
+ import type { CodemodeOperation } from "@opengeni/contracts";
2
+ /** Programmatic receipts are not open SDK calls. Exact arguments stay in protected storage. */
3
+ export declare function programmaticApproval(operation: CodemodeOperation): {
4
+ id: string;
5
+ name: string;
6
+ source: "codemode";
7
+ operationId: string;
8
+ requestId: string | undefined;
9
+ };
10
+ export declare function programmaticContinuationNote(operations: readonly CodemodeOperation[]): string | undefined;
@@ -32,6 +32,8 @@ export type TurnInputOptions = {
32
32
  unavailableSandboxFilesNote?: string;
33
33
  runCredentialsNote?: string;
34
34
  mcpAvailabilityNote?: string;
35
+ codemodeContinuationNote?: string;
36
+ programmaticApprovalAcknowledged?: boolean;
35
37
  knowledgeSourcePreparationNote?: string;
36
38
  providerApi: HistoryProviderApi;
37
39
  projectCanonicalHistory?: ModelHistoryAttachmentProjector;
@@ -34,7 +34,7 @@ import {
34
34
  warnDrainSnapshotFailure,
35
35
  withFirstPartyTools,
36
36
  workflowIdForSession
37
- } from "./chunk-JKMG5ZHH.js";
37
+ } from "./chunk-QUX5LT2B.js";
38
38
  import {
39
39
  inspectOpenSandboxKubernetesInventory,
40
40
  recordActiveUserGauges,
@@ -8813,4 +8813,4 @@ export {
8813
8813
  createControlActivities,
8814
8814
  createControlActivitiesFromServices
8815
8815
  };
8816
- //# sourceMappingURL=activities-control-QSDPXLLD.js.map
8816
+ //# sourceMappingURL=activities-control-PEXBWKQJ.js.map
@@ -99,7 +99,7 @@ import {
99
99
  withFirstPartyTools,
100
100
  wrapLazyTurnBoxWithRouting,
101
101
  wrapTurnBoxWithRouting
102
- } from "./chunk-JKMG5ZHH.js";
102
+ } from "./chunk-QUX5LT2B.js";
103
103
  import {
104
104
  StreamTimingMetrics,
105
105
  makeMachineOpObserver,
@@ -7654,6 +7654,7 @@ import {
7654
7654
  sessionTurnHasFinalReplyNudge,
7655
7655
  getSessionHumanInputRequest,
7656
7656
  getWorkspace,
7657
+ listTurnCodemodeApprovals,
7657
7658
  recordUsageEvent as recordUsageEvent3,
7658
7659
  registerPendingSessionToolCall,
7659
7660
  recordPendingSessionToolCallResult as recordPendingSessionToolCallResult2,
@@ -8080,6 +8081,27 @@ async function retentionTokenCounts(items, project2) {
8080
8081
  return counts;
8081
8082
  }
8082
8083
 
8084
+ // src/activities/programmatic-approvals.ts
8085
+ function programmaticApproval(operation) {
8086
+ return {
8087
+ id: operation.operationId,
8088
+ name: operation.identity.toolName,
8089
+ source: "codemode",
8090
+ operationId: operation.operationId,
8091
+ requestId: operation.approvalRequestId
8092
+ };
8093
+ }
8094
+ function programmaticContinuationNote(operations) {
8095
+ if (operations.length === 0) return void 0;
8096
+ return [
8097
+ "Stored programmatic operations (durable server receipts):",
8098
+ ...operations.map(
8099
+ (operation) => `${operation.operationId}: ${operation.state}${operation.errorCode ? ` (${operation.errorCode})` : ""}`
8100
+ ),
8101
+ "Approval resumes the stored operation, not the earlier program's JavaScript stack. Read results with the current Codemode client's status/resume handle. Do not resubmit arguments for completed, running, waiting, or uncertain operations."
8102
+ ].join("\n");
8103
+ }
8104
+
8083
8105
  // src/activities/agent-turn/turn-sandbox-access.ts
8084
8106
  function ownedTurnSandboxForAgent(sandboxState) {
8085
8107
  return sandboxState.lazyOwnedSandbox ?? sandboxState.resolvedSandbox?.established;
@@ -8263,7 +8285,7 @@ async function resultItemForOpenSuffixMember(input) {
8263
8285
  }
8264
8286
  }
8265
8287
  async function settleOpenSuffixResumeIfNeeded(input) {
8266
- if (input.trigger.type !== "user.approvalDecision" && input.trigger.type !== "user.humanInputResponse") {
8288
+ if (input.trigger.type !== "user.approvalDecision" && input.trigger.type !== "user.humanInputResponse" && !input.additionalApprovals?.length) {
8267
8289
  return { action: "continue" };
8268
8290
  }
8269
8291
  const rows = await listTurnOpenSuffixToolCalls(
@@ -8272,7 +8294,7 @@ async function settleOpenSuffixResumeIfNeeded(input) {
8272
8294
  input.sessionId,
8273
8295
  input.turnId
8274
8296
  );
8275
- if (rows.length === 0) {
8297
+ if (rows.length === 0 && !input.additionalApprovals?.length) {
8276
8298
  return { action: "continue" };
8277
8299
  }
8278
8300
  const callId = matchingOpenSuffixCallId({
@@ -8280,96 +8302,108 @@ async function settleOpenSuffixResumeIfNeeded(input) {
8280
8302
  humanInputToolCallId: input.humanInputResume?.toolCallId
8281
8303
  });
8282
8304
  const target = resolveOpenSuffixResumeTarget(rows, callId);
8283
- if (!target || !callId) {
8305
+ if ((!target || !callId) && !input.programmaticApprovalAcknowledged && (input.trigger.type === "user.approvalDecision" || input.trigger.type === "user.humanInputResponse" || !input.additionalApprovals?.length)) {
8284
8306
  throw new Error("Open suffix resume event does not match an interruption");
8285
8307
  }
8286
- const truncationTokens = input.modelToolOutputTruncationTokens ?? target.modelToolOutputTruncationTokens ?? void 0;
8287
- let resultItem = target.resultItem;
8288
- let eventOutput = resultItem?.output;
8289
- if (!resultItem) {
8290
- const member = await resultItemForOpenSuffixMember({
8291
- agent: input.agent,
8292
- row: target,
8293
- trigger: input.trigger,
8294
- humanInputResume: input.humanInputResume
8295
- });
8296
- resultItem = member.resultItem;
8297
- eventOutput = member.eventOutput;
8298
- const recorded = await recordPendingSessionToolCallResult(input.db, {
8299
- accountId: input.accountId,
8300
- workspaceId: input.workspaceId,
8301
- sessionId: input.sessionId,
8302
- turnId: input.turnId,
8303
- executionGeneration: input.executionGeneration,
8304
- attemptId: input.attemptId,
8305
- callId: target.callId,
8306
- ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8307
- resultItem,
8308
- eventOutput
8309
- });
8310
- if (!recorded.accepted) {
8311
- return { action: "cancelled" };
8312
- }
8313
- if (!recorded.recorded) {
8314
- const latest = resolveOpenSuffixResumeTarget(
8315
- await listTurnOpenSuffixToolCalls(
8316
- input.db,
8317
- input.workspaceId,
8318
- input.sessionId,
8319
- input.turnId
8320
- ),
8321
- callId
8322
- );
8323
- resultItem = latest?.resultItem ?? resultItem;
8324
- } else if (input.publish) {
8325
- await input.publish([
8326
- {
8327
- type: "agent.toolCall.output",
8328
- payload: { id: target.callId, output: eventOutput }
8329
- }
8330
- ]);
8308
+ if (target && callId) {
8309
+ const truncationTokens = input.modelToolOutputTruncationTokens ?? target.modelToolOutputTruncationTokens ?? void 0;
8310
+ let resultItem = target.resultItem;
8311
+ let eventOutput = resultItem?.output;
8312
+ if (!resultItem) {
8313
+ const member = await resultItemForOpenSuffixMember({
8314
+ agent: input.agent,
8315
+ row: target,
8316
+ trigger: input.trigger,
8317
+ humanInputResume: input.humanInputResume
8318
+ });
8319
+ resultItem = member.resultItem;
8320
+ eventOutput = member.eventOutput;
8321
+ const recorded = await recordPendingSessionToolCallResult(input.db, {
8322
+ accountId: input.accountId,
8323
+ workspaceId: input.workspaceId,
8324
+ sessionId: input.sessionId,
8325
+ turnId: input.turnId,
8326
+ executionGeneration: input.executionGeneration,
8327
+ attemptId: input.attemptId,
8328
+ callId: target.callId,
8329
+ ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8330
+ resultItem,
8331
+ eventOutput
8332
+ });
8333
+ if (!recorded.accepted) {
8334
+ return { action: "cancelled" };
8335
+ }
8336
+ if (!recorded.recorded) {
8337
+ const latest = resolveOpenSuffixResumeTarget(
8338
+ await listTurnOpenSuffixToolCalls(
8339
+ input.db,
8340
+ input.workspaceId,
8341
+ input.sessionId,
8342
+ input.turnId
8343
+ ),
8344
+ callId
8345
+ );
8346
+ resultItem = latest?.resultItem ?? resultItem;
8347
+ } else if (input.publish) {
8348
+ await input.publish([
8349
+ {
8350
+ type: "agent.toolCall.output",
8351
+ payload: { id: target.callId, output: eventOutput }
8352
+ }
8353
+ ]);
8354
+ }
8331
8355
  }
8332
- }
8333
- if (!resultItem) {
8334
- throw new Error(`Open suffix resume for ${target.callId} has no result item`);
8335
- }
8336
- const history = await getActiveSessionHistoryItems(input.db, input.workspaceId, input.sessionId);
8337
- if (!openSuffixPairPresentInHistory(
8338
- history.map((row) => row.item),
8339
- callId
8340
- )) {
8341
- const historyItems = openSuffixHistoryItems(target, resultItem);
8342
- if (historyItems.length === 0) {
8343
- throw new Error(`Open suffix resume for ${target.callId} produced no paired history`);
8356
+ if (!resultItem) {
8357
+ throw new Error(`Open suffix resume for ${target.callId} has no result item`);
8344
8358
  }
8345
- const nextPosition = await nextSessionHistoryPosition(
8359
+ const history = await getActiveSessionHistoryItems(
8346
8360
  input.db,
8347
8361
  input.workspaceId,
8348
8362
  input.sessionId
8349
8363
  );
8350
- const appended = await appendSessionHistoryItems2(input.db, {
8351
- accountId: input.accountId,
8352
- workspaceId: input.workspaceId,
8353
- sessionId: input.sessionId,
8354
- turnId: input.turnId,
8355
- expectedExecutionGeneration: input.executionGeneration,
8356
- expectedAttemptId: input.attemptId,
8357
- ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8358
- items: historyItems.map((item, offset) => ({
8359
- position: nextPosition + offset,
8360
- item
8361
- }))
8362
- });
8363
- if (!appended) {
8364
- return { action: "cancelled" };
8364
+ if (!openSuffixPairPresentInHistory(
8365
+ history.map((row) => row.item),
8366
+ callId
8367
+ )) {
8368
+ const historyItems = openSuffixHistoryItems(target, resultItem);
8369
+ if (historyItems.length === 0) {
8370
+ throw new Error(`Open suffix resume for ${target.callId} produced no paired history`);
8371
+ }
8372
+ const nextPosition = await nextSessionHistoryPosition(
8373
+ input.db,
8374
+ input.workspaceId,
8375
+ input.sessionId
8376
+ );
8377
+ const appended = await appendSessionHistoryItems2(input.db, {
8378
+ accountId: input.accountId,
8379
+ workspaceId: input.workspaceId,
8380
+ sessionId: input.sessionId,
8381
+ turnId: input.turnId,
8382
+ expectedExecutionGeneration: input.executionGeneration,
8383
+ expectedAttemptId: input.attemptId,
8384
+ ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8385
+ items: historyItems.map((item, offset) => ({
8386
+ position: nextPosition + offset,
8387
+ item
8388
+ }))
8389
+ });
8390
+ if (!appended) {
8391
+ return { action: "cancelled" };
8392
+ }
8365
8393
  }
8366
8394
  }
8367
8395
  const remaining = (await listTurnOpenSuffixToolCalls(input.db, input.workspaceId, input.sessionId, input.turnId)).filter((row) => row.resultItem == null);
8368
- if (remaining.length === 0) {
8396
+ if (remaining.length === 0 && !input.additionalApprovals?.length) {
8369
8397
  return { action: "continue" };
8370
8398
  }
8371
- const requiresActionApprovals = remainingPendingApprovalsFromSuffix(remaining);
8372
- const pendingApprovals = remainingRunStatePendingApprovalsFromSuffix(remaining);
8399
+ const requiresActionApprovals = [
8400
+ ...remainingPendingApprovalsFromSuffix(remaining),
8401
+ ...input.additionalApprovals ?? []
8402
+ ];
8403
+ const pendingApprovals = [
8404
+ ...remainingRunStatePendingApprovalsFromSuffix(remaining),
8405
+ ...input.additionalApprovals ?? []
8406
+ ];
8373
8407
  const settled = await input.settle({
8374
8408
  events: [
8375
8409
  ...requiresActionApprovals.length > 0 ? [
@@ -8997,6 +9031,8 @@ async function runTurnStreamAttempt(deps) {
8997
9031
  await parallelSessionTitle?.cancel();
8998
9032
  };
8999
9033
  let runInput = null;
9034
+ let codemodeContinuationNote;
9035
+ let programmaticApprovalAcknowledged = false;
9000
9036
  const prepareRunAttemptInput = async () => {
9001
9037
  const historyPreparationStartedAt = performance.now();
9002
9038
  let historyPreparationOutcome = "completed";
@@ -9009,6 +9045,8 @@ async function runTurnStreamAttempt(deps) {
9009
9045
  subjectId: fileAuthoritySubjectId
9010
9046
  },
9011
9047
  recovering: turn.executionGeneration > 1,
9048
+ programmaticApprovalAcknowledged,
9049
+ ...codemodeContinuationNote ? { codemodeContinuationNote } : {},
9012
9050
  ...unavailableSandboxFilesNote ? { unavailableSandboxFilesNote } : {},
9013
9051
  ...runCredentialsNote ? { runCredentialsNote } : {},
9014
9052
  ...mcpAvailabilityNote ? { mcpAvailabilityNote } : {},
@@ -9967,10 +10005,17 @@ async function runTurnStreamAttempt(deps) {
9967
10005
  });
9968
10006
  }
9969
10007
  }
9970
- if (eventing.stream.interruptions.length > 0) {
10008
+ const programmaticPending = (await listTurnCodemodeApprovals(db, {
10009
+ accountId: input.accountId,
10010
+ workspaceId: input.workspaceId,
10011
+ sessionId: input.sessionId,
10012
+ turnId: activeTurnId
10013
+ })).filter((operation) => operation.state === "waiting_for_approval").map(programmaticApproval);
10014
+ if (eventing.stream.interruptions.length > 0 || programmaticPending.length > 0) {
9971
10015
  await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
9972
10016
  await historySink.reconcileConversationTruth({ requireDurable: true });
9973
- const approvals = runtime.serializeApprovals(eventing.stream.interruptions);
10017
+ const sdkApprovals = runtime.serializeApprovals(eventing.stream.interruptions);
10018
+ const approvals = [...sdkApprovals, ...programmaticPending];
9974
10019
  const humanInputInterruptions = runtime.serializeHumanInputRequests?.(eventing.stream.interruptions) ?? [];
9975
10020
  const interactionInterventionInterruptions = runtime.serializeInteractionInterventionRequests?.(eventing.stream.interruptions) ?? [];
9976
10021
  const latestWorkspace = await getWorkspace(db, input.workspaceId);
@@ -10042,11 +10087,14 @@ async function runTurnStreamAttempt(deps) {
10042
10087
  const interruptionCallIds = interruptionCallIdsFromPause({
10043
10088
  humanInputRequests,
10044
10089
  interactionInterventionRequests,
10045
- pendingApprovals
10090
+ pendingApprovals: [
10091
+ ...sdkApprovals,
10092
+ ...interactionInterventionInterruptions.map((interruption) => interruption.approval)
10093
+ ]
10046
10094
  });
10047
10095
  assertOpenSuffixResumable(suffixMembers, interruptionCallIds);
10048
10096
  const suffixByCallId = new Map(suffixMembers.map((member) => [member.callId, member]));
10049
- const attached = await attachOpenSuffixToPendingToolCalls(db, {
10097
+ const attached = interruptionCallIds.length === 0 ? { accepted: true } : await attachOpenSuffixToPendingToolCalls(db, {
10050
10098
  accountId: input.accountId,
10051
10099
  workspaceId: input.workspaceId,
10052
10100
  sessionId: input.sessionId,
@@ -10203,7 +10251,19 @@ async function runTurnStreamAttempt(deps) {
10203
10251
  control.activityStatus = "idle";
10204
10252
  return claimedResult({ status: "idle" });
10205
10253
  };
10254
+ await eventing.toolPreparationReady;
10255
+ const codemodeOperations = await eventing.codemodeDispatcher?.resumeApproved(
10256
+ `sandbox:${input.attemptId}`,
10257
+ trigger.type === "user.approvalDecision" ? trigger.payload.approvalId : void 0
10258
+ ) ?? [];
10259
+ codemodeContinuationNote = programmaticContinuationNote(codemodeOperations);
10260
+ programmaticApprovalAcknowledged = trigger.type === "user.approvalDecision" && codemodeOperations.some(
10261
+ (operation) => operation.operationId === trigger.payload.approvalId
10262
+ );
10263
+ const pendingProgrammaticApprovals = codemodeOperations.filter((operation) => operation.state === "waiting_for_approval").map(programmaticApproval);
10206
10264
  const openSuffixResume = await settleOpenSuffixResumeIfNeeded({
10265
+ additionalApprovals: pendingProgrammaticApprovals,
10266
+ programmaticApprovalAcknowledged,
10207
10267
  db,
10208
10268
  agent,
10209
10269
  accountId: input.accountId,
@@ -12486,9 +12546,8 @@ function modelSupportsImageInputForTurn(resolvedModel) {
12486
12546
  return resolvedModel === null || resolvedModel.configured.capabilities?.inputModalities.includes("image") !== false;
12487
12547
  }
12488
12548
  function modelAttachmentInputPolicyForTurn(resolvedModel) {
12489
- const typedTransport = resolvedModel === null || resolvedModel.provider.api === "responses" || resolvedModel.provider.api === "anthropic-messages";
12490
12549
  return {
12491
- supportsImageInput: typedTransport && modelSupportsImageInputForTurn(resolvedModel),
12550
+ supportsImageInput: modelSupportsImageInputForTurn(resolvedModel),
12492
12551
  inputFileMediaTypes: []
12493
12552
  };
12494
12553
  }
@@ -32894,7 +32953,12 @@ import { allowedFirstPartyMcpToolsForSession } from "@opengeni/config";
32894
32953
 
32895
32954
  // src/activities/codemode-dispatcher.ts
32896
32955
  import { randomUUID as randomUUID3 } from "crypto";
32897
- import { bindMcpTelemetry, measureMcpPhase as measureMcpPhase2, withMcpCallIdentity as withMcpCallIdentity2 } from "@opengeni/observability";
32956
+ import {
32957
+ bindMcpTelemetry,
32958
+ measureMcpPhase as measureMcpPhase2,
32959
+ withMcpCallIdentity as withMcpCallIdentity2,
32960
+ recordToolApproval
32961
+ } from "@opengeni/observability";
32898
32962
  import {
32899
32963
  AttemptToolApprovalRequiredError,
32900
32964
  AttemptToolCatalogStaleError,
@@ -32907,10 +32971,16 @@ import {
32907
32971
  import {
32908
32972
  CODEMODE_CLAIM_LEASE_MS,
32909
32973
  CODEMODE_CLAIM_HEARTBEAT_MS,
32910
- CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT
32974
+ CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT,
32975
+ CodemodeOperation
32911
32976
  } from "@opengeni/contracts";
32912
32977
  import {
32913
32978
  cancelQueuedCodemodeOperationsForAttempt,
32979
+ waitForCodemodeApproval,
32980
+ bindCodemodePreparation,
32981
+ listTurnCodemodeApprovals as listTurnCodemodeApprovals2,
32982
+ adoptCodemodeApprovalWithOutput,
32983
+ readTurnCodemodeOperation,
32914
32984
  claimCodemodeOperation,
32915
32985
  failCodemodeOperation,
32916
32986
  getSessionEventByClientEventId,
@@ -32924,7 +32994,7 @@ import {
32924
32994
  publishDurableSessionEvents as publishDurableSessionEvents8
32925
32995
  } from "@opengeni/events";
32926
32996
  var CodemodeAttemptDispatcher = class {
32927
- constructor(db, bus, environment, scope, turnSignal, maxConcurrentCalls = CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT, timings = {}, toolDisplayMetadata, toolFamily) {
32997
+ constructor(db, bus, environment, scope, turnSignal, maxConcurrentCalls = CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT, timings = {}, toolDisplayMetadata, toolFamily, approvalWait) {
32928
32998
  this.db = db;
32929
32999
  this.bus = bus;
32930
33000
  this.environment = environment;
@@ -32933,6 +33003,7 @@ var CodemodeAttemptDispatcher = class {
32933
33003
  this.maxConcurrentCalls = maxConcurrentCalls;
32934
33004
  this.toolDisplayMetadata = toolDisplayMetadata;
32935
33005
  this.toolFamily = toolFamily;
33006
+ this.approvalWait = approvalWait;
32936
33007
  if (environment.catalog.accountId !== scope.accountId || environment.catalog.workspaceId !== scope.workspaceId || environment.catalog.sessionId !== scope.sessionId || environment.catalog.turnId !== scope.turnId || environment.catalog.attemptId !== scope.attemptId || environment.catalog.executionGeneration !== scope.executionGeneration) {
32937
33008
  throw new Error("Codemode dispatcher scope does not match its tool environment");
32938
33009
  }
@@ -33022,6 +33093,76 @@ var CodemodeAttemptDispatcher = class {
33022
33093
  })
33023
33094
  );
33024
33095
  }
33096
+ async resumeApproved(callerSubjectId, decisionOperationId) {
33097
+ const operations = await listTurnCodemodeApprovals2(this.db, this.scope);
33098
+ if (decisionOperationId && // Model-tool approval IDs are opaque strings. Only programmatic UUIDs
33099
+ // can address the Codemode operation journal.
33100
+ CodemodeOperation.shape.operationId.safeParse(decisionOperationId).success && !operations.some((operation) => operation.operationId === decisionOperationId)) {
33101
+ const decided = await readTurnCodemodeOperation(this.db, {
33102
+ ...this.scope,
33103
+ callerSubjectId,
33104
+ operationId: decisionOperationId
33105
+ });
33106
+ if (decided) operations.push(decided);
33107
+ }
33108
+ for (const operation of operations) {
33109
+ if (!["waiting_for_approval", "queued", "running"].includes(operation.state)) continue;
33110
+ let effectDigest = null;
33111
+ try {
33112
+ effectDigest = this.environment.effectDigest(operation.identity);
33113
+ } catch {
33114
+ }
33115
+ const continuation = await adoptCodemodeApprovalWithOutput(this.db, {
33116
+ ...this.scope,
33117
+ operationId: operation.operationId,
33118
+ callerSubjectId,
33119
+ catalogDigest: this.environment.catalog.digest,
33120
+ effectDigest
33121
+ });
33122
+ await publishDurableSessionEvents8(
33123
+ this.bus,
33124
+ this.scope.workspaceId,
33125
+ this.scope.sessionId,
33126
+ continuation.events
33127
+ );
33128
+ const adopted = continuation.operation;
33129
+ if (adopted && adopted.state !== operation.state) {
33130
+ const outcome = adopted.state === "queued" ? "resumed" : adopted.state === "outcome_unknown" ? "unknown" : adopted.errorCode === "approval_stale" ? "stale" : "rejected";
33131
+ recordToolApproval(
33132
+ outcome,
33133
+ "continuation",
33134
+ operation.state === "waiting_for_approval" ? Math.max(0, Date.now() - Date.parse(operation.updatedAt)) : void 0
33135
+ );
33136
+ }
33137
+ if (adopted?.state !== "queued") continue;
33138
+ const claim = await claimCodemodeOperation(this.db, {
33139
+ ...this.scope,
33140
+ operationId: adopted.operationId,
33141
+ claimId: randomUUID3(),
33142
+ catalogDigest: this.environment.catalog.digest,
33143
+ claimLeaseMs: this.claimLeaseMs
33144
+ });
33145
+ if (claim.status === "claimed") {
33146
+ const execution = this.execute(claim.operation, claim.claimId, true);
33147
+ this.inFlight.set(adopted.operationId, execution);
33148
+ try {
33149
+ await execution;
33150
+ } finally {
33151
+ this.inFlight.delete(adopted.operationId);
33152
+ }
33153
+ }
33154
+ }
33155
+ const receipts = [];
33156
+ for (const operation of operations) {
33157
+ const receipt = await readTurnCodemodeOperation(this.db, {
33158
+ ...this.scope,
33159
+ callerSubjectId,
33160
+ operationId: operation.operationId
33161
+ });
33162
+ if (receipt) receipts.push(receipt);
33163
+ }
33164
+ return receipts;
33165
+ }
33025
33166
  async close(reason = "Execution attempt ended") {
33026
33167
  if (this.closing) {
33027
33168
  await Promise.allSettled([...this.inFlight.values()]);
@@ -33102,18 +33243,51 @@ var CodemodeAttemptDispatcher = class {
33102
33243
  return;
33103
33244
  }
33104
33245
  let preparedCall;
33246
+ let releaseWait;
33105
33247
  try {
33248
+ if (operation.durableApproval) {
33249
+ if (!this.approvalWait) throw new Error("Durable approval yield is unavailable");
33250
+ releaseWait = this.approvalWait.beginWait();
33251
+ if (!await bindCodemodePreparation(this.db, {
33252
+ ...this.scope,
33253
+ operationId: operation.operationId,
33254
+ claimId,
33255
+ effectDigest: this.environment.effectDigest(operation.identity)
33256
+ }))
33257
+ throw new Error("Operation preparation is stale");
33258
+ }
33106
33259
  preparedCall = await this.environment.prepareCall(
33107
33260
  {
33108
33261
  operationId: operation.operationId,
33109
- catalogDigest: operation.catalogDigest,
33262
+ catalogDigest: this.environment.catalog.digest,
33110
33263
  identity: operation.identity,
33111
33264
  arguments: operation.arguments,
33112
- caller: operation.caller
33265
+ caller: {
33266
+ kind: "codemode",
33267
+ subjectId: operation.caller.subjectId === `sandbox:${operation.attemptId}` ? `sandbox:${this.scope.attemptId}` : operation.caller.subjectId
33268
+ }
33113
33269
  },
33114
- { signal }
33270
+ {
33271
+ signal,
33272
+ ...operation.durableApproval ? { transportMeta: { durableApproval: true } } : {}
33273
+ }
33115
33274
  );
33275
+ if (preparedCall.waitingForApproval) {
33276
+ const waiting = await waitForCodemodeApproval(this.db, {
33277
+ ...this.scope,
33278
+ operationId: operation.operationId,
33279
+ claimId,
33280
+ ...preparedCall.waitingForApproval,
33281
+ effectDigest: preparedCall.effectDigest
33282
+ });
33283
+ if (!waiting) throw new Error("Approval could not be bound to the operation");
33284
+ recordToolApproval("waiting");
33285
+ releaseWait?.(true);
33286
+ return;
33287
+ }
33288
+ releaseWait?.(false);
33116
33289
  } catch (error51) {
33290
+ releaseWait?.(false);
33117
33291
  await this.settleWithOutput(operation, claimId, {
33118
33292
  state: "failed",
33119
33293
  errorCode: errorCode(error51, signal, false),
@@ -33244,6 +33418,7 @@ var CodemodeAttemptDispatcher = class {
33244
33418
  settlement
33245
33419
  });
33246
33420
  if (!result.committed || result.events.length === 0) return;
33421
+ recordToolApproval(settlement.state === "outcome_unknown" ? "unknown" : settlement.state);
33247
33422
  await publishDurableSessionEvents8(
33248
33423
  this.bus,
33249
33424
  this.scope.workspaceId,
@@ -33270,7 +33445,7 @@ function codemodeToolCallCreatedClientEventId(operationId) {
33270
33445
  function assertMatchingCodemodeToolCallCreated(event, operation, entry, scope) {
33271
33446
  const payload = recordValue(event.payload);
33272
33447
  const raw = recordValue(payload?.raw);
33273
- if (event.type !== "agent.toolCall.created" || event.turnId !== scope.turnId || event.turnGeneration !== scope.executionGeneration || event.turnAttemptId !== scope.attemptId || event.turnAssociation !== "current" || payload?.id !== operation.operationId || payload?.name !== entry.modelName || payload?.origin !== "codemode" || payload?.subjectId !== operation.caller.subjectId || raw?.type !== "codemode_call" || raw?.serverId !== operation.identity.serverId || raw?.toolName !== operation.identity.toolName || raw?.catalogDigest !== operation.catalogDigest) {
33448
+ if (event.type !== "agent.toolCall.created" || event.turnId !== scope.turnId || event.turnGeneration !== operation.executionGeneration || event.turnAttemptId !== operation.attemptId || event.turnAssociation !== "current" || payload?.id !== operation.operationId || payload?.name !== entry.modelName || payload?.origin !== "codemode" || payload?.subjectId !== operation.caller.subjectId || raw?.type !== "codemode_call" || raw?.serverId !== operation.identity.serverId || raw?.toolName !== operation.identity.toolName || raw?.catalogDigest !== operation.catalogDigest) {
33274
33449
  throw new Error("Codemode tool-call creation id is bound to incompatible durable evidence");
33275
33450
  }
33276
33451
  }
@@ -35634,7 +35809,8 @@ async function prepareTurnToolRuntime(deps) {
35634
35809
  void 0,
35635
35810
  {},
35636
35811
  (name) => mcpToolDisplayMetadata(tools.mcpServers, name),
35637
- (entry) => toolFamilyForCatalogIdentity(entry, runSettings.mcpServers)
35812
+ (entry) => toolFamilyForCatalogIdentity(entry, runSettings.mcpServers),
35813
+ tools.inputWaitYield
35638
35814
  );
35639
35815
  eventing.codemodeDispatcher.start();
35640
35816
  };
@@ -37668,8 +37844,8 @@ async function buildTurnAgent(deps) {
37668
37844
  // tool. Gateway Responses routes likewise expose ordinary function
37669
37845
  // tools, not OpenAI-hosted sandbox tools. Tell buildAgent to use
37670
37846
  // function apply_patch and wrap successful view_image results as
37671
- // typed input_image content. Chat wires have no proven typed image
37672
- // result transport and therefore receive no view_image tool.
37847
+ // typed input_image content. The Chat adapter projects tool images
37848
+ // into a labelled image envelope after the paired tool results.
37673
37849
  structuredToolTransport: structuredToolTransportForTurn(resolvedModel),
37674
37850
  ...promptCacheKey ? { promptCacheKey } : {}
37675
37851
  } : (
@@ -40507,4 +40683,4 @@ export {
40507
40683
  createTurnActivities,
40508
40684
  createTurnActivitiesFromServices
40509
40685
  };
40510
- //# sourceMappingURL=activities-turn-BMY3CDKL.js.map
40686
+ //# sourceMappingURL=activities-turn-W27A2SBO.js.map