@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
1,710 lines • 84.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SandboxRuntimeManager = void 0;
const agentToolConfiguration_1 = require("../../agentToolConfiguration.js");
const logger_1 = __importStar(require("../../logger.js"));
const errors_1 = require("../../errors.js");
const memory_1 = require("../capabilities/memory.js");
const errors_2 = require("../errors.js");
const manifest_1 = require("../manifest.js");
const mountSecurity_1 = require("../mountSecurity.js");
const remoteMountCommandAllowlist_1 = require("../shared/remoteMountCommandAllowlist.js");
const environment_1 = require("../shared/environment.js");
const stableJson_1 = require("../shared/stableJson.js");
const generation_1 = require("../memory/generation.js");
const agentPreparation_1 = require("./agentPreparation.js");
const agentKeys_1 = require("./agentKeys.js");
const livePreservedSessions_1 = require("./livePreservedSessions.js");
const providedSessionManifest_1 = require("./providedSessionManifest.js");
const sessionSerialization_1 = require("./sessionSerialization.js");
const manifestPersistence_1 = require("../sandboxes/shared/manifestPersistence.js");
const sessionLifecycle_1 = require("./sessionLifecycle.js");
const sessionState_1 = require("./sessionState.js");
const spans_1 = require("./spans.js");
const runAsManifest_1 = require("./runAsManifest.js");
const workspacePaths_1 = require("../workspacePaths.js");
const sandboxAgentIdentitiesByRunState = new WeakMap();
class SandboxRuntimeManager {
sandboxConfig;
workspaceScope;
runState;
agentKeys;
agentsByKey;
acquiredAgents = new Map();
sessionsByAgent = new Map();
sessionsByAgentKey = new Map();
sessionAgentNamesByKey = new Map();
preparedAgents = new Map();
preparedSessions = new Map();
preparedManifestSignatures = new Map();
ownedSessionAgentKeys = new Set();
sessionStartPromises = new WeakMap();
activeMemory;
currentAgentId;
constructor(args) {
this.sandboxConfig = args.sandboxConfig;
this.workspaceScope = new workspacePaths_1.SandboxWorkspaceScope(args.sandboxConfig?.cwd);
this.runState = args.runState;
const existingIdentityRegistry = args.runState
? sandboxAgentIdentitiesByRunState.get(args.runState)
: undefined;
if (existingIdentityRegistry) {
this.agentKeys = existingIdentityRegistry.agentKeys;
this.agentsByKey = existingIdentityRegistry.agentsByKey;
}
else {
this.agentKeys = (0, agentKeys_1.allocateAgentKeys)(args.startingAgent);
this.agentsByKey = (0, agentKeys_1.indexAgentsByKey)(args.startingAgent, this.agentKeys);
if (args.runState) {
sandboxAgentIdentitiesByRunState.set(args.runState, {
agentKeys: this.agentKeys,
agentsByKey: this.agentsByKey,
});
}
}
}
async prepareAgent(args) {
const { currentAgent, turnInput, runConfigModel, tracingParent } = args;
if (!(0, agentKeys_1.isSandboxAgent)(currentAgent)) {
this.activeMemory = undefined;
return {
executionAgent: currentAgent,
turnInput,
};
}
if (!this.sandboxConfig) {
throw new errors_1.UserError('SandboxAgent execution requires `RunConfig.sandbox`.');
}
return await (0, spans_1.withSandboxSpan)('sandbox.prepare_agent', {
agent_name: currentAgent.name,
}, async (prepareSpan) => {
this.acquireAgent(currentAgent);
const session = await this.ensureSession(currentAgent, prepareSpan);
await this.ensureWorkspaceScopeAccessible(session, (0, runAsManifest_1.sandboxRunAsName)(currentAgent.runAs));
// Bind a clone to the live session so capability tools and instructions can carry
// per-session state without mutating the public SandboxAgent instance.
const executionAgent = this.getPreparedAgent(currentAgent, session, runConfigModel, tracingParent);
const memory = executionAgent.capabilities.find(memory_1.isMemoryCapability);
if (memory) {
this.activeMemory = {
session,
memory,
runAs: (0, runAsManifest_1.sandboxRunAsName)(currentAgent.runAs),
};
}
else {
this.activeMemory = undefined;
}
return {
executionAgent,
turnInput: executionAgent.capabilities.reduce((input, capability) => capability.processContext(input), turnInput),
};
}, tracingParent);
}
async cleanup(state, options = {}) {
const preserveOwnedSessions = options.preserveOwnedSessions ?? false;
const runCleanup = async (cleanupSpan) => {
let preserveCleanupHandles = false;
try {
const cleanupPlan = await this.planCleanup(state, { preserveOwnedSessions }, cleanupSpan);
await this.executeCleanupPlan(state, cleanupPlan, {
onCloseError: () => {
preserveCleanupHandles = true;
},
tracingParent: cleanupSpan,
});
}
finally {
if ((0, sessionState_1.hasPreservedOwnedSessions)((0, sessionState_1.getSerializedSandboxState)(state))) {
sandboxAgentIdentitiesByRunState.set(state, {
agentKeys: this.agentKeys,
agentsByKey: this.agentsByKey,
});
}
else {
sandboxAgentIdentitiesByRunState.delete(state);
}
this.releaseAgents();
this.sessionsByAgent.clear();
if (!preserveCleanupHandles) {
this.sessionsByAgentKey.clear();
this.sessionAgentNamesByKey.clear();
this.ownedSessionAgentKeys.clear();
}
this.preparedAgents.clear();
this.preparedSessions.clear();
this.preparedManifestSignatures.clear();
this.activeMemory = undefined;
this.currentAgentId = undefined;
}
};
if (this.sessionsByAgentKey.size === 0 &&
this.preparedAgents.size === 0 &&
this.ownedSessionAgentKeys.size === 0) {
await runCleanup(options.tracingParent);
return;
}
await (0, spans_1.withSandboxSpan)('sandbox.cleanup', {}, runCleanup, options.tracingParent);
}
async planCleanup(state, options, tracingParent) {
if (this.sessionsByAgentKey.size > 0) {
return await this.planCleanupForActiveSessions(state, options);
}
return await this.planCleanupForSerializedStateOnly(state, options, tracingParent);
}
async planCleanupForActiveSessions(state, options) {
const cleanupPlan = options.preserveOwnedSessions
? {}
: {
ownedSessionCloseTarget: 'all',
afterOwnedSessionClose: {
forgetLivePreservedSessions: true,
},
};
const { closeErrors: rejectedCloseErrors } = await this.closeLivePreservedOwnedSessions(state, {
rejectedOnly: true,
});
if (rejectedCloseErrors.length > 0) {
cleanupPlan.deferredError = lifecycleErrorForCloseErrors('Failed to close one or more rejected live sandbox sessions.', rejectedCloseErrors);
}
try {
await this.runPreStopHooksBeforeRelease();
const serializedState = await this.serializeState({
includeOwnedSessions: options.preserveOwnedSessions,
});
state._sandbox = serializedState;
if (options.preserveOwnedSessions && serializedState) {
(0, livePreservedSessions_1.rememberLivePreservedOwnedSessions)({
state,
serializedState,
sessionsByAgentKey: this.sessionsByAgentKey,
});
const serializedOnlySessionAgentKeys = (0, livePreservedSessions_1.preservedOwnedSessionAgentKeysWithoutLiveReuse)(serializedState);
if (serializedOnlySessionAgentKeys.size > 0) {
cleanupPlan.ownedSessionCloseTarget = serializedOnlySessionAgentKeys;
cleanupPlan.afterOwnedSessionClose = {
removeFreshCreationSessionsFromSandboxState: true,
};
}
}
if (options.preserveOwnedSessions &&
!serializedState &&
this.ownedSessionAgentKeys.size > 0) {
cleanupPlan.ownedSessionCloseTarget = 'all';
cleanupPlan.afterOwnedSessionClose = {
forgetLivePreservedSessions: true,
};
}
}
catch (error) {
cleanupPlan.deferredError = cleanupPlan.deferredError
? new errors_2.SandboxLifecycleError('Failed to clean up rejected live sessions and serialize active sandbox sessions.', { errors: [cleanupPlan.deferredError, error] })
: error;
if (this.ownedSessionAgentKeys.size > 0) {
cleanupPlan.ownedSessionCloseTarget = 'all';
cleanupPlan.afterOwnedSessionClose = {
forgetLivePreservedSessions: true,
removeClosedSessionsFromSandboxState: true,
};
}
}
return cleanupPlan;
}
async planCleanupForSerializedStateOnly(state, options, tracingParent) {
const cleanupPlan = options.preserveOwnedSessions
? {}
: {
ownedSessionCloseTarget: 'all',
afterOwnedSessionClose: {
forgetLivePreservedSessions: true,
},
};
let sandboxState = (0, sessionState_1.getSerializedSandboxState)(state);
if (((0, sessionState_1.hasPreservedOwnedSessions)(sandboxState) ||
(0, livePreservedSessions_1.hasRejectedLivePreservedOwnedSessions)(state)) &&
!options.preserveOwnedSessions) {
const { closedAgentKeys, closeErrors } = await this.closeLivePreservedOwnedSessions(state);
if (closedAgentKeys.size > 0) {
if (sandboxState &&
!removeClosedPreservedOwnedSessions(sandboxState, closedAgentKeys)) {
state._sandbox = undefined;
sandboxState = undefined;
}
}
if (closeErrors.length === 1) {
throw closeErrors[0];
}
if (closeErrors.length > 1) {
throw new errors_2.SandboxLifecycleError('Failed to close one or more live preserved owned sandbox sessions.', { errors: closeErrors });
}
if ((0, sessionState_1.hasPreservedOwnedSessions)(sandboxState)) {
if (this.sandboxConfig?.client) {
try {
await this.adoptPreservedOwnedSessions(tracingParent);
if (this.ownedSessionAgentKeys.size > 0) {
cleanupPlan.ownedSessionCloseTarget = 'all';
cleanupPlan.afterOwnedSessionClose = {
clearSandboxState: true,
forgetLivePreservedSessions: true,
};
}
else {
cleanupPlan.ownedSessionCloseTarget = undefined;
cleanupPlan.afterOwnedSessionClose = undefined;
}
}
catch (error) {
cleanupPlan.deferredError = error;
}
}
else {
state._sandbox = undefined;
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessions)(state);
cleanupPlan.ownedSessionCloseTarget = undefined;
cleanupPlan.afterOwnedSessionClose = undefined;
}
}
else if (closedAgentKeys.size > 0) {
state._sandbox = undefined;
}
}
else if (!(0, sessionState_1.hasPreservedOwnedSessions)(sandboxState)) {
state._sandbox = undefined;
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessions)(state);
}
return cleanupPlan;
}
async executeCleanupPlan(state, plan, options) {
if (plan.ownedSessionCloseTarget) {
let closeResult;
try {
closeResult = await this.closeOwnedSessions(plan.ownedSessionCloseTarget === 'all'
? undefined
: plan.ownedSessionCloseTarget, {
removeClosedSessionsFromSandboxState: Boolean(plan.afterOwnedSessionClose
?.removeClosedSessionsFromSandboxState ||
plan.afterOwnedSessionClose?.clearSandboxState),
tracingParent: options.tracingParent,
});
}
catch (error) {
options.onCloseError();
throw error;
}
const { closedAgentKeys: closedSessionAgentKeys, closeErrors } = closeResult;
if (plan.afterOwnedSessionClose?.forgetLivePreservedSessions) {
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessions)(state);
}
if (plan.afterOwnedSessionClose?.removeFreshCreationSessionsFromSandboxState) {
const sandboxState = (0, sessionState_1.getSerializedSandboxState)(state);
const freshCreationAgentKeys = new Set([...closedSessionAgentKeys].filter((agentKey) => sandboxState?.sessionsByAgent[agentKey]?.requiresFreshCreation ===
true));
if (freshCreationAgentKeys.size > 0) {
this.invalidateSerializedSessions(freshCreationAgentKeys);
}
}
if (plan.afterOwnedSessionClose?.removeClosedSessionsFromSandboxState) {
const sandboxState = (0, sessionState_1.getSerializedSandboxState)(state);
if (sandboxState &&
!removeClosedSerializedSessions(sandboxState, closedSessionAgentKeys)) {
state._sandbox = undefined;
}
}
if (closeErrors.length > 0) {
options.onCloseError();
throw lifecycleErrorForCloseErrors('Failed to close one or more owned sandbox sessions.', closeErrors);
}
if (plan.afterOwnedSessionClose?.clearSandboxState) {
state._sandbox = undefined;
}
}
if (plan.deferredError) {
throw plan.deferredError;
}
}
async closeLivePreservedOwnedSessions(state, options = {}) {
const sessionAgentKeys = new Map();
for (const entry of (0, livePreservedSessions_1.livePreservedOwnedSessionEntries)(state)) {
if (options.rejectedOnly && !entry.reuseRejected) {
continue;
}
const agentKeys = sessionAgentKeys.get(entry.session) ?? [];
agentKeys.push(entry.agentKey);
sessionAgentKeys.set(entry.session, agentKeys);
}
const closedAgentKeys = new Set();
const closeErrors = [];
for (const [session, agentKeys] of sessionAgentKeys) {
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({ state, session });
this.detachSessionAliases(session);
try {
await (0, sessionLifecycle_1.cleanupSandboxSession)(session);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({ state, session });
for (const agentKey of agentKeys) {
closedAgentKeys.add(agentKey);
}
}
catch (error) {
closeErrors.push(error);
}
}
const agentKeysWithLiveHandles = new Set((0, livePreservedSessions_1.livePreservedOwnedSessionEntries)(state).map((entry) => entry.agentKey));
for (const agentKey of agentKeysWithLiveHandles) {
closedAgentKeys.delete(agentKey);
}
return { closedAgentKeys, closeErrors };
}
async runPreStopHooksBeforeRelease() {
const shouldRunForOwnedSessions = Boolean(this.sandboxConfig?.client?.serializeSessionState);
const sessionsForPreStop = new Set();
const sessionsForHooks = new Set();
for (const [agentKey, session] of this.sessionsByAgentKey) {
if (!this.ownedSessionAgentKeys.has(agentKey)) {
sessionsForPreStop.add(session);
continue;
}
// Owned sessions without serialization run hooks through the close lifecycle.
if (shouldRunForOwnedSessions) {
sessionsForHooks.add(session);
}
}
for (const session of sessionsForPreStop) {
await (0, sessionLifecycle_1.runSandboxSessionPreStop)(session);
}
for (const session of sessionsForHooks) {
if (!sessionsForPreStop.has(session)) {
await (0, sessionLifecycle_1.runSandboxSessionPreStopHooks)(session);
}
}
}
async enqueueMemoryGeneration(state, args) {
if (!this.activeMemory || this.activeMemory.memory.generate === null) {
return;
}
try {
const manager = (0, generation_1.getOrCreateSandboxMemoryGenerationManager)({
session: this.activeMemory.session,
memory: this.activeMemory.memory,
runAs: this.activeMemory.runAs,
runAgent: args.runAgent,
tracingParent: args.tracingParent,
});
await manager.enqueueState(state, {
exception: args.exception,
inputOverride: args.inputOverride,
rolloutIdentity: {
conversationId: state._conversationId ?? undefined,
sdkSessionId: await args.sdkSessionId?.(),
groupId: args.groupId,
},
});
}
catch (error) {
(0, logger_1.logModelAndToolActionWarning)(logger_1.default, 'Failed to enqueue sandbox memory generation:', error);
}
}
async adoptPreservedOwnedSessions(tracingParent) {
// An explicit state replaces any preserved RunState entry. Do not inspect
// or resume that stale envelope before the replacement is processed. Signal
// callers to rehydrate any live tool closures against the explicit state.
if (this.sandboxConfig?.sessionState !== undefined) {
return true;
}
const sandboxState = (0, sessionState_1.getSerializedSandboxState)(this.runState);
if (!(0, sessionState_1.hasPreservedOwnedSessions)(sandboxState)) {
return false;
}
const client = this.sandboxConfig?.client;
if (!client) {
throw new errors_1.UserError('Sandbox client must be configured to restore preserved sandbox sessions.');
}
if (sandboxState && sandboxState.backendId !== client.backendId) {
throw new errors_1.UserError('RunState sandbox backend does not match the configured sandbox client.');
}
const preservedEntries = Object.entries((0, sessionState_1.getPreviousSerializedSessionsByAgent)(sandboxState, client)).filter(([, entry]) => entry.preservedOwnedSession);
let resumedSession = false;
for (const [agentKey] of preservedEntries) {
if (this.sessionsByAgentKey.has(agentKey)) {
continue;
}
const entry = (0, sessionState_1.getSerializedSessionEntryForAgent)((0, sessionState_1.getSerializedSandboxState)(this.runState), agentKey);
if (!entry?.preservedOwnedSession) {
continue;
}
(0, sessionState_1.assertSessionStateEnvelope)(client, entry.sessionState);
const trustedManifest = this.resolveTrustedManifestForAgentKey(agentKey);
const clientPreparesEnvironment = client.environmentPreparation === 'client';
const resumeTrustedManifest = clientPreparesEnvironment
? (0, sessionState_1.resolveTrustedManifestForResume)(client, trustedManifest, this.sandboxConfig?.options)
: trustedManifest;
if (clientPreparesEnvironment && !resumeTrustedManifest) {
throw new errors_1.UserError('Client-owned environment preparation requires a current trusted manifest before resuming a preserved sandbox session.');
}
const liveDecision = await this.inspectLivePreservedOwnedSessionReuse({
client,
agentKey,
serializedEntry: entry,
trustedManifest: resumeTrustedManifest,
trustedManifestAlreadyResolved: clientPreparesEnvironment,
});
if (entry.requiresFreshCreation === true) {
if (liveDecision) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
continue;
}
throw serializedOwnedSessionReplacementError(client);
}
if (liveDecision?.reusable) {
this.sessionsByAgentKey.set(agentKey, liveDecision.entry.session);
this.sessionAgentNamesByKey.set(agentKey, liveDecision.entry.currentAgentName);
this.ownedSessionAgentKeys.add(agentKey);
continue;
}
if (liveDecision?.mountAuthorityChanged) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
continue;
}
if (client.serializedSessionStateRequiresFreshCreation ||
(0, sessionState_1.serializedSessionEntryRequiresFreshMountAuthority)(entry) ||
(resumeTrustedManifest &&
((0, mountSecurity_1.manifestHasInContainerMounts)(resumeTrustedManifest) ||
(0, mountSecurity_1.manifestHasNonResumableMountAuthority)(resumeTrustedManifest)))) {
if (liveDecision) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
}
else {
this.invalidateSerializedSession(agentKey);
}
continue;
}
if (!client.resume) {
throw new errors_1.UserError('Sandbox client must implement resume() to restore preserved sandbox sessions.');
}
const serializedState = await (0, sessionState_1.deserializeSandboxSessionStateEntry)(client, entry, resumeTrustedManifest, {
clientOptions: this.sandboxConfig?.options,
snapshot: this.sandboxConfig?.snapshot,
}, { trustedManifestAlreadyResolved: clientPreparesEnvironment });
if (!serializedState) {
continue;
}
if (await serializedSessionStateRequiresFreshCreationForOptions(client, serializedState, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(clientPreparesEnvironment
? { trustedManifest: resumeTrustedManifest }
: {}),
})) {
if (liveDecision) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
}
else {
throw serializedOwnedSessionReplacementError(client);
}
continue;
}
const rejectedLiveEntry = await this.prepareRejectedLiveSessionReplacement(client, liveDecision);
let session = await (0, spans_1.withSandboxSpan)('sandbox.resume_session', {
agent_name: entry.currentAgentName,
backend_id: client.backendId,
}, async () => await resumeSandboxSession(client, serializedState, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(clientPreparesEnvironment
? { trustedManifest: resumeTrustedManifest }
: {}),
}), tracingParent);
session = await this.replaceRejectedLiveSession(rejectedLiveEntry, session);
this.applyArchiveLimits(session);
this.sessionsByAgentKey.set(agentKey, session);
this.sessionAgentNamesByKey.set(agentKey, entry.currentAgentName);
this.ownedSessionAgentKeys.add(agentKey);
resumedSession = true;
}
return resumedSession;
}
acquireAgent(agent) {
const agentId = (0, agentKeys_1.getObjectId)(agent);
if (this.acquiredAgents.has(agentId)) {
return;
}
(0, agentKeys_1.acquireSandboxAgent)(agent);
this.acquiredAgents.set(agentId, agent);
}
async ensureSession(agent, tracingParent) {
const agentId = (0, agentKeys_1.getObjectId)(agent);
const agentKey = this.agentKey(agent);
const existing = this.sessionsByAgent.get(agentId);
if (existing) {
(0, mountSecurity_1.assertSandboxSessionStateUsable)(existing.state);
this.currentAgentId = agentId;
return existing;
}
const existingByKey = this.sessionsByAgentKey.get(agentKey);
if (existingByKey) {
(0, mountSecurity_1.assertSandboxSessionStateUsable)(existingByKey.state);
this.applyArchiveLimits(existingByKey);
await this.ensureSessionStarted(existingByKey, agent, 'resume', {
oncePerSession: true,
tracingParent,
});
this.currentAgentId = agentId;
this.sessionsByAgent.set(agentId, existingByKey);
this.sessionAgentNamesByKey.set(agentKey, agent.name);
return existingByKey;
}
if (this.sandboxConfig?.session) {
const session = this.sandboxConfig.session;
(0, mountSecurity_1.assertSandboxSessionStateUsable)(session.state);
this.applyArchiveLimits(session);
const configuredManifest = this.resolveConfiguredManifest(agent, {
providedSession: session,
});
if (this.sandboxConfig.client?.environmentPreparation === 'client' &&
(manifestHasDeferredEnvironmentValues(configuredManifest.manifest) ||
manifestHasDeferredEnvironmentValues(session.state.manifest))) {
throw new errors_1.UserError('Client-owned environment preparation cannot revalidate deferred environment values for an already-running provided session. Resume the session through the client instead.');
}
// Provided sessions are already running, so only a safe additive manifest delta can
// be applied instead of reprovisioning root, env, users, groups, or mounts.
await (0, providedSessionManifest_1.applyManifestToProvidedSession)(session, configuredManifest.manifest, (0, runAsManifest_1.sandboxRunAsName)(agent.runAs));
await this.ensureSessionStarted(session, agent, 'provided', {
oncePerSession: true,
tracingParent,
});
this.registerSessionForAgent(agent, session);
return session;
}
const configuredManifest = this.resolveConfiguredManifest(agent);
(0, mountSecurity_1.validateMountCredentialBoundaries)(configuredManifest.manifest);
const client = this.requireClient();
const resumed = await this.resumeSessionForAgent(client, agent, configuredManifest.manifest, tracingParent);
if (resumed) {
this.applyArchiveLimits(resumed);
await this.ensureSessionStarted(resumed, agent, 'resume', {
tracingParent,
});
this.registerSessionForAgent(agent, resumed, { owned: true });
return resumed;
}
if (!client.create) {
throw new errors_1.UserError('Sandbox execution requires a sandbox client with create() support.');
}
const clientPreparesEnvironment = client.environmentPreparation === 'client';
if (clientPreparesEnvironment) {
(0, mountSecurity_1.validateMountEnvironmentCredentialBoundaries)(configuredManifest.manifest, {}, { preserveDeferredValuePresence: true });
}
const createManifest = clientPreparesEnvironment
? (0, manifest_1.cloneManifest)(configuredManifest.manifest)
: await (0, mountSecurity_1.resolveAndValidateMountEnvironment)(configuredManifest.manifest);
const createSession = client.create.bind(client);
const createArgs = {
snapshot: this.resolveSnapshotSpec(client),
options: this.sandboxConfig?.options,
concurrencyLimits: this.sandboxConfig?.concurrencyLimits,
archiveLimits: this.sandboxConfig?.archiveLimits,
};
if (configuredManifest.passToCreate) {
createArgs.manifest = createManifest;
}
const session = await (0, spans_1.withSandboxSpan)('sandbox.create_session', {
agent_name: agent.name,
backend_id: client.backendId,
}, async () => await createSession(createArgs), tracingParent);
if (clientPreparesEnvironment) {
bindClientPreparedTrustedManifest(session, configuredManifest.manifest);
}
else if (configuredManifest.passToCreate) {
session.state.environment = {
...(session.state.environment ?? {}),
...(await createManifest.resolveEnvironment()),
};
const manifestWithTrustedPolicies = manifestWithTrustedRuntimePolicies(session.state.manifest, configuredManifest.manifest);
(0, mountSecurity_1.recordLiveMountCredentialAuthority)(manifestWithTrustedPolicies, session.state.manifest);
session.state.manifest = manifestWithTrustedPolicies;
}
this.applyArchiveLimits(session);
await this.ensureSessionStarted(session, agent, 'create', {
tracingParent,
});
this.registerSessionForAgent(agent, session, { owned: true });
return session;
}
applyArchiveLimits(session) {
if (this.sandboxConfig?.archiveLimits === undefined) {
return;
}
session.setArchiveLimits?.(this.sandboxConfig.archiveLimits);
}
registerSessionForAgent(agent, session, options = {}) {
const agentId = (0, agentKeys_1.getObjectId)(agent);
const agentKey = this.agentKey(agent);
this.currentAgentId = agentId;
this.sessionsByAgent.set(agentId, session);
this.sessionsByAgentKey.set(agentKey, session);
this.sessionAgentNamesByKey.set(agentKey, agent.name);
if (options.owned) {
this.ownedSessionAgentKeys.add(agentKey);
(0, mountSecurity_1.captureLiveMountCredentialAuthorityIfAbsent)(session.state.manifest, session.state.environment);
}
}
async ensureSessionStarted(session, agent, reason, options = {}) {
if (!session.start) {
return;
}
if (options.oncePerSession) {
// Provided and resumed sessions may be shared by multiple agents in one run; keep
// their provider-specific start hook idempotent.
const started = this.sessionStartPromises.get(session);
if (started) {
await started;
return;
}
}
if (session.running && (await session.running())) {
if (options.oncePerSession) {
this.sessionStartPromises.set(session, Promise.resolve());
}
return;
}
const startPromise = (0, spans_1.withSandboxSpan)('sandbox.start', {
agent_name: agent.name,
}, async () => {
await session.start({ reason });
}, options.tracingParent);
if (options.oncePerSession) {
this.sessionStartPromises.set(session, startPromise);
}
try {
await startPromise;
}
catch (error) {
if (options.oncePerSession) {
this.sessionStartPromises.delete(session);
}
throw error;
}
}
getPreparedAgent(agent, session, runConfigModel, tracingParent) {
const agentId = (0, agentKeys_1.getObjectId)(agent);
const manifestSignature = getManifestSignature(session.state.manifest);
const cached = this.preparedAgents.get(agentId);
if (cached &&
this.preparedSessions.get(agentId) === session &&
this.preparedManifestSignatures.get(agentId) === manifestSignature) {
const cachedSandboxAgent = cached;
for (const capability of cachedSandboxAgent.capabilities) {
capability.bindTracingParent(tracingParent);
}
(0, agentToolConfiguration_1.refreshPreparedAgentTools)(cachedSandboxAgent);
return cachedSandboxAgent;
}
// Capability instructions include a rendered filesystem view, so a manifest change
// invalidates the prepared-agent cache even when the live session object is unchanged.
const prepared = (0, agentPreparation_1.prepareSandboxAgent)({
agent,
session,
capabilities: (0, agentPreparation_1.cloneSandboxCapabilities)(agent.capabilities),
runConfigModel,
processManifest: false,
tracingParent,
workspaceScope: this.workspaceScope,
});
this.preparedAgents.set(agentId, prepared);
this.preparedSessions.set(agentId, session);
this.preparedManifestSignatures.set(agentId, manifestSignature);
return prepared;
}
async ensureWorkspaceScopeAccessible(session, runAs) {
const cwd = this.workspaceScope.cwd;
if (cwd === undefined) {
return;
}
if (!session.directoryExists) {
throw new errors_1.UserError('Sandbox sessions used with sandbox.cwd must provide directoryExists().');
}
const accessible = await session.directoryExists(cwd, runAs);
if (!accessible) {
throw new errors_1.UserError(`Sandbox working directory "${cwd}" does not exist or is not accessible.`);
}
}
async resumeSessionForAgent(client, agent, trustedManifest, tracingParent) {
const agentKey = this.agentKey(agent);
const serializedEntry = (0, sessionState_1.getSerializedSessionEntryForAgent)((0, sessionState_1.getSerializedSandboxState)(this.runState), agentKey);
const explicitSessionStateInput = this.sandboxConfig?.sessionState;
const hasExplicitSessionState = explicitSessionStateInput !== undefined;
if (!hasExplicitSessionState && serializedEntry) {
(0, sessionState_1.assertSessionStateEnvelope)(client, serializedEntry.sessionState);
}
const clientPreparesEnvironment = client.environmentPreparation === 'client';
const resumeTrustedManifest = clientPreparesEnvironment &&
(hasExplicitSessionState || serializedEntry !== undefined)
? (0, sessionState_1.resolveTrustedManifestForResume)(client, trustedManifest, this.sandboxConfig?.options)
: trustedManifest;
const explicitTrustedManifest = explicitSessionStateInput
? resumeTrustedManifest
: trustedManifest;
if (explicitSessionStateInput) {
client.validateSessionStateForResume?.({ source: 'explicit', state: explicitSessionStateInput }, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(clientPreparesEnvironment
? { trustedManifest: explicitTrustedManifest }
: {}),
});
}
const explicitResume = hasExplicitSessionState
? await this.prepareExplicitSessionStateForResume(client, trustedManifest, clientPreparesEnvironment ? explicitTrustedManifest : undefined)
: undefined;
const explicitSessionState = explicitResume?.state;
if (explicitSessionState && !client.resume) {
throw new errors_1.UserError('Sandbox client must implement resume() to restore sandbox session state.');
}
const explicitClientPreparedLiveEntry = hasExplicitSessionState && clientPreparesEnvironment
? (0, livePreservedSessions_1.livePreservedOwnedSession)({
runState: this.runState,
client,
agentKey,
serializedEntry,
})
: undefined;
const liveDecision = hasExplicitSessionState && clientPreparesEnvironment
? explicitClientPreparedLiveEntry
? { entry: explicitClientPreparedLiveEntry, reusable: false }
: undefined
: await this.inspectLivePreservedOwnedSessionReuse({
client,
agentKey,
serializedEntry,
trustedManifest: resumeTrustedManifest,
trustedManifestAlreadyResolved: clientPreparesEnvironment,
});
if (!hasExplicitSessionState &&
serializedEntry?.requiresFreshCreation === true) {
if (liveDecision) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
return undefined;
}
throw serializedOwnedSessionReplacementError(client);
}
if (!hasExplicitSessionState && liveDecision?.reusable) {
return liveDecision.entry.session;
}
if (!hasExplicitSessionState && liveDecision?.mountAuthorityChanged) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
return undefined;
}
if (!hasExplicitSessionState &&
(client.serializedSessionStateRequiresFreshCreation ||
(0, mountSecurity_1.manifestHasInContainerMounts)(resumeTrustedManifest) ||
(0, mountSecurity_1.manifestHasNonResumableMountAuthority)(resumeTrustedManifest) ||
(0, sessionState_1.serializedSessionEntryRequiresFreshMountAuthority)(serializedEntry))) {
if (liveDecision) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
}
else {
this.invalidateSerializedSession(agentKey);
}
return undefined;
}
if (!client.resume) {
if (this.sandboxConfig?.sessionState || serializedEntry) {
throw new errors_1.UserError('Sandbox client must implement resume() to restore sandbox session state.');
}
return undefined;
}
if (explicitSessionState) {
const rejectedLiveEntry = await this.prepareRejectedLiveSessionReplacement(client, liveDecision ? { ...liveDecision, reusable: false } : undefined);
const session = await (0, spans_1.withSandboxSpan)('sandbox.resume_session', {
agent_name: agent.name,
backend_id: client.backendId,
}, async () => await resumeSandboxSession(client, explicitSessionState, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(clientPreparesEnvironment
? { trustedManifest: explicitResume?.trustedManifest }
: {}),
}), tracingParent);
return await this.replaceRejectedLiveSession(rejectedLiveEntry, session);
}
const serializedState = await (0, sessionState_1.deserializeSandboxSessionStateEntry)(client, serializedEntry, resumeTrustedManifest, {
clientOptions: this.sandboxConfig?.options,
snapshot: this.sandboxConfig?.snapshot,
}, { trustedManifestAlreadyResolved: clientPreparesEnvironment });
if (!serializedState) {
return undefined;
}
if (await serializedSessionStateRequiresFreshCreationForOptions(client, serializedState, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(clientPreparesEnvironment
? { trustedManifest: resumeTrustedManifest }
: {}),
})) {
if (liveDecision) {
await this.discardLiveSessionWithChangedMountAuthority(liveDecision);
}
else {
throw serializedOwnedSessionReplacementError(client);
}
return undefined;
}
const rejectedLiveEntry = await this.prepareRejectedLiveSessionReplacement(client, liveDecision);
const session = await (0, spans_1.withSandboxSpan)('sandbox.resume_session', {
agent_name: agent.name,
backend_id: client.backendId,
}, async () => await resumeSandboxSession(client, serializedState, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(clientPreparesEnvironment
? { trustedManifest: resumeTrustedManifest }
: {}),
}), tracingParent);
return await this.replaceRejectedLiveSession(rejectedLiveEntry, session);
}
async prepareExplicitSessionStateForResume(client, trustedManifest, preResolvedTrustedManifest) {
const persistedState = this.sandboxConfig.sessionState;
const trustedManifestForPolicy = preResolvedTrustedManifest ?? trustedManifest;
if (client.serializedSessionStateRequiresFreshCreation ||
(0, mountSecurity_1.manifestHasInContainerMounts)(trustedManifestForPolicy) ||
(0, mountSecurity_1.manifestHasNonResumableMountAuthority)(trustedManifestForPolicy) ||
(0, mountSecurity_1.manifestHasInContainerMounts)(persistedState.manifest) ||
(0, mountSecurity_1.manifestHasNonResumableMountAuthority)(persistedState.manifest)) {
throw new errors_1.UserError(client.serializedSessionStateRequiresFreshCreation
? 'This sandbox provider cannot safely resume persisted session state. Create a fresh sandbox from current trusted configuration.'
: 'Sandbox session state with in-container mounts cannot be resumed safely. Create a fresh sandbox from the current trusted manifest.');
}
const resolvedTrustedManifest = preResolvedTrustedManifest ??
(0, sessionState_1.resolveTrustedManifestForResume)(client, trustedManifest, this.sandboxConfig?.options);
if (client.backendId === 'docker' &&
client.environmentPreparation !== 'client' &&
Object.keys(persistedState.manifest.environment).some((key) => !(key in trustedManifestForPolicy.environment))) {
throw new errors_1.UserError('Docker sandbox session state cannot be resumed because the current trusted manifest removes an environment variable. Start a fresh session from a snapshot instead.');
}
const topologyReboundState = (0, manifestPersistence_1.rebindPersistedMountCredentials)(persistedState, resolvedTrustedManifest);
(0, manifestPersistence_1.assertMountCredentialsRebound)(topologyReboundState);
if (!(0, mountSecurity_1.liveMountCredentialAuthorityMatches)(persistedState.manifest, resolvedTrustedManifest)) {
throw new errors_2.SandboxMountError('Sandbox session state mount authority does not match the current trusted manifest. Create a fresh sandbox from current trusted configuration.', undefined, 'mount_config_invalid');
}
if (client.environmentPreparation === 'client') {
(0, mountSecurity_1.validateMountCredentialBoundaries)(resolvedTrustedManifest);
(0, mountSecurity_1.validateMountEnvironmentCredentialBoundaries)(resolvedTrustedManifest, {}, { preserveDeferredValuePresence: true });
const pathReboundState = (0, manifestPersistence_1.rebindPersistedPathGrants)(topologyReboundState, resolvedTrustedManifest, {
replaceWithTrustedManifest: client.backendId === 'docker',
});
(0, manifestPersistence_1.assertMountCredentialsRebound)(pathReboundState);
(0, manifestPersistence_1.assertHostPathGrantsRebound)(pathReboundState);
return {
state: persistedState,
trustedManifest: resolvedTrustedManifest,
};
}
const materializedTrustedManifest = await (0, mountSecurity_1.resolveAndValidateMountEnvironment)(resolvedTrustedManifest);
const credentialReboundState = (0, manifestPersistence_1.rebindPersistedMountCredentials)(topologyReboundState, materializedTrustedManifest);
const sessionState = (0, manifestPersistence_1.rebindPersistedPathGrants)(credentialReboundState, materializedTrustedManifest, {
replaceWithTrustedManifest: client.backendId === 'docker',
});
if (client.backendId === 'docker') {
sessionState.environment = {
...(sessionState.environment ?? {}),
...(await materializedTrustedManifest.resolveEnvironment()),
};
}
(0, manifestPersistence_1.assertMountCredentialsRebound)(sessionState);
(0, mountSecurity_1.validateMountCredentialBoundaries)(sessionState.manifest);
(0, mountSecurity_1.validateMountEnvironmentCredentialBoundaries)(sessionState.manifest, sessionState.environment ?? {});
(0, manifestPersistence_1.assertHostPathGrantsRebound)(sessionState);
return {
state: sessionState,
trustedManifest: materializedTrustedManifest,
};
}
async inspectLivePreservedOwnedSessionReuse(args) {
const liveEntry = (0, livePreservedSessions_1.livePreservedOwnedSession)({
runState: this.runState,
client: args.client,
agentKey: args.agentKey,
serializedEntry: args.serializedEntry,
});
if (!liveEntry) {
return undefined;
}
const trustedManifest = args.trustedManifestAlreadyResolved
? args.trustedManifest
: (0, sessionState_1.resolveTrustedManifestForResume)(args.client, args.trustedManifest, this.sandboxConfig?.options);
(0, sessionState_1.assertTrustedManifestForDockerRunState)(args.client, trustedManifest);
return await (0, providedSessionManifest_1.withExclusiveProvidedSessionManifestMutation)(liveEntry.session.state, async () => await (0, mountSecurity_1.withExclusiveSandboxStateInspection)(liveEntry.session.state, async () => {
if (liveEntry.reuseRejected) {
await serializedSessionStateRequiresFreshCreationForOptions(args.client, liveEntry.session.state, {
archiveLimits: this.sandboxConfig?.archiveLimits,
clientOptions: this.sandboxConfig?.options,
...(args.client.environmentPreparation === 'client'
? { trustedManifest }
: {}),
});
return {
entry: liveEntry,
reusable: false,
...(args.client
.preservedOwnedSessionReuseRejectionRequiresFreshCreation ===
true
? { mountAuthorityChanged: true }
: {}),
};
}
if ((0, mountSecurity_1.isSandboxSessionStateUnsafe)(liveEntry.session.state)) {
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
mountAuthorityChanged: true,
};
}
if (!trustedManifest ||
(0, mountSecurity_1.manifestHasNonResumableMountAuthority)(trustedManifest) ||
!(0, mountSecurity_1.liveMountCredentialAuthorityMatches)(liveEntry.session.state.manifest, trustedManifest)) {
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
mountAuthorityChanged: true,
};
}
const canReuse = args.client.canReusePreservedOwnedSession;
const clientPreparesEnvironment = args.client.environmentPreparation === 'client';
if (args.client.backendId === 'docker' &&
!clientPreparesEnvironment &&
Object.keys(liveEntry.session.state.manifest.environment).some((key) => !(key in trustedManifest.environment))) {
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return { entry: liveEntry, reusable: false };
}
const hasDeferredEnvironmentValues = manifestHasDeferredEnvironmentValues(trustedManifest) ||
manifestHasDeferredEnvironmentValues(liveEntry.session.state.manifest);
if (clientPreparesEnvironment) {
(0, mountSecurity_1.validateMountCredentialBoundaries)(trustedManifest);
(0, mountSecurity_1.validateMountEnvironmentCredentialBoundaries)(trustedManifest, {}, {
preserveDeferredValuePresence: true,
});
const hasOtherMappedAlias = [...this.sessionsByAgentKey].some(([mappedAgentKey, mappedSession]) => mappedAgentKey !== args.agentKey &&
mappedSession === liveEntry.session);
if (hasOtherMappedAlias &&
!(0, sessionState_1.clientPreparedManifestAuthoritiesMatch)(liveEntry.session.state.manifest, liveEntry.session.state.manifest, trustedManifest)) {
const requiresFreshCreation = args.client
.preservedOwnedSessionReuseRejectionRequiresFreshCreation ===
true;
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
...(requiresFreshCreation
? { mountAuthorityChanged: true }
: {}),
};
}
if (!canReuse) {
const authorityMatches = !hasDeferredEnvironmentValues &&
(0, sessionState_1.clientPreparedManifestAuthoritiesMatch)(liveEntry.session.state.manifest, liveEntry.session.state.manifest, trustedManifest);
if (!authorityMatches) {
const requiresFreshCreation = args.client
.preservedOwnedSessionReuseRejectionRequiresFreshCreation ===
true;
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
...(requiresFreshCreation
? { mountAuthorityChanged: true }
: {}),
};
}
}
else {
const reusable = await canReuse.call(args.client, liveEntry.session.state, {
clientOptions: this.sandboxConfig?.options,
revalidateManifestEntries: true,
trustedManifest,
});
if (!reusable) {
const requiresFreshCreation = args.client
.preservedOwnedSessionReuseRejectionRequiresFreshCreation ===
true;
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
...(requiresFreshCreation
? { mountAuthorityChanged: true }
: {}),
};
}
}
args.client.rebindPreservedOwnedSessionState?.(liveEntry.session.state, {
clientOptions: this.sandboxConfig?.options,
revalidateManifestEntries: true,
trustedManifest,
});
bindClientPreparedTrustedManifest(liveEntry.session, trustedManifest);
return { entry: liveEntry, reusable: true };
}
const candidateState = (0, manifestPersistence_1.rebindPersistedPathGrants)(liveEntry.session.state, trustedManifest, {
replaceWithTrustedManifest: args.client.backendId === 'docker',
});
(0, manifest_1.replaceManifestMountCredentialExposurePolicy)(candidateState.manifest, trustedManifest);
(0, mountSecurity_1.recordLiveMountCredentialAuthority)(candidateState.manifest, liveEntry.session.state.manifest);
(0, mountSecurity_1.validateMountCredentialBoundaries)(trustedManifest);
if (args.client.backendId === 'docker') {
candidateState.environment = {
...(liveEntry.session.state.environment ?? {}),
...(await trustedManifest.resolveEnvironment()),
};
}
if (!canReuse) {
const trustedEnvironment = await trustedManifest.resolveEnvironment();
if (!(0, mountSecurity_1.liveMountEnvironmentAuthorityMatches)(liveEntry.session.state.manifest, trustedManifest, trustedEnvironment)) {
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
mountAuthorityChanged: true,
};
}
await refreshLiveEnvironmentReferences(candidateState, trustedManifest, trustedEnvironment);
}
else {
const reusable = await canReuse.call(args.client, candidateState, {
clientOptions: this.sandboxConfig?.options,
revalidateManifestEntries: true,
trustedManifest,
});
if (!reusable) {
const requiresFreshCreation = args.client
.preservedOwnedSessionReuseRejectionRequiresFreshCreation ===
true;
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: liveEntry.session,
});
return {
entry: liveEntry,
reusable: false,
...(requiresFreshCreation
? { mountAuthorityChanged: true }
: {}),
};
}
if (args.client.backendId !== 'docker') {
await refreshLiveEnvironmentReferences(candidateState, trustedManifest);
}
}
args.client.rebindPreservedOwnedSessionState?.(liveEntry.session.state, {
clientOptions: this.sandboxConfig?.options,
revalidateManifestEntries: true,
trustedManifest,
});
// Rebind only after the backend has verified that its live authority still
// matches the current trusted manifest.
const reboundManifest = args.client.backendId === 'docker'
? manifestWithTrustedRuntimePolicies((0, manifestPersistence_1.rebindPersistedPathGrants)(liveEntry.session.state, trustedManifest, {
replaceWithTrustedGrantSet: true,
}).manifest, trustedManifest)
: candidateState.manifest;
(0, mountSecurity_1.recordLiveMountCredentialAuthority)(reboundManifest, candidateState.manifest);
liveEntry.session.state.manifest = reboundManifest;
liveEntry.session.state.environment = candidateState.environment;
return { entry: liveEntry, reusable: true };
}));
}
async discardLiveSessionWithChangedMountAuthority(decision) {
const session = decision.entry.session;
const agentKeys = new Set([decision.entry.agentKey]);
for (const entry of (0, livePreservedSessions_1.livePreservedOwnedSessionEntries)(this.runState)) {
if (entry.session === session) {
agentKeys.add(entry.agentKey);
}
}
for (const agentKey of this.detachSessionAliases(session)) {
agentKeys.add(agentKey);
}
this.markSerializedSessionsRequireFreshCreation(agentKeys);
for (const agentKey of agentKeys) {
this.sessionsByAgentKey.delete(agentKey);
this.sessionAgentNamesByKey.delete(agentKey);
this.ownedSessionAgentKeys.delete(agentKey);
}
await (0, sessionLifecycle_1.cleanupSandboxSession)(decision.entry.session);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session,
});
this.invalidateSerializedSessions(agentKeys);
}
detachSessionAliases(session) {
const detachedAgentKeys = new Set();
for (const [agentKey, mappedSession] of this.sessionsByAgentKey) {
if (mappedSession !== session) {
continue;
}
detachedAgentKeys.add(agentKey);
this.sessionsByAgentKey.delete(agentKey);
this.sessionAgentNamesByKey.delete(agentKey);
this.ownedSessionAgentKeys.delete(agentKey);
}
for (const [agentId, mappedSession] of this.sessionsByAgent) {
if (mappedSession !== session) {
continue;
}
this.sessionsByAgent.delete(agentId);
if (this.currentAgentId === agentId) {
this.currentAgentId = undefined;
}
}
for (const [agentId, preparedSession] of this.preparedSessions) {
if (preparedSession !== session) {
continue;
}
this.preparedSessions.delete(agentId);
this.preparedAgents.delete(agentId);
this.preparedManifestSignatures.delete(agentId);
}
if (this.activeMemory?.session === session) {
this.activeMemory = undefined;
}
return detachedAgentKeys;
}
invalidateSerializedSession(agentKey) {
this.invalidateSerializedSessions(new Set([agentKey]));
}
markSerializedSessionsRequireFreshCreation(agentKeys) {
const sandboxState = (0, sessionState_1.getSerializedSandboxState)(this.runState);
if (!sandboxState) {
return;
}
for (const agentKey of agentKeys) {
const entry = sandboxState.sessionsByAgent[agentKey];
if (!entry) {
continue;
}
entry.preservedOwnedSession = true;
entry.reuseLiveSession = false;
entry.requiresFreshCreation = true;
}
}
invalidateSerializedSessions(agentKeys) {
const sandboxState = (0, sessionState_1.getSerializedSandboxState)(this.runState);
const agentKeysWithLiveHandles = new Set(this.runState
? (0, livePreservedSessions_1.livePreservedOwnedSessionEntries)(this.runState).map((entry) => entry.agentKey)
: []);
const removableAgentKeys = new Set([...agentKeys].filter((agentKey) => !agentKeysWithLiveHandles.has(agentKey)));
if (sandboxState &&
removableAgentKeys.size > 0 &&
!removeClosedSerializedSessions(sandboxState, removableAgentKeys)) {
this.runState._sandbox = undefined;
}
}
async prepareRejectedLiveSessionReplacement(client, decision) {
if (!decision || decision.reusable) {
return undefined;
}
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session: decision.entry.session,
});
if (client.backendId === 'docker') {
return decision.entry;
}
this.detachSessionAliases(decision.entry.session);
await (0, sessionLifecycle_1.cleanupSandboxSession)(decision.entry.session);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session: decision.entry.session,
});
return undefined;
}
async replaceRejectedLiveSession(rejectedEntry, replacement) {
if (!rejectedEntry) {
return replacement;
}
(0, livePreservedSessions_1.rememberRejectedLivePreservedOwnedSessionHandle)({
state: this.runState,
source: rejectedEntry,
session: replacement,
});
this.detachSessionAliases(rejectedEntry.session);
try {
await (0, sessionLifecycle_1.cleanupSandboxSession)(rejectedEntry.session);
}
catch (rejectedCleanupError) {
try {
await (0, sessionLifecycle_1.cleanupSandboxSession)(replacement);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session: replacement,
});
}
catch (replacementCleanupError) {
throw new errors_2.SandboxLifecycleError('Failed to close the rejected live sandbox session and its replacement.', {
errors: [rejectedCleanupError, replacementCleanupError],
});
}
throw rejectedCleanupError;
}
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session: rejectedEntry.session,
});
const siblingRejectedSessions = new Set(this.runState
? (0, livePreservedSessions_1.livePreservedOwnedSessionEntries)(this.runState)
.filter((entry) => entry.reuseRejected &&
entry.agentKey === rejectedEntry.agentKey &&
entry.session !== replacement)
.map((entry) => entry.session)
: []);
const siblingCleanupErrors = [];
for (const siblingSession of siblingRejectedSessions) {
this.detachSessionAliases(siblingSession);
try {
await (0, sessionLifecycle_1.cleanupSandboxSession)(siblingSession);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session: siblingSession,
});
}
catch (error) {
siblingCleanupErrors.push(error);
}
}
if (siblingCleanupErrors.length > 0) {
try {
await (0, sessionLifecycle_1.cleanupSandboxSession)(replacement);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session: replacement,
});
}
catch (replacementCleanupError) {
siblingCleanupErrors.push(replacementCleanupError);
}
throw lifecycleErrorForCloseErrors('Failed to close rejected live sandbox session handles and their replacement.', siblingCleanupErrors);
}
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session: replacement,
});
return replacement;
}
async serializeState(args = {}) {
const currentAgent = this.currentAgentId
? this.acquiredAgents.get(this.currentAgentId)
: undefined;
const sandboxState = (0, sessionState_1.getSerializedSandboxState)(this.runState);
const preferredCurrentAgentKey = currentAgent
? this.agentKey(currentAgent)
: sandboxState?.currentAgentKey;
const retainedRejectedAgentKeys = new Set(this.runState
? (0, livePreservedSessions_1.livePreservedOwnedSessionEntries)(this.runState)
.filter((entry) => entry.reuseRejected)
.map((entry) => entry.agentKey)
: []);
return await (0, sessionSerialization_1.serializeSandboxRuntimeState)({
client: this.sandboxConfig?.client,
sandboxState,
sessionsByAgentKey: this.sessionsByAgentKey,
sessionAgentNamesByKey: this.sessionAgentNamesByKey,
ownedSessionAgentKeys: this.ownedSessionAgentKeys,
trustedManifestForAgentKey: (agentKey) => (0, sessionState_1.resolveTrustedManifestForResume)(this.sandboxConfig.client, this.resolveTrustedManifestForAgentKey(agentKey, this.ownedSessionAgentKeys.has(agentKey)
? undefined
: this.sessionsByAgentKey.get(agentKey)), this.sandboxConfig?.options),
clientOptions: this.sandboxConfig?.options,
includeOwnedSessions: args.includeOwnedSessions,
preferredCurrentAgentKey,
retainedPreservedOwnedSessionAgentKeys: retainedRejectedAgentKeys,
});
}
requireClient() {
if (!this.sandboxConfig?.client) {
throw new errors_1.UserError('Sandbox execution requires `RunConfig.sandbox.client` unless a live session is provided.');
}
return this.sandboxConfig.client;
}
resolveSnapshotSpec(client) {
if (this.sandboxConfig?.snapshot) {
return this.sandboxConfig.snapshot;
}
if (!client.supportsDefaultOptions) {
return undefined;
}
return { type: 'local' };
}
agentKey(agent) {
const agentId = (0, agentKeys_1.getObjectId)(agent);
const existing = this.agentKeys.get(agentId);
if (existing) {
this.agentsByKey.set(existing, agent);
return existing;
}
const agentKey = this.allocateRuntimeAgentKey(agent.name);
this.agentKeys.set(agentId, agentKey);
this.agentsByKey.set(agentKey, agent);
return agentKey;
}
allocateRuntimeAgentKey(agentName) {
const usedKeys = new Set(this.agentKeys.values());
if (!usedKeys.has(agentName)) {
return agentName;
}
let suffix = 2;
while (usedKeys.has(`${agentName}_${suffix}`)) {
suffix += 1;
}
return `${agentName}_${suffix}`;
}
resolveConfiguredManifest(agent, options = {}) {
const baseManifest = this.sandboxConfig?.manifest ??
agent.defaultManifest ??
options.providedSession?.state.manifest;
const initialManifest = baseManifest
? (0, manifest_1.cloneManifest)(baseManifest)
: new manifest_1.Manifest();
const manifestWithIdentity = options.providedSession
? initialManifest
: (0, runAsManifest_1.manifestWithRunAsUser)(initialManifest, agent.runAs);
const configuredManifest = (0, agentPreparation_1.cloneSandboxCapabilities)(agent.capabilities).reduce((manifest, capability) => capability.processManifest(manifest), manifestWithIdentity);
// Passing a truly default manifest to providers can override their natural root
// defaults, so create() receives a manifest only when configuration changed it.
return {
manifest: configuredManifest,
passToCreate: baseManifest !== undefined || !isDefaultManifest(configuredManifest),
};
}
resolveTrustedManifestForAgentKey(agentKey, providedSession) {
const agent = this.agentsByKey.get(agentKey);
if (!agent || !(0, agentKeys_1.isSandboxAgent)(agent)) {
return undefined;
}
return this.resolveConfiguredManifest(agent, { providedSession }).manifest;
}
async closeOwnedSessions(agentKeys, options = {}) {
const keysToClose = [...(agentKeys ?? this.ownedSessionAgentKeys)].filter((agentKey) => this.ownedSessionAgentKeys.has(agentKey));
const sessionsToClose = new Map();
for (const agentKey of keysToClose) {
const session = this.sessionsByAgentKey.get(agentKey);
if (!session || !(0, sessionLifecycle_1.hasSessionCleanup)(session)) {
continue;
}
const existing = sessionsToClose.get(session);
if (existing) {
existing.agentKeys.push(agentKey);
}
else {
sessionsToClose.set(session, {
agentKeys: [agentKey],
agentName: this.sessionAgentNamesByKey.get(agentKey),
});
}
}
if (sessionsToClose.size === 0) {
return { closedAgentKeys: new Set(), closeErrors: [] };
}
const closedAgentKeys = new Set();
const closeErrors = [];
await (0, spans_1.withSandboxSpan)('sandbox.cleanup_sessions', {
session_count: sessionsToClose.size,
}, async (cleanupSessionsSpan) => {
const sessions = [...sessionsToClose];
const results = await Promise.allSettled(sessions.map(async ([session, { agentName }]) => {
await (0, spans_1.withSandboxSpan)('sandbox.shutdown', {
agent_name: agentName,
}, async () => {
(0, livePreservedSessions_1.rejectLivePreservedOwnedSessionHandle)({
state: this.runState,
session,
});
await (0, sessionLifecycle_1.cleanupSandboxSession)(session);
(0, livePreservedSessions_1.forgetLivePreservedOwnedSessionHandle)({
state: this.runState,
session,
});
}, cleanupSessionsSpan);
}));
for (const [index, result] of results.entries()) {
const [session, { agentKeys }] = sessions[index];
if (result.status === 'rejected') {
closeErrors.push(result.reason);
continue;
}
const closedKeys = new Set(agentKeys);
if (options.removeClosedSessionsFromSandboxState) {
this.invalidateSerializedSessions(closedKeys);
}
for (const agentKey of closedKeys) {
this.sessionsByAgentKey.delete(agentKey);
this.sessionAgentNamesByKey.delete(agentKey);
this.ownedSessionAgentKeys.delete(agentKey);
closedAgentKeys.add(agentKey);
}
for (const [agentId, mappedSession] of this.sessionsByAgent) {
if (mappedSession === session) {
this.sessionsByAgent.delete(agentId);
if (this.currentAgentId === agentId) {
this.currentAgentId = undefined;
}
}
}
for (const [agentId, preparedSession] of this.preparedSessions) {
if (preparedSession === session) {
this.preparedSessions.delete(agentId);
this.preparedAgents.delete(agentId);
this.preparedManifestSignatures.delete(agentId);
}
}
if (this.activeMemory?.session === session) {
this.activeMemory = undefined;
}
}
}, options.tracingParent);
return { closedAgentKeys, closeErrors };
}
releaseAgents() {
(0, agentKeys_1.releaseSandboxAgents)(this.acquiredAgents.values());
this.acquiredAgents.clear();
}
}
exports.SandboxRuntimeManager = SandboxRuntimeManager;
function getManifestSignature(manifest) {
return (0, stableJson_1.stableJsonStringify)({
version: manifest.version,
root: manifest.root,
entries: manifest.entries,
environment: (0, environment_1.serializeManifestEnvironment)(manifest),
users: manifest.users,
groups: manifest.groups,
extraPathGrants: manifest.extraPathGrants,
remoteMountCommandAllowlist: manifest.remoteMountCommandAllowlist,
mountCredentialExposure: manifest.mountTargets().map(({ mountPath }) => ({
mountPath,
mountScoped: (0, manifest_1.manifestAcknowledgesInContainerMountCredentialExposure)(manifest, mountPath, 'mount_scoped'),
broad: (0, manifest_1.manifestAcknowledgesInContainerMountCredentialExposure)(manifest, mountPath, 'broad'),
})),
});
}
function isDefaultManifest(manifest) {
const defaultManifest = new manifest_1.Manifest();
return (manifest.version === defaultManifest.version &&
manifest.root === defaultManifest.root &&
Object.keys(manifest.entries).length === 0 &&
Object.keys(manifest.environment).length === 0 &&
manifest.users.length === 0 &&
manifest.groups.length === 0 &&
manifest.extraPathGrants.length === 0 &&
(0, remoteMountCommandAllowlist_1.isDefaultRemoteMountCommandAllowlist)(manifest.remoteMountCommandAllowlist));
}
function manifestWithTrustedRuntimePolicies(manifest, trustedManifest) {
const merged = new manifest_1.Manifest({
version: manifest.version,
root: manifest.root,
entries: structuredClone(manifest.entries),
environment: Object.fromEntries(Object.entries(trustedManifest.environment).map(([key, value]) => [
key,
value.init(),
])),
users: structuredClone(manifest.users),
groups: structuredClone(manifest.groups),
extraPathGrants: structuredClone(manifest.extraPathGrants),
remoteMountCommandAllowlist: [
...trustedManifest.remoteMountCommandAllowlist,
],
});
(0, manifest_1.replaceManifestMountCredentialExposurePolicy)(merged, trustedManifest);
return merged;
}
function manifestHasDeferredEnvironmentValues(manifest) {
return Object.values(manifest.environment).some((value) => (0, manifest_1.isEnvValueReference)(value) || value.resolver !== undefined);
}
async function resumeSandboxSession(client, state, options) {
if (client.environmentPreparation !== 'client') {
return await client.resume(state, options);
}
const session = await (0, providedSessionManifest_1.withExclusiveProvidedSessionManifestMutation)(state, () => (0, mountSecurity_1.withExclusiveSandboxStateInspection)(state, async () => {
const previousManifest = state.manifest;
const rebound = (0, manifestPersistence_1.rebindPersistedPathGrants)((0, manifestPersistence_1.rebindPersistedMountCredentials)(state, options.trustedManifest), options.trustedManifest, { replaceWithTrustedManifest: client.backendId === 'docker' });
(0, manifestPersistence_1.assertMountCredentialsRebound)(rebound);
(0, manifestPersistence_1.assertHostPathGrantsRebound)(rebound);
// Preserve branded provider state while restoring SDK-owned authority before
// the provider can use it. A failed resume must leave the input retryable.
state.manifest = rebound.manifest;
try {
const resumed = await client.resume(state, options);
if (resumed.state === state) {
bindClientPreparedTrustedManifest(resumed, options.trustedManifest);
}
return resumed;
}
catch (error) {
state.manifest = previousManifest;
throw error;
}
}));
if (session.state !== state) {
// Do not hold the input lock while acquiring a distinct returned-state lock.
await (0, providedSessionManifest_1.withExclusiveProvidedSessionManifestMutation)(session.state, () => (0, mountSecurity_1.withExclusiveSandboxStateInspection)(session.state, async () => {
bindClientPreparedTrustedManifest(session, options.trustedManifest);
}));
}
return session;
}
function bindClientPreparedTrustedManifest(session, trustedManifest) {
const runnerManifest = (0, sessionState_1.mergeClientPreparedManifestAuthority)(session.state.manifest, trustedManifest);
(0, mountSecurity_1.recordLiveMountCredentialAuthority)(runnerManifest, trustedManifest);
session.state.manifest = runnerManifest;
}
async function refreshLiveEnvironmentReferences(state, trustedManifest, resolvedTrustedEnvironment) {
if (!trustedManifest) {
return;
}
const referenceKeys = new Set([
...Object.entries(state.manifest.environment)
.filter(([, value]) => (0, manifest_1.isEnvValueReference)(value))
.map(([key]) => key),
...Object.entries(trustedManifest.environment)
.filter(([, value]) => (0, manifest_1.isEnvValueReference)(value))
.map(([key]) => key),
]);
if (referenceKeys.size === 0) {
return;
}
const resolvedEnvironment = Object.fromEntries((await Promise.all([...referenceKeys].map(async (key) => {
const value = trustedManifest.environment[key];
return value
? [
key,
resolvedTrustedEnvironment?.[key] ?? (await value.resolve()),
]
: undefined;
}))).filter((entry) => entry !== undefined));
const environment = { ...(state.environment ?? {}) };
const manifestEnvironment = Object.fromEntries(Object.entries(state.manifest.environment)
.filter(([key]) => !referenceKeys.has(key))
.map(([key, value]) => [key, value.init()]));
for (const key of referenceKeys) {
delete environment[key];
const value = trustedManifest.environment[key];
if (value) {
manifestEnvironment[key] = value.init();
}
}
const manifest = new manifest_1.Manifest({
version: state.manifest.version,
root: state.manifest.root,
entries: structuredClone(state.manifest.entries),
environment: manifestEnvironment,
users: structuredClone(state.manifest.users),
groups: structuredClone(state.manifest.groups),
extraPathGrants: structuredClone(state.manifest.extraPathGrants),
remoteMountCommandAllowlist: [
...state.manifest.remoteMountCommandAllowlist,
],
});
(0, manifest_1.replaceManifestMountCredentialExposurePolicy)(manifest, state.manifest);
(0, mountSecurity_1.recordLiveMountCredentialAuthority)(manifest, state.manifest);
state.environment = {
...environment,
...resolvedEnvironment,
};
state.manifest = manifest;
}
function removeClosedPreservedOwnedSessions(sandboxState, agentKeys) {
return removeClosedSerializedSessions(sandboxState, agentKeys, {
requirePreservedSession: true,
});
}
async function serializedSessionStateRequiresFreshCreationForOptions(client, state, options = {}) {
return ((await client.serializedSessionStateRequiresFreshCreationForOptions?.(state, options)) ?? false);
}
function serializedOwnedSessionReplacementError(client) {
return new errors_1.UserError(`Sandbox client "${client.backendId}" cannot safely replace an owned sandbox from untrusted serialized state because remote ownership cannot be verified. Reconnect through a caller-managed sandbox selection, or explicitly clean up the old sandbox before starting a new run.`);
}
function lifecycleErrorForCloseErrors(message, errors) {
return errors.length === 1
? errors[0]
: new errors_2.SandboxLifecycleError(message, { errors });
}
function removeClosedSerializedSessions(sandboxState, agentKeys, options = {}) {
for (const agentKey of agentKeys) {
delete sandboxState.sessionsByAgent[agentKey];
}
const remainingEntries = Object.values(sandboxState.sessionsByAgent);
if (options.requirePreservedSession &&
!remainingEntries.some((entry) => entry.preservedOwnedSession)) {
return false;
}
const currentEntry = sandboxState.sessionsByAgent[sandboxState.currentAgentKey];
if (currentEntry) {
sandboxState.currentAgentName = currentEntry.currentAgentName;
sandboxState.sessionState = currentEntry.sessionState;
return true;
}
const nextEntry = options.requirePreservedSession
? remainingEntries.find((entry) => entry.preservedOwnedSession)
: remainingEntries[0];
if (!nextEntry) {
return false;
}
sandboxState.currentAgentKey = nextEntry.currentAgentKey;
sandboxState.currentAgentName = nextEntry.currentAgentName;
sandboxState.sessionState = nextEntry.sessionState;
return true;
}
//# sourceMappingURL=manager.js.map