@opengeni/worker-bundle 1.4.0 → 1.4.4
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 +6 -0
- package/dist/activities/agent-turn/sandbox-provision.d.ts +22 -0
- package/dist/activities/agent-turn/turn-context.d.ts +6 -0
- package/dist/activities/agent-turn/web-search.d.ts +5 -1
- package/dist/activities/codemode-dispatcher.d.ts +8 -0
- package/dist/activities/programmatic-approvals.d.ts +0 -17
- package/dist/activities/sandbox-lease.d.ts +9 -0
- package/dist/{activities-control-D5KVEPHI.js → activities-control-VRFS65FT.js} +59 -13
- package/dist/activities-control-VRFS65FT.js.map +1 -0
- package/dist/{activities-turn-4Z3M7Z3F.js → activities-turn-P2YAW2QW.js} +6038 -5890
- package/dist/activities-turn-P2YAW2QW.js.map +1 -0
- package/dist/{chunk-BMTWULF7.js → chunk-BICGFGO7.js} +92 -17
- package/dist/chunk-BICGFGO7.js.map +1 -0
- package/dist/{chunk-FF4HPY7L.js → chunk-HVNNCWEU.js} +108 -3
- package/dist/chunk-HVNNCWEU.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/observability-metrics.d.ts +52 -14
- package/dist/sandbox-resume.d.ts +3 -0
- package/package.json +21 -21
- package/src/activities/agent-turn/errors.ts +61 -7
- package/src/activities/agent-turn/finalization.ts +16 -0
- 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/stream-attempt.ts +37 -50
- package/src/activities/agent-turn/turn-context.ts +8 -0
- package/src/activities/agent-turn/web-search.ts +135 -6
- package/src/activities/codemode-dispatcher.ts +57 -8
- package/src/activities/programmatic-approvals.ts +0 -26
- package/src/activities/sandbox-lease.ts +77 -10
- package/src/observability-metrics.ts +130 -18
- package/src/sandbox-resume.ts +24 -5
- package/src/sandbox-routing.ts +12 -0
- package/dist/activities-control-D5KVEPHI.js.map +0 -1
- package/dist/activities-turn-4Z3M7Z3F.js.map +0 -1
- package/dist/chunk-BMTWULF7.js.map +0 -1
- package/dist/chunk-FF4HPY7L.js.map +0 -1
|
@@ -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` = warm leases whose exact provider instance is no
|
|
226
|
+
* 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;
|
|
@@ -284,7 +300,7 @@ export declare function recordModelRequestPhase(observability: Observability, in
|
|
|
284
300
|
outcome?: "completed" | "failed" | "timed_out";
|
|
285
301
|
durationSeconds: number;
|
|
286
302
|
}): void;
|
|
287
|
-
export type TurnStartupPhase = "services_initialization" | "claim_catalog_read" | "claim_atomic" | "claim_session_read" | "claim_capability_settings" | "learning_policy_freeze" | "attachment_authority_projection" | "claim_and_policy" | "turn_start_settlement" | "credential_selection" | "runtime_preparation" | "session_mcp_settings" | "sandbox_establish" | "file_resolution" | "tool_context_preparation" | "tool_preparation" | "tool_server_construction" | "tool_required_connect" | "tool_optional_connect" | "tool_attempt_catalog_build" | "tool_attempt_catalog_persist" | "tool_workspace_gateway_catalog_build" | "post_tool_preparation" | "agent_construction" | "post_agent_preparation" | "
|
|
303
|
+
export type TurnStartupPhase = "services_initialization" | "claim_catalog_read" | "claim_atomic" | "claim_session_read" | "claim_capability_settings" | "learning_policy_freeze" | "attachment_authority_projection" | "claim_and_policy" | "turn_start_settlement" | "credential_selection" | "runtime_preparation" | "session_mcp_settings" | "sandbox_establish" | "file_resolution" | "tool_context_preparation" | "tool_preparation" | "tool_server_construction" | "tool_required_connect" | "tool_optional_connect" | "tool_attempt_catalog_build" | "tool_attempt_catalog_persist" | "tool_workspace_gateway_catalog_build" | "post_tool_preparation" | "agent_construction" | "post_agent_preparation" | "file_materialization" | "history_preparation" | "history_system_update_load" | "history_current_attachment_resolution" | "history_durable_history_load" | "history_sandbox_envelope_load" | "history_canonical_projection" | "history_provider_projection" | "history_attachment_ref_projection" | "history_screenshot_materialization" | "history_model_attachment_projection" | "history_runtime_input_assembly" | "history_artifact_candidate_scan" | "history_generated_image_materialization" | "history_position_load" | "owned_sandbox_setup" | "runtime_stream_initialization" | "model_request_preparation" | "model_sdk_serialization" | "model_prepare_sandbox_agent_preparation" | "model_prepare_sandbox_agent_manifest_inventory" | "model_prepare_sandbox_session_manifest_inventory" | "model_prepare_sandbox_manifest_apply" | "model_prepare_sandbox_entry_materialization" | "model_prepare_sandbox_running_check" | "model_prepare_sandbox_start" | "model_prepare_sandbox_client_create" | "model_prepare_sandbox_client_resume" | "model_prepare_sandbox_client_delete" | "model_prepare_sandbox_client_state_serialize" | "model_prepare_sandbox_client_reuse_check" | "model_prepare_sandbox_workspace_mutation_admission" | "model_prepare_sandbox_workspace_mutation_provider" | "model_prepare_sandbox_workspace_mutation_settlement" | "model_prepare_sandbox_first_routed_resolution_other" | "model_prepare_sandbox_first_routed_mutation_admission" | "model_prepare_sandbox_first_routed_provider_operation" | "model_prepare_sandbox_first_routed_mutation_settlement" | "model_prepare_sandbox_first_routed_other" | "model_prepare_sandbox_snapshot_wait" | "model_prepare_runner_before_first_sandbox_operation" | "model_prepare_sdk_after_first_sandbox_operation" | "model_prepare_runner_before_mcp_tools" | "model_prepare_mcp_tools_snapshot" | "model_prepare_mcp_tools_before_input_filter" | "model_prepare_mcp_tools_before_repository_skill_discovery" | "model_prepare_repository_skill_discovery" | "model_prepare_repository_skill_discovery_before_input_filter" | "model_prepare_input_filter_base" | "model_prepare_input_filter_genesis" | "model_prepare_input_filter_host" | "model_prepare_input_filter_tool_output" | "model_prepare_input_filter_modality" | "model_prepare_input_filter_context" | "model_prepare_responses_input_conversion" | "model_prepare_responses_request_build" | "model_credential_resolution" | "model_wire_normalization" | "model_request_audit" | "stream_bootstrap";
|
|
288
304
|
export type TurnStartupOutcome = "completed" | "failed";
|
|
289
305
|
export type TurnStartupMilestone = "queue" | "provider_dispatch" | "first_byte";
|
|
290
306
|
export type TurnStartupCache = "hit" | "miss" | "disabled" | "none";
|
|
@@ -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.4.
|
|
3
|
+
"version": "1.4.4",
|
|
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.4.
|
|
58
|
-
"@opengeni/capabilities": "1.4.
|
|
59
|
-
"@opengeni/codemode": "1.4.
|
|
60
|
-
"@opengeni/codex": "1.4.
|
|
61
|
-
"@opengeni/config": "1.4.
|
|
62
|
-
"@opengeni/contracts": "1.4.
|
|
63
|
-
"@opengeni/core": "1.4.
|
|
64
|
-
"@opengeni/db": "1.4.
|
|
65
|
-
"@opengeni/documents": "1.4.
|
|
66
|
-
"@opengeni/events": "1.4.
|
|
67
|
-
"@opengeni/github": "1.4.
|
|
68
|
-
"@opengeni/interaction": "1.4.
|
|
69
|
-
"@opengeni/jev": "1.4.
|
|
70
|
-
"@opengeni/network": "1.4.
|
|
71
|
-
"@opengeni/observability": "1.4.
|
|
72
|
-
"@opengeni/runtime": "1.4.
|
|
73
|
-
"@opengeni/sdk": "1.4.
|
|
74
|
-
"@opengeni/storage": "1.4.
|
|
75
|
-
"@opengeni/tool-gateway": "1.4.
|
|
76
|
-
"@opengeni/xai-subscription": "1.4.
|
|
57
|
+
"@opengeni/agent-proto": "1.4.4",
|
|
58
|
+
"@opengeni/capabilities": "1.4.4",
|
|
59
|
+
"@opengeni/codemode": "1.4.4",
|
|
60
|
+
"@opengeni/codex": "1.4.4",
|
|
61
|
+
"@opengeni/config": "1.4.4",
|
|
62
|
+
"@opengeni/contracts": "1.4.4",
|
|
63
|
+
"@opengeni/core": "1.4.4",
|
|
64
|
+
"@opengeni/db": "1.4.4",
|
|
65
|
+
"@opengeni/documents": "1.4.4",
|
|
66
|
+
"@opengeni/events": "1.4.4",
|
|
67
|
+
"@opengeni/github": "1.4.4",
|
|
68
|
+
"@opengeni/interaction": "1.4.4",
|
|
69
|
+
"@opengeni/jev": "1.4.4",
|
|
70
|
+
"@opengeni/network": "1.4.4",
|
|
71
|
+
"@opengeni/observability": "1.4.4",
|
|
72
|
+
"@opengeni/runtime": "1.4.4",
|
|
73
|
+
"@opengeni/sdk": "1.4.4",
|
|
74
|
+
"@opengeni/storage": "1.4.4",
|
|
75
|
+
"@opengeni/tool-gateway": "1.4.4",
|
|
76
|
+
"@opengeni/xai-subscription": "1.4.4",
|
|
77
77
|
"@temporalio/activity": "^1.17.0",
|
|
78
78
|
"@temporalio/client": "^1.17.0",
|
|
79
79
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
RoutingWorkspaceRootChangedError,
|
|
35
35
|
RoutingBackendRecoveryRequiredError,
|
|
36
36
|
ResponsesStreamingTerminalError,
|
|
37
|
+
classifyModelStreamIdleTimeoutError,
|
|
37
38
|
SandboxMaterializationVerificationError,
|
|
38
39
|
materializationVerificationDiagnostic,
|
|
39
40
|
type MaterializationVerificationDiagnostic,
|
|
@@ -500,15 +501,20 @@ function retryableDatabaseFailureCode(
|
|
|
500
501
|
const connectionOutage = requireDatabaseProvenance
|
|
501
502
|
? isRunningTurnDatabaseConnectionSqlState(sqlState)
|
|
502
503
|
: isDatabaseConnectionSqlState(sqlState);
|
|
504
|
+
// PostgreSQL aborts a deadlock or serialization victim's whole
|
|
505
|
+
// transaction: unlike a lost connection, that write certainly did not
|
|
506
|
+
// commit, so it is at least as safe for exact-attempt recovery.
|
|
507
|
+
const rolledBack = isRetryablePersistenceSqlState(sqlState);
|
|
503
508
|
const code = typed
|
|
504
509
|
? retryablePersistenceFailureCode(sqlState)
|
|
505
|
-
: connectionOutage ||
|
|
510
|
+
: connectionOutage || rolledBack
|
|
506
511
|
? databaseFailureCode(sqlState)
|
|
507
512
|
: null;
|
|
508
513
|
if (
|
|
509
514
|
!code ||
|
|
510
515
|
(requireDatabaseProvenance &&
|
|
511
516
|
!connectionOutage &&
|
|
517
|
+
!rolledBack &&
|
|
512
518
|
!(sqlState === null && hasOwnTransport(node)))
|
|
513
519
|
)
|
|
514
520
|
return null;
|
|
@@ -603,12 +609,7 @@ export function postClaimDatabaseRecoveryFailure(input: {
|
|
|
603
609
|
};
|
|
604
610
|
}): ApplicationFailure | null {
|
|
605
611
|
const code = retryableDatabaseFailureCode(input.error, input.requireDatabaseProvenance);
|
|
606
|
-
if (
|
|
607
|
-
!code ||
|
|
608
|
-
input.executionGeneration < 1 ||
|
|
609
|
-
(input.requireDatabaseProvenance && code !== "db_failure")
|
|
610
|
-
)
|
|
611
|
-
return null;
|
|
612
|
+
if (!code || input.executionGeneration < 1) return null;
|
|
612
613
|
if (
|
|
613
614
|
(input.sandboxSetupOutcomeUnknown && input.sandboxSetupRecoveryExhausted) ||
|
|
614
615
|
((input.sandboxSetupOutcomeUnknown || input.sandboxSetupRecoveryExhausted) &&
|
|
@@ -1178,6 +1179,26 @@ function providerHttpStatus(error: unknown): number | undefined {
|
|
|
1178
1179
|
return undefined;
|
|
1179
1180
|
}
|
|
1180
1181
|
|
|
1182
|
+
/**
|
|
1183
|
+
* A fetch-layer timeout: the runtime's DOMException `TimeoutError` (message
|
|
1184
|
+
* "The operation timed out."), possibly wrapped by SDK/runner `cause` chains.
|
|
1185
|
+
* Explicit cancellation (`AbortError`) is deliberately not matched.
|
|
1186
|
+
*/
|
|
1187
|
+
export function isTransportTimeoutError(error: unknown): boolean {
|
|
1188
|
+
let current: unknown = error;
|
|
1189
|
+
const seen = new Set<unknown>();
|
|
1190
|
+
for (let depth = 0; depth < 8 && current && typeof current === "object"; depth += 1) {
|
|
1191
|
+
if (seen.has(current)) break;
|
|
1192
|
+
seen.add(current);
|
|
1193
|
+
const value = current as { name?: unknown; message?: unknown; cause?: unknown };
|
|
1194
|
+
if (value.name === "TimeoutError" || value.message === "The operation timed out.") {
|
|
1195
|
+
return true;
|
|
1196
|
+
}
|
|
1197
|
+
current = value.cause;
|
|
1198
|
+
}
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1181
1202
|
export function isTransientProviderError(error: unknown): boolean {
|
|
1182
1203
|
if (error instanceof ResponsesStreamingTerminalError) {
|
|
1183
1204
|
return error.category === "unavailable";
|
|
@@ -1715,6 +1736,39 @@ function baseAgentRunFailurePayload(
|
|
|
1715
1736
|
...(mcpTransportDiagnostic ? { mcpTransportDiagnostic } : {}),
|
|
1716
1737
|
};
|
|
1717
1738
|
}
|
|
1739
|
+
// A generic model stream that went silent mid-response (typed idle bound) or
|
|
1740
|
+
// a transport timeout ("The operation timed out." DOMException TimeoutError
|
|
1741
|
+
// from the fetch layer) is a dead upstream connection, not a request fault:
|
|
1742
|
+
// checkpoint durable truth and recover the same turn within the finite
|
|
1743
|
+
// provider recovery budget. Codex/SuperGrok typed timeouts are classified
|
|
1744
|
+
// above under their own transport policy.
|
|
1745
|
+
const streamIdle = classifyModelStreamIdleTimeoutError(error);
|
|
1746
|
+
if (streamIdle) {
|
|
1747
|
+
const seconds =
|
|
1748
|
+
streamIdle.idleTimeoutMs !== null ? Math.round(streamIdle.idleTimeoutMs / 1_000) : null;
|
|
1749
|
+
const window = seconds !== null ? ` for ${seconds}s` : "";
|
|
1750
|
+
return {
|
|
1751
|
+
error:
|
|
1752
|
+
streamIdle.kind === "progress"
|
|
1753
|
+
? `The model provider stream stalled: only keepalive traffic and no response progress${window}. The same turn will retry after a short delay.`
|
|
1754
|
+
: `The model provider stopped sending response data${window}. The same turn will retry after a short delay.`,
|
|
1755
|
+
code: "provider_unavailable",
|
|
1756
|
+
retryable: true,
|
|
1757
|
+
timeoutClass: streamIdle.kind === "progress" ? "progress_stream" : "idle_stream",
|
|
1758
|
+
responseObserved: true,
|
|
1759
|
+
detail: streamIdle.message,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
if (status === undefined && isTransportTimeoutError(error)) {
|
|
1763
|
+
return {
|
|
1764
|
+
error:
|
|
1765
|
+
"The model provider connection timed out. The same turn will retry after a short delay.",
|
|
1766
|
+
code: "provider_unavailable",
|
|
1767
|
+
retryable: true,
|
|
1768
|
+
timeoutClass: "transport",
|
|
1769
|
+
detail: message,
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1718
1772
|
if (code === UNKNOWN_MODEL_FINISH_REASON_CODE) {
|
|
1719
1773
|
return {
|
|
1720
1774
|
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();
|
|
@@ -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,
|
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
classifySandboxLogicalProvisionFailure,
|
|
67
67
|
isLazySandboxProvisionRetryable,
|
|
68
68
|
createTurnSandboxProvisioner,
|
|
69
|
+
trackPhysicalSandboxResume,
|
|
69
70
|
} from "./sandbox-provision";
|
|
70
71
|
import {
|
|
71
72
|
resolveActiveSandboxBackend,
|
|
@@ -645,40 +646,43 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
|
|
|
645
646
|
const lazyHolderId = sandboxState.sandboxHolderId;
|
|
646
647
|
const lazyGroupId = sandboxState.sandboxGroupId;
|
|
647
648
|
sandboxState.resumeManagedGroupBox = () =>
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
649
|
+
trackPhysicalSandboxResume(
|
|
650
|
+
sandboxState.inFlightSandboxResumes,
|
|
651
|
+
resumeBoxForTurn(
|
|
652
|
+
{
|
|
653
|
+
db,
|
|
654
|
+
settings: runSettings,
|
|
655
|
+
logicalFallbackSettings: logicalSandboxSettings,
|
|
656
|
+
cancellationSignal: sandboxResumeSignal,
|
|
657
|
+
sandboxMetrics: runtimeMetricsHooksForObservability(observability),
|
|
658
|
+
observability,
|
|
659
|
+
freshSandboxReadinessReplacementBudget,
|
|
660
|
+
onSandboxLost: publishSandboxLost,
|
|
661
|
+
objectStorage,
|
|
662
|
+
bus,
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
accountId: input.accountId,
|
|
666
|
+
workspaceId: input.workspaceId,
|
|
667
|
+
sandboxGroupId: lazyGroupId,
|
|
668
|
+
sessionId: input.sessionId,
|
|
669
|
+
backend: groupBoxBackend,
|
|
670
|
+
os: groupBoxOs,
|
|
671
|
+
environment: sandboxEnvironment,
|
|
672
|
+
...(groupBoxImage
|
|
673
|
+
? {
|
|
674
|
+
image: groupBoxImage,
|
|
675
|
+
imagePolicy: groupBoxImagePolicy,
|
|
676
|
+
}
|
|
677
|
+
: {}),
|
|
678
|
+
// The lazy acquire must enforce the same frozen rig authority
|
|
679
|
+
// as the eager acquire; otherwise a warm box for another rig
|
|
680
|
+
// can bypass the shared-state conflict/rotation fence.
|
|
681
|
+
...(rigVersion ? { rigVersionId: rigVersion.id } : {}),
|
|
682
|
+
},
|
|
683
|
+
"turn",
|
|
684
|
+
lazyHolderId,
|
|
685
|
+
),
|
|
682
686
|
);
|
|
683
687
|
if (
|
|
684
688
|
shouldPrefetchManagedSandbox({
|
|
@@ -725,52 +729,55 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
|
|
|
725
729
|
);
|
|
726
730
|
try {
|
|
727
731
|
sandboxState.resolvedSandbox = await waitForTurnOperation(
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
732
|
+
trackPhysicalSandboxResume(
|
|
733
|
+
sandboxState.inFlightSandboxResumes,
|
|
734
|
+
resumeBoxForTurn(
|
|
735
|
+
{
|
|
736
|
+
db,
|
|
737
|
+
settings: runSettings,
|
|
738
|
+
logicalFallbackSettings: logicalSandboxSettings,
|
|
739
|
+
cancellationSignal: sandboxResumeSignal,
|
|
740
|
+
sandboxMetrics: runtimeMetricsHooksForObservability(observability),
|
|
741
|
+
observability,
|
|
742
|
+
freshSandboxReadinessReplacementBudget,
|
|
743
|
+
onSandboxLost: publishSandboxLost,
|
|
744
|
+
objectStorage,
|
|
745
|
+
bus,
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
accountId: input.accountId,
|
|
749
|
+
workspaceId: input.workspaceId,
|
|
750
|
+
sandboxGroupId: session.sandboxGroupId,
|
|
751
|
+
sessionId: input.sessionId,
|
|
752
|
+
// groupBoxBackend, not turn.sandboxBackend: a machine-home turn that
|
|
753
|
+
// is not machine-primary resumes a real cloud group box (the
|
|
754
|
+
// deployment default), never a "selfhosted" box (which would throw
|
|
755
|
+
// for lack of a bound agentId).
|
|
756
|
+
backend: groupBoxBackend,
|
|
757
|
+
os: groupBoxOs,
|
|
758
|
+
environment: sandboxEnvironment,
|
|
759
|
+
// Deployment/workspace pins select NEW creates only. The lease
|
|
760
|
+
// retains a live group's image through between-turn repins, even
|
|
761
|
+
// with other holders; only rotation/reaping elects a new image.
|
|
762
|
+
// Select the image for the actual group-box backend; a configured Modal image must
|
|
763
|
+
// never override a Docker run. The selfhosted branch
|
|
764
|
+
// (establishSelfhostedTurnSession) NEVER passes
|
|
765
|
+
// an image — B3 lives only on this managed-box branch.
|
|
766
|
+
...(groupBoxImage
|
|
767
|
+
? {
|
|
768
|
+
image: groupBoxImage,
|
|
769
|
+
imagePolicy: groupBoxImagePolicy,
|
|
770
|
+
}
|
|
771
|
+
: {}),
|
|
772
|
+
// RIG IS SHARED STATE (M3): stamp the frozen rig version so the lease
|
|
773
|
+
// conflicts on a live shared box set up under a different rig (solo
|
|
774
|
+
// durable rotation / N-holders SandboxRigConflictError). Omitted for a
|
|
775
|
+
// rig-less turn -> never stamped or enforced (shares exactly as today).
|
|
776
|
+
...(rigVersion ? { rigVersionId: rigVersion.id } : {}),
|
|
777
|
+
},
|
|
778
|
+
"turn",
|
|
779
|
+
managedOwnership!.holderId,
|
|
780
|
+
),
|
|
774
781
|
),
|
|
775
782
|
sandboxResumeSignal,
|
|
776
783
|
releaseLateSandbox,
|
|
@@ -153,6 +153,57 @@ export async function waitForTurnOperation<T>(
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Upper bound for joining a cancelled attempt's in-flight provider establish.
|
|
158
|
+
* Worker shutdown cancels activities 5s after SIGTERM and force-stops 100s
|
|
159
|
+
* after it; this leaves room for the remaining finalization steps.
|
|
160
|
+
*/
|
|
161
|
+
export const PHYSICAL_SANDBOX_RESUME_DRAIN_TIMEOUT_MS = 60_000;
|
|
162
|
+
|
|
163
|
+
/** Register the PHYSICAL lease acquire/provider establish promise, not the
|
|
164
|
+
* cancellable wrapper awaiting it. */
|
|
165
|
+
export function trackPhysicalSandboxResume<T>(
|
|
166
|
+
inFlight: Set<Promise<unknown>>,
|
|
167
|
+
operation: Promise<T>,
|
|
168
|
+
): Promise<T> {
|
|
169
|
+
inFlight.add(operation);
|
|
170
|
+
const settle = (): void => {
|
|
171
|
+
inFlight.delete(operation);
|
|
172
|
+
};
|
|
173
|
+
operation.then(settle, settle);
|
|
174
|
+
return operation;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Join every lease acquire/provider establish this attempt started, even after
|
|
179
|
+
* the turn-facing wrapper rejected on cancellation. An establish has no
|
|
180
|
+
* portable abort: after cancellation it still finishes its own exact cleanup
|
|
181
|
+
* (terminate an unpublished box and roll warming back to cold, or keep a
|
|
182
|
+
* published box and drop its holder). If the activity returned first, a
|
|
183
|
+
* shutting-down worker exited mid-cleanup and stranded a warming lease naming a
|
|
184
|
+
* terminated box; the replacement attempt then waited out the warming TTL and
|
|
185
|
+
* inherited a lost-workspace verdict. This wait is deliberately independent of
|
|
186
|
+
* the finalizer cancellation signal (cancellation is what started it) and is
|
|
187
|
+
* bounded so an uncooperative provider call cannot pin the activity.
|
|
188
|
+
*/
|
|
189
|
+
export async function drainPhysicalSandboxResumes(
|
|
190
|
+
inFlight: Set<Promise<unknown>>,
|
|
191
|
+
timeoutMs: number = PHYSICAL_SANDBOX_RESUME_DRAIN_TIMEOUT_MS,
|
|
192
|
+
): Promise<"none" | "settled" | "timed_out"> {
|
|
193
|
+
if (inFlight.size === 0) return "none";
|
|
194
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
195
|
+
try {
|
|
196
|
+
return await Promise.race([
|
|
197
|
+
Promise.allSettled([...inFlight]).then(() => "settled" as const),
|
|
198
|
+
new Promise<"timed_out">((resolve) => {
|
|
199
|
+
timer = setTimeout(() => resolve("timed_out"), Math.max(1, timeoutMs));
|
|
200
|
+
}),
|
|
201
|
+
]);
|
|
202
|
+
} finally {
|
|
203
|
+
if (timer) clearTimeout(timer);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
156
207
|
export function sleep(ms: number): Promise<void> {
|
|
157
208
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
158
209
|
}
|