@opengeni/worker-bundle 1.3.0 → 1.4.2
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/agent-turn/errors.d.ts +1 -13
- package/dist/activities/agent-turn/sandbox-provision.d.ts +22 -0
- package/dist/activities/agent-turn/subscription-lease.d.ts +1 -0
- package/dist/activities/agent-turn/tool-policy.d.ts +10 -1
- package/dist/activities/agent-turn/turn-context.d.ts +6 -0
- package/dist/activities/agent-turn/web-search.d.ts +37 -0
- package/dist/activities/codemode-dispatcher.d.ts +8 -0
- package/dist/activities/sandbox-lease.d.ts +4 -0
- package/dist/{activities-control-PEXBWKQJ.js → activities-control-AKJZHDTY.js} +53 -13
- package/dist/activities-control-AKJZHDTY.js.map +1 -0
- package/dist/{activities-turn-W27A2SBO.js → activities-turn-AOXNE6BM.js} +5984 -5571
- package/dist/activities-turn-AOXNE6BM.js.map +1 -0
- package/dist/{chunk-XONQEVAD.js → chunk-DCQ7XTXW.js} +108 -3
- package/dist/chunk-DCQ7XTXW.js.map +1 -0
- package/dist/{chunk-QUX5LT2B.js → chunk-WVZQJBGD.js} +166 -45
- package/dist/chunk-WVZQJBGD.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/observability-metrics.d.ts +51 -13
- package/dist/sandbox-resume.d.ts +3 -0
- package/package.json +21 -21
- package/src/activities/agent-turn/agent-build.ts +4 -2
- package/src/activities/agent-turn/errors.ts +89 -10
- package/src/activities/agent-turn/finalization.ts +16 -0
- package/src/activities/agent-turn/governance-model.ts +8 -8
- package/src/activities/agent-turn/run.ts +12 -0
- package/src/activities/agent-turn/sandbox-establish.ts +87 -80
- package/src/activities/agent-turn/sandbox-provision.ts +51 -0
- package/src/activities/agent-turn/sandbox-runtime.ts +16 -0
- package/src/activities/agent-turn/stream-attempt.ts +36 -15
- package/src/activities/agent-turn/subscription-lease.ts +10 -1
- package/src/activities/agent-turn/tool-environment.ts +19 -0
- package/src/activities/agent-turn/tool-policy.ts +25 -0
- package/src/activities/agent-turn/turn-context.ts +8 -0
- package/src/activities/agent-turn/web-search.ts +381 -0
- package/src/activities/codemode-dispatcher.ts +57 -8
- package/src/activities/connector-attachments.ts +3 -1
- package/src/activities/sandbox-lease.ts +63 -10
- package/src/activity-services.ts +8 -0
- package/src/observability-metrics.ts +130 -17
- package/src/sandbox-resume.ts +24 -5
- package/src/sandbox-routing.ts +70 -24
- package/dist/activities-control-PEXBWKQJ.js.map +0 -1
- package/dist/activities-turn-W27A2SBO.js.map +0 -1
- package/dist/chunk-QUX5LT2B.js.map +0 -1
- package/dist/chunk-XONQEVAD.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
startContextCompactionPendingMonitor,
|
|
20
20
|
startSessionRecoveryMonitor,
|
|
21
21
|
startTurnCapacityMonitor
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-DCQ7XTXW.js";
|
|
23
23
|
import "./chunk-PZ5AY32C.js";
|
|
24
24
|
|
|
25
25
|
// src/index.ts
|
|
@@ -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-AKJZHDTY.js")).createControlActivities(dependencies) : (await import("./activities-turn-AOXNE6BM.js")).createTurnActivities(dependencies);
|
|
843
843
|
}
|
|
844
844
|
function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
|
|
845
845
|
const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
|
|
@@ -176,7 +176,7 @@ export declare function startContextCompactionPendingMonitor(input: {
|
|
|
176
176
|
};
|
|
177
177
|
export declare function recordSandboxLeaseGauges(observability: Observability, counts: Partial<Record<SandboxLeaseLiveness, number>>): void;
|
|
178
178
|
export declare function recordOpenSandboxKubernetesInventoryGauges(observability: Observability, inventory: OpenSandboxKubernetesInventory): void;
|
|
179
|
-
export declare const SANDBOX_INVENTORY_PROJECTION_DOMAINS: readonly ["leases", "checkpoint_artifacts", "recovery_observations", "rotation_backlog", "retained_processes", "expired_drains", "opensandbox_kubernetes"];
|
|
179
|
+
export declare const SANDBOX_INVENTORY_PROJECTION_DOMAINS: readonly ["leases", "checkpoint_artifacts", "recovery_observations", "rotation_backlog", "retained_processes", "expired_drains", "opensandbox_kubernetes", "modal_provider", "interaction_idle"];
|
|
180
180
|
export type SandboxInventoryProjectionDomain = (typeof SANDBOX_INVENTORY_PROJECTION_DOMAINS)[number];
|
|
181
181
|
export declare function recordSandboxInventoryProjectionSuccess(observability: Observability, domain: SandboxInventoryProjectionDomain, timestampSeconds?: number): void;
|
|
182
182
|
export declare function recordSandboxInventoryProjectionFailure(observability: Observability, domain: SandboxInventoryProjectionDomain): void;
|
|
@@ -218,6 +218,22 @@ export declare function recordManagedAuthNewSignupsSwitchGauge(observability: Ob
|
|
|
218
218
|
* grants happen only while this gauge and the runtime switch gauge are both 1.
|
|
219
219
|
*/
|
|
220
220
|
export declare function recordVerifiedSignupTrialDeploymentFlagGauge(observability: Observability, enabled: boolean): void;
|
|
221
|
+
/**
|
|
222
|
+
* Provider-side Modal inventory reconciled against live leases by the orphan
|
|
223
|
+
* sweep: `running` = every running box in the app, `unleased` = running boxes
|
|
224
|
+
* no live lease protects (orphans, counted before termination), and
|
|
225
|
+
* `lease_missing_instance` = live (warming/warm/draining) leases whose exact
|
|
226
|
+
* provider instance is no longer running (zombies).
|
|
227
|
+
*/
|
|
228
|
+
export declare function recordModalSandboxInventoryGauges(observability: Observability, inventory: {
|
|
229
|
+
running: number;
|
|
230
|
+
unleased: number;
|
|
231
|
+
liveLeaseInstancesMissing: number;
|
|
232
|
+
}): void;
|
|
233
|
+
/** Warm leases held only by Browser/Computer interaction holders, by time since
|
|
234
|
+
* the newest interaction activity. Such boxes never drain on their own until the
|
|
235
|
+
* interaction session ends or the provider deadline. */
|
|
236
|
+
export declare function recordInteractionOnlyLeaseGauges(observability: Observability, counts: Record<string, number>): void;
|
|
221
237
|
export declare function recordSandboxOrphansTerminated(observability: Observability, count: number): void;
|
|
222
238
|
export type SandboxCheckpointArtifactMetricState = "candidate" | "current" | "previous" | "delete_pending" | "deleting" | "delete_failed" | "deleted";
|
|
223
239
|
export declare function recordSandboxCheckpointArtifactGauges(observability: Observability, counts: Record<SandboxCheckpointArtifactMetricState, number>): void;
|
|
@@ -375,22 +391,35 @@ export declare function recordTurnStartupMilestone(observability: Observability,
|
|
|
375
391
|
durationSeconds: number;
|
|
376
392
|
}): void;
|
|
377
393
|
export type StreamDeltaClass = "message" | "reasoning";
|
|
394
|
+
/** First streamed content of a provider response. `any` is the first reasoning
|
|
395
|
+
* or answer delta; `text` is the first answer-text delta. They overlap — never
|
|
396
|
+
* sum across `content`. */
|
|
397
|
+
export type ProviderFirstContent = "any" | "text";
|
|
378
398
|
/**
|
|
379
|
-
* Per-
|
|
380
|
-
*
|
|
399
|
+
* Per-stream timing tracker fed every normalized runtime event in push order,
|
|
400
|
+
* plus two producer-side hooks. It separates OUR latency from the PROVIDER's:
|
|
381
401
|
*
|
|
382
|
-
* - `opengeni_stream_ttft_seconds{provider}` —
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
402
|
+
* - `opengeni_stream_ttft_seconds{provider}` — user-perceived (re)start latency:
|
|
403
|
+
* time from a stream start / structural boundary (tool call, tool result,
|
|
404
|
+
* completed message, usage frame) to the first streamed content delta. It
|
|
405
|
+
* mixes our between-call work with provider time; keep it for the absolute
|
|
406
|
+
* dashboard view, not for provider alerting.
|
|
407
|
+
* - `opengeni_model_request_pre_dispatch_seconds{provider}` — OUR per-request
|
|
408
|
+
* latency: SDK model entry (the first admission check) to the literal
|
|
409
|
+
* provider dispatch. Covers admission, the durable history checkpoint,
|
|
410
|
+
* credit revalidation, the durable request audit and request build.
|
|
411
|
+
* - `opengeni_model_provider_ttft_seconds{provider,content}` — the PROVIDER's
|
|
412
|
+
* latency: literal dispatch to the first streamed `any` (reasoning or text)
|
|
413
|
+
* delta and to the first answer `text` delta. Includes network, provider
|
|
414
|
+
* queueing, prompt prefill and reasoning before the first streamed token.
|
|
388
415
|
* - `opengeni_stream_inter_delta_gap_seconds{provider,class}` — gap between
|
|
389
|
-
* consecutive content deltas of the SAME class
|
|
390
|
-
*
|
|
391
|
-
* measures only the choppiness of a live token stream.
|
|
416
|
+
* consecutive content deltas of the SAME class, reset at every structural
|
|
417
|
+
* event so a gap never spans a tool call or a model boundary.
|
|
392
418
|
*
|
|
393
|
-
*
|
|
419
|
+
* Dispatch timing deliberately survives structural events: the consumer may
|
|
420
|
+
* still be persisting the previous response's tool results when the producer
|
|
421
|
+
* dispatches the next request, but the next request's first delta always
|
|
422
|
+
* follows its own dispatch. Purely observational and clock-injectable.
|
|
394
423
|
*/
|
|
395
424
|
export declare class StreamTimingMetrics {
|
|
396
425
|
private readonly observability;
|
|
@@ -399,11 +428,20 @@ export declare class StreamTimingMetrics {
|
|
|
399
428
|
private ttftAnchor;
|
|
400
429
|
private ttftArmed;
|
|
401
430
|
private readonly lastDeltaAt;
|
|
431
|
+
private modelEntryAt;
|
|
432
|
+
private dispatchedAt;
|
|
433
|
+
private readonly providerFirstRecorded;
|
|
402
434
|
constructor(observability: Observability, options: {
|
|
403
435
|
provider: string;
|
|
404
436
|
now?: () => number;
|
|
405
437
|
});
|
|
438
|
+
/** Producer side: the SDK entered a model request (first admission check).
|
|
439
|
+
* Admission can be re-entered for the same request; keep the earliest. */
|
|
440
|
+
onModelRequestEntry(): void;
|
|
441
|
+
/** Producer side: request bytes are about to leave this process. */
|
|
442
|
+
onProviderDispatch(): void;
|
|
406
443
|
onEvent(type: SessionEventType): void;
|
|
444
|
+
private observeProviderFirst;
|
|
407
445
|
}
|
|
408
446
|
/** Flush shape of the streaming batcher: how many events coalesced into one flush
|
|
409
447
|
* (the coalescing win) and how long that flush took (append + publish). */
|
package/dist/sandbox-resume.d.ts
CHANGED
|
@@ -54,6 +54,9 @@ export type SandboxResumeServices = {
|
|
|
54
54
|
* replacement. Production uses {@link freshSandboxReadinessReplacementDelayMs}.
|
|
55
55
|
* It may be async so a test can observe the rolled-back lease in between. */
|
|
56
56
|
freshSandboxReadinessReplacementDelayMs?: () => number | Promise<number>;
|
|
57
|
+
/** Test seam: runs immediately after the elected spawner published its box
|
|
58
|
+
* warm, before the final cancellation check. */
|
|
59
|
+
onSpawnedSandboxPublished?: () => void | Promise<void>;
|
|
57
60
|
/**
|
|
58
61
|
* The turn attempt's fresh-box readiness replacement budget. The lazy
|
|
59
62
|
* provisioner may call resumeBoxForTurn again after a typed lease
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.2",
|
|
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.4.2",
|
|
58
|
+
"@opengeni/capabilities": "1.4.2",
|
|
59
|
+
"@opengeni/codemode": "1.4.2",
|
|
60
|
+
"@opengeni/codex": "1.4.2",
|
|
61
|
+
"@opengeni/config": "1.4.2",
|
|
62
|
+
"@opengeni/contracts": "1.4.2",
|
|
63
|
+
"@opengeni/core": "1.4.2",
|
|
64
|
+
"@opengeni/db": "1.4.2",
|
|
65
|
+
"@opengeni/documents": "1.4.2",
|
|
66
|
+
"@opengeni/events": "1.4.2",
|
|
67
|
+
"@opengeni/github": "1.4.2",
|
|
68
|
+
"@opengeni/interaction": "1.4.2",
|
|
69
|
+
"@opengeni/jev": "1.4.2",
|
|
70
|
+
"@opengeni/network": "1.4.2",
|
|
71
|
+
"@opengeni/observability": "1.4.2",
|
|
72
|
+
"@opengeni/runtime": "1.4.2",
|
|
73
|
+
"@opengeni/sdk": "1.4.2",
|
|
74
|
+
"@opengeni/storage": "1.4.2",
|
|
75
|
+
"@opengeni/tool-gateway": "1.4.2",
|
|
76
|
+
"@opengeni/xai-subscription": "1.4.2",
|
|
77
77
|
"@temporalio/activity": "^1.17.0",
|
|
78
78
|
"@temporalio/client": "^1.17.0",
|
|
79
79
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -57,7 +57,6 @@ import { VideoGenerationRejectedResult, resolveAgentToolFamilies } from "@openge
|
|
|
57
57
|
|
|
58
58
|
import {
|
|
59
59
|
structuredToolTransportForTurn,
|
|
60
|
-
hostedWebSearchForTurn,
|
|
61
60
|
connectedSubscriptionImageGenerationAuthority,
|
|
62
61
|
textVerbosityForTurn,
|
|
63
62
|
reasoningSummaryForTurn,
|
|
@@ -80,6 +79,7 @@ import type {
|
|
|
80
79
|
import { SESSION_TITLE_MODEL_TOOL_NAME } from "./session-title";
|
|
81
80
|
import { resolveTurnSandboxAccess } from "./turn-sandbox-access";
|
|
82
81
|
import { resolveVideoReferenceSandboxAccess } from "./video-reference-sandbox";
|
|
82
|
+
import { turnWebSearchPlan } from "./web-search";
|
|
83
83
|
|
|
84
84
|
export type BuildTurnAgentDeps = {
|
|
85
85
|
skillCatalog: NonNullable<BuildAgentOptions["skillCatalog"]>;
|
|
@@ -246,7 +246,9 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
|
|
|
246
246
|
},
|
|
247
247
|
}
|
|
248
248
|
: {};
|
|
249
|
-
|
|
249
|
+
// Fallback mode (the default) keeps hosted search exactly as resolved; the
|
|
250
|
+
// operator's `replace` mode withholds it in favour of provider tools.
|
|
251
|
+
const hostedWebSearch = turnWebSearchPlan(resolvedModel, runSettings).hostedWebSearch;
|
|
250
252
|
const resolveImageReferences = async (
|
|
251
253
|
references: Parameters<typeof resolveImageGenerationReferencesForTool>[0]["references"],
|
|
252
254
|
) =>
|
|
@@ -27,8 +27,12 @@ import {
|
|
|
27
27
|
isModalTaskExecStartPreDispatchUnavailableError,
|
|
28
28
|
isModalCommandStartOutcomeUnknownError,
|
|
29
29
|
isProviderCommandObservationUnavailableError,
|
|
30
|
+
ProviderCommandInputOutcomeUnknownError,
|
|
31
|
+
ProviderCommandStartOutcomeUnknownError,
|
|
30
32
|
isRoutingMutationOutcomeUnknownError,
|
|
33
|
+
isRoutingMutationOutputRejectedError,
|
|
31
34
|
RoutingWorkspaceRootChangedError,
|
|
35
|
+
RoutingBackendRecoveryRequiredError,
|
|
32
36
|
ResponsesStreamingTerminalError,
|
|
33
37
|
SandboxMaterializationVerificationError,
|
|
34
38
|
materializationVerificationDiagnostic,
|
|
@@ -380,7 +384,7 @@ export function preClaimAdmissionFailure(error: unknown): ApplicationFailure {
|
|
|
380
384
|
* objects (including array containers), not queue positions/duplicate refs.
|
|
381
385
|
* The separate link ceiling bounds huge duplicate arrays without truncating
|
|
382
386
|
* them into permission. Cycles are harmless; overflow/unreadable edges hold. */
|
|
383
|
-
function
|
|
387
|
+
function structuredRecoveryCauseGraph(error: unknown): Map<object, Set<object>> | null {
|
|
384
388
|
const graph = new Map<object, Set<object>>();
|
|
385
389
|
const queue: object[] = [];
|
|
386
390
|
let links = 0;
|
|
@@ -414,7 +418,7 @@ function retryableDatabaseFailureCode(
|
|
|
414
418
|
requireDatabaseProvenance = false,
|
|
415
419
|
): PostClaimDatabaseRecoveryDetail["code"] | null {
|
|
416
420
|
try {
|
|
417
|
-
const graph =
|
|
421
|
+
const graph = structuredRecoveryCauseGraph(error);
|
|
418
422
|
if (!graph) return null;
|
|
419
423
|
const transports = new Set<object>();
|
|
420
424
|
const boundaries = new Set<object>();
|
|
@@ -429,7 +433,8 @@ function retryableDatabaseFailureCode(
|
|
|
429
433
|
// Ask the canonical transport predicate about ONLY this node's facts. Its
|
|
430
434
|
// recursive search must not pair a DB sibling with an unrelated provider.
|
|
431
435
|
for (const node of graph.keys()) {
|
|
432
|
-
if (isRoutingMutationOutcomeUnknownError(node)
|
|
436
|
+
if (isRoutingMutationOutcomeUnknownError(node) || isRoutingMutationOutputRejectedError(node))
|
|
437
|
+
return null;
|
|
433
438
|
const record = node as Record<string, unknown>;
|
|
434
439
|
if (
|
|
435
440
|
requireDatabaseProvenance
|
|
@@ -495,15 +500,20 @@ function retryableDatabaseFailureCode(
|
|
|
495
500
|
const connectionOutage = requireDatabaseProvenance
|
|
496
501
|
? isRunningTurnDatabaseConnectionSqlState(sqlState)
|
|
497
502
|
: isDatabaseConnectionSqlState(sqlState);
|
|
503
|
+
// PostgreSQL aborts a deadlock or serialization victim's whole
|
|
504
|
+
// transaction: unlike a lost connection, that write certainly did not
|
|
505
|
+
// commit, so it is at least as safe for exact-attempt recovery.
|
|
506
|
+
const rolledBack = isRetryablePersistenceSqlState(sqlState);
|
|
498
507
|
const code = typed
|
|
499
508
|
? retryablePersistenceFailureCode(sqlState)
|
|
500
|
-
: connectionOutage ||
|
|
509
|
+
: connectionOutage || rolledBack
|
|
501
510
|
? databaseFailureCode(sqlState)
|
|
502
511
|
: null;
|
|
503
512
|
if (
|
|
504
513
|
!code ||
|
|
505
514
|
(requireDatabaseProvenance &&
|
|
506
515
|
!connectionOutage &&
|
|
516
|
+
!rolledBack &&
|
|
507
517
|
!(sqlState === null && hasOwnTransport(node)))
|
|
508
518
|
)
|
|
509
519
|
return null;
|
|
@@ -598,12 +608,7 @@ export function postClaimDatabaseRecoveryFailure(input: {
|
|
|
598
608
|
};
|
|
599
609
|
}): ApplicationFailure | null {
|
|
600
610
|
const code = retryableDatabaseFailureCode(input.error, input.requireDatabaseProvenance);
|
|
601
|
-
if (
|
|
602
|
-
!code ||
|
|
603
|
-
input.executionGeneration < 1 ||
|
|
604
|
-
(input.requireDatabaseProvenance && code !== "db_failure")
|
|
605
|
-
)
|
|
606
|
-
return null;
|
|
611
|
+
if (!code || input.executionGeneration < 1) return null;
|
|
607
612
|
if (
|
|
608
613
|
(input.sandboxSetupOutcomeUnknown && input.sandboxSetupRecoveryExhausted) ||
|
|
609
614
|
((input.sandboxSetupOutcomeUnknown || input.sandboxSetupRecoveryExhausted) &&
|
|
@@ -742,13 +747,56 @@ export function modelPreparationFailureEventPayload(error: unknown, durationMs:
|
|
|
742
747
|
* only those structural error links with a strict bound; never classify from
|
|
743
748
|
* message text, which could originate in model or tool content.
|
|
744
749
|
*/
|
|
750
|
+
/**
|
|
751
|
+
* Only the home resolver's positive pre-dispatch signal permits a fresh attempt.
|
|
752
|
+
* The same error class can also escape AFTER a provider mutation, so neither its
|
|
753
|
+
* retryable property nor message alone establishes replay safety. Inspect the
|
|
754
|
+
* complete bounded SDK cause graph and let uncertain peer outcomes veto this
|
|
755
|
+
* narrow recovery; unreadable or overflowing graphs fail closed.
|
|
756
|
+
*/
|
|
757
|
+
function isPreDispatchHomeBackendRecoveryRequired(error: unknown): boolean {
|
|
758
|
+
const graph = structuredRecoveryCauseGraph(error);
|
|
759
|
+
if (!graph) return false;
|
|
760
|
+
let found = false;
|
|
761
|
+
try {
|
|
762
|
+
for (const node of graph.keys()) {
|
|
763
|
+
if (
|
|
764
|
+
isRoutingMutationOutcomeUnknownError(node) ||
|
|
765
|
+
isRoutingMutationOutputRejectedError(node) ||
|
|
766
|
+
isRawProviderCommandOutcomeUnknown(node) ||
|
|
767
|
+
isModalCommandStartOutcomeUnknownError(node) ||
|
|
768
|
+
isProviderCommandObservationUnavailableError(node)
|
|
769
|
+
)
|
|
770
|
+
return false;
|
|
771
|
+
if (node instanceof RoutingBackendRecoveryRequiredError) {
|
|
772
|
+
if (
|
|
773
|
+
node.op !== "resolve_home_backend" ||
|
|
774
|
+
!Number.isSafeInteger(node.leaseEpoch) ||
|
|
775
|
+
node.leaseEpoch < 0 ||
|
|
776
|
+
!node.retryable ||
|
|
777
|
+
(node.recovery !== "pending" && node.recovery !== "superseded")
|
|
778
|
+
)
|
|
779
|
+
return false;
|
|
780
|
+
found = true;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} catch {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
return found;
|
|
787
|
+
}
|
|
788
|
+
|
|
745
789
|
export function sandboxRouteTransitionCode(
|
|
746
790
|
error: unknown,
|
|
747
791
|
):
|
|
748
792
|
| "home_unavailable_this_turn"
|
|
749
793
|
| "workspace_root_changed_this_turn"
|
|
750
794
|
| "native_capabilities_changed_this_attempt"
|
|
795
|
+
| "home_backend_recovery_pending"
|
|
751
796
|
| null {
|
|
797
|
+
if (isPreDispatchHomeBackendRecoveryRequired(error)) {
|
|
798
|
+
return "home_backend_recovery_pending";
|
|
799
|
+
}
|
|
752
800
|
const pending: unknown[] = [error];
|
|
753
801
|
const seen = new WeakSet<object>();
|
|
754
802
|
let inspected = 0;
|
|
@@ -1273,10 +1321,41 @@ function anthropicRequestDiagnostic(error: unknown): AnthropicRequestError | und
|
|
|
1273
1321
|
: undefined;
|
|
1274
1322
|
}
|
|
1275
1323
|
|
|
1324
|
+
function isRawProviderCommandOutcomeUnknown(error: unknown): boolean {
|
|
1325
|
+
try {
|
|
1326
|
+
return (
|
|
1327
|
+
error instanceof ProviderCommandInputOutcomeUnknownError ||
|
|
1328
|
+
error instanceof ProviderCommandStartOutcomeUnknownError
|
|
1329
|
+
);
|
|
1330
|
+
} catch {
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1276
1335
|
export function agentRunFailurePayload(
|
|
1277
1336
|
error: unknown,
|
|
1278
1337
|
options: { isCodexTurn?: boolean } = {},
|
|
1279
1338
|
): ReturnType<typeof baseAgentRunFailurePayload> {
|
|
1339
|
+
const graph = structuredRecoveryCauseGraph(error);
|
|
1340
|
+
const nodes = graph ? [...graph.keys()] : [];
|
|
1341
|
+
const outputRejected = nodes.find(isRoutingMutationOutputRejectedError);
|
|
1342
|
+
if (outputRejected) {
|
|
1343
|
+
// A known receipt applies only to its own mutation. Uncertain peers retain
|
|
1344
|
+
// their established terminal classification, regardless of SDK graph order.
|
|
1345
|
+
const unknown = nodes.find(
|
|
1346
|
+
(node) =>
|
|
1347
|
+
isRoutingMutationOutcomeUnknownError(node) ||
|
|
1348
|
+
isModalCommandStartOutcomeUnknownError(node) ||
|
|
1349
|
+
isProviderCommandObservationUnavailableError(node) ||
|
|
1350
|
+
isRawProviderCommandOutcomeUnknown(node),
|
|
1351
|
+
);
|
|
1352
|
+
if (unknown) return { ...baseAgentRunFailurePayload(unknown, options), retryable: false };
|
|
1353
|
+
return {
|
|
1354
|
+
error: outputRejected.message,
|
|
1355
|
+
code: outputRejected.code,
|
|
1356
|
+
retryable: false,
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1280
1359
|
const failure = baseAgentRunFailurePayload(error, options);
|
|
1281
1360
|
const diagnostic = materializationVerificationDiagnostic(error);
|
|
1282
1361
|
const anthropic = anthropicRequestDiagnostic(error);
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
type ResumedTurnSandbox,
|
|
44
44
|
} from "../../sandbox-resume";
|
|
45
45
|
import { createTurnCredentialLeases } from "./credential-leases";
|
|
46
|
+
import { drainPhysicalSandboxResumes } from "./sandbox-provision";
|
|
46
47
|
import { safeErrorDiagnostic, safeErrorForTelemetry } from "./errors";
|
|
47
48
|
import {
|
|
48
49
|
assertPhysicalToolQuiescenceForCancellation,
|
|
@@ -778,6 +779,21 @@ async function finalizeTurnAttemptSteps(
|
|
|
778
779
|
}
|
|
779
780
|
monitor.enter("workspace_snapshot");
|
|
780
781
|
await drainInFlightWarmSnapshot();
|
|
782
|
+
// Join any provider establish still running behind a cancelled wrapper
|
|
783
|
+
// (finalization already aborted provisioning when no box resolved). Its
|
|
784
|
+
// own exact cleanup (roll an unpublished box back to cold, or keep a
|
|
785
|
+
// published box and drop its holder) must commit before this activity can
|
|
786
|
+
// let a shutting-down worker exit. A late success is routed through
|
|
787
|
+
// releaseLateSandbox before the release targets are collected below.
|
|
788
|
+
monitor.enter("sandbox_provisioning");
|
|
789
|
+
if (sandboxState.inFlightSandboxResumes.size > 0) {
|
|
790
|
+
const drained = await drainPhysicalSandboxResumes(sandboxState.inFlightSandboxResumes);
|
|
791
|
+
if (drained === "timed_out") {
|
|
792
|
+
console.error(
|
|
793
|
+
"in-flight sandbox establish did not settle before finalization; the lease reaper owns its warming row",
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
781
797
|
monitor.enter("sandbox_release");
|
|
782
798
|
const sandboxReleaseTargets = new Set(sandboxState.lateSandboxesAwaitingWriterDrain);
|
|
783
799
|
sandboxState.lateSandboxesAwaitingWriterDrain.clear();
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
lazyToolTransportForTurn,
|
|
60
60
|
openAiHostedImageProviderBindingForTurn,
|
|
61
61
|
modelAttachmentInputPolicyForTurn,
|
|
62
|
+
resolveAcceptedTurnModel,
|
|
62
63
|
} from "./tool-policy";
|
|
63
64
|
|
|
64
65
|
import type { ClaimTurnOk } from "./claim";
|
|
@@ -361,10 +362,11 @@ export async function prepareGovernanceAndModel(
|
|
|
361
362
|
// a chat-only Fireworks model. Resolving against the default-model settings
|
|
362
363
|
// keeps gating consistent with the router. Cost accounting covers registry
|
|
363
364
|
// models via configuredModelPricing.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
//
|
|
366
|
+
// The accepted policy is then projected back onto the resolved shape: a
|
|
367
|
+
// turn frozen before hosted web search was enabled keeps its frozen tool set
|
|
368
|
+
// on every attempt, so recovery never adds a tool mid-turn.
|
|
369
|
+
const resolvedModel = resolveAcceptedTurnModel(runtime, capabilitySettings, turnExecutionPolicy);
|
|
368
370
|
const providerApi = resolvedModel?.provider.api ?? "responses";
|
|
369
371
|
const nativeImageProviderBinding =
|
|
370
372
|
providerApi === "responses"
|
|
@@ -372,10 +374,8 @@ export async function prepareGovernanceAndModel(
|
|
|
372
374
|
: null;
|
|
373
375
|
const lazyToolTransport = lazyToolTransportForTurn(resolvedModel);
|
|
374
376
|
const modelInputPolicy = modelAttachmentInputPolicyForTurn(resolvedModel);
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
// image transport for that wire yet; exposing view_image there would turn
|
|
378
|
-
// pixels into a multi-megabyte text/base64 function result.
|
|
377
|
+
// The shared input policy combines model modality with the supported wire
|
|
378
|
+
// transport, including typed image projection for vision-capable Chat models.
|
|
379
379
|
const supportsImageInput = modelInputPolicy.supportsImageInput;
|
|
380
380
|
media.modelCanReceiveRetainedSessionImages = supportsImageInput;
|
|
381
381
|
const attachmentProjector = createModelHistoryAttachmentProjector(
|
|
@@ -385,6 +385,14 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
385
385
|
getModelRunSettings: () => eventing.modelRunSettings,
|
|
386
386
|
getExecutionGeneration: () => attempt.executionGeneration,
|
|
387
387
|
});
|
|
388
|
+
// Diagnostic only: a timing observer can never fence or fail a request.
|
|
389
|
+
const observeProviderDispatch = () => {
|
|
390
|
+
try {
|
|
391
|
+
eventing.providerDispatchObserver?.();
|
|
392
|
+
} catch {
|
|
393
|
+
// Metrics emission must never affect a model call.
|
|
394
|
+
}
|
|
395
|
+
};
|
|
388
396
|
const checkpointBeforeProviderDispatch = async () => {
|
|
389
397
|
await awaitModelCallAdmission();
|
|
390
398
|
await checkpointHistoryBeforeProviderDispatch(historySink, {
|
|
@@ -682,6 +690,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
682
690
|
}, // latest wins; flushed once in finally
|
|
683
691
|
beforeProviderDispatch: () => {
|
|
684
692
|
leases.codex.assertUsable();
|
|
693
|
+
observeProviderDispatch();
|
|
685
694
|
},
|
|
686
695
|
onRequestPreparationDiagnostic: (phase) => {
|
|
687
696
|
if (
|
|
@@ -959,6 +968,9 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
959
968
|
},
|
|
960
969
|
},
|
|
961
970
|
]);
|
|
971
|
+
// SuperGrok places bytes on the wire immediately after this
|
|
972
|
+
// awaited audit returns.
|
|
973
|
+
if (event.phase === "started") observeProviderDispatch();
|
|
962
974
|
attempt.providerRecoveryCount = providerRecoveryCountAfterModelRequestPhase(
|
|
963
975
|
attempt.providerRecoveryCount,
|
|
964
976
|
event.phase,
|