@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/activities/codemode-dispatcher.d.ts +6 -2
- package/dist/activities/open-suffix-resume.d.ts +2 -0
- package/dist/activities/programmatic-approvals.d.ts +10 -0
- package/dist/activities/run-input.d.ts +2 -0
- package/dist/{activities-control-QSDPXLLD.js → activities-control-PEXBWKQJ.js} +2 -2
- package/dist/{activities-turn-BMY3CDKL.js → activities-turn-W27A2SBO.js} +273 -97
- package/dist/activities-turn-W27A2SBO.js.map +1 -0
- package/dist/{chunk-JKMG5ZHH.js → chunk-QUX5LT2B.js} +3 -2
- package/dist/{chunk-JKMG5ZHH.js.map → chunk-QUX5LT2B.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +21 -21
- package/src/activities/agent-turn/agent-build.ts +2 -2
- package/src/activities/agent-turn/stream-attempt.ts +66 -21
- package/src/activities/agent-turn/tool-environment.ts +1 -0
- package/src/activities/agent-turn/tool-policy.ts +1 -5
- package/src/activities/codemode-dispatcher.ts +145 -7
- package/src/activities/open-suffix-resume.ts +107 -86
- package/src/activities/programmatic-approvals.ts +26 -0
- package/src/activities/run-input.ts +4 -1
- package/dist/activities-turn-BMY3CDKL.js.map +0 -1
- /package/dist/{activities-control-QSDPXLLD.js.map → activities-control-PEXBWKQJ.js.map} +0 -0
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-
|
|
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.
|
|
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.
|
|
58
|
-
"@opengeni/capabilities": "1.
|
|
59
|
-
"@opengeni/codemode": "1.
|
|
60
|
-
"@opengeni/codex": "1.
|
|
61
|
-
"@opengeni/config": "1.
|
|
62
|
-
"@opengeni/contracts": "1.
|
|
63
|
-
"@opengeni/core": "1.
|
|
64
|
-
"@opengeni/db": "1.
|
|
65
|
-
"@opengeni/documents": "1.
|
|
66
|
-
"@opengeni/events": "1.
|
|
67
|
-
"@opengeni/github": "1.
|
|
68
|
-
"@opengeni/interaction": "1.
|
|
69
|
-
"@opengeni/jev": "1.
|
|
70
|
-
"@opengeni/network": "1.
|
|
71
|
-
"@opengeni/observability": "1.
|
|
72
|
-
"@opengeni/runtime": "1.
|
|
73
|
-
"@opengeni/sdk": "1.
|
|
74
|
-
"@opengeni/storage": "1.
|
|
75
|
-
"@opengeni/tool-gateway": "1.
|
|
76
|
-
"@opengeni/xai-subscription": "1.
|
|
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
|
|
761
|
-
//
|
|
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
|
-
|
|
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
|
|
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 =
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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:
|
|
298
|
+
supportsImageInput: modelSupportsImageInputForTurn(resolvedModel),
|
|
303
299
|
inputFileMediaTypes: [],
|
|
304
300
|
};
|
|
305
301
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
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
|
-
|
|
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:
|
|
397
|
+
catalogDigest: this.environment.catalog.digest,
|
|
285
398
|
identity: operation.identity,
|
|
286
399
|
arguments: operation.arguments,
|
|
287
|
-
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 !==
|
|
494
|
-
event.turnAttemptId !==
|
|
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 ||
|