diff --git a/packages/adapter-utils/src/execution-target-sandbox.test.ts b/packages/adapter-utils/src/execution-target-sandbox.test.ts index 5c3f86c03a..90fcb45b76 100644 --- a/packages/adapter-utils/src/execution-target-sandbox.test.ts +++ b/packages/adapter-utils/src/execution-target-sandbox.test.ts @@ -34,6 +34,7 @@ import { startAdapterExecutionTargetProcessSessionBridge, startAdapterExecutionTargetPaperclipBridge, type AdapterSandboxExecutionTarget, + type EffectiveExecutionCapabilities, type EffectiveSandboxCapabilities, } from "./execution-target.js"; import { @@ -3102,7 +3103,7 @@ describe("sandbox adapter execution targets", () => { // The full effective-capability snapshot with one flag set. The two strict // gates read `duplexCommandStream`; the other flags stay false. - function duplexCapabilities(duplexCommandStream: boolean): EffectiveSandboxCapabilities { + function duplexCapabilities(duplexCommandStream: boolean): EffectiveExecutionCapabilities { return { reusableLeases: false, nativeSyncIn: false, @@ -6900,3 +6901,23 @@ describe("duplex readiness gate replay-buffer reservation", () => { expect(counts.underflows).toBe(0); }); }); + +describe("EffectiveSandboxCapabilities deprecated alias", () => { + it("still type-checks as EffectiveExecutionCapabilities", () => { + // A type-level check, not a runtime one: this assignment fails to compile + // if the alias drifts from the renamed interface. Keep it here so a later + // removal of the alias is a deliberate act, not an accident. + const snapshot: EffectiveExecutionCapabilities = { + reusableLeases: false, + nativeSyncIn: false, + nativeSyncOut: false, + persistentProcessSessions: false, + independentControlCommands: false, + incrementalSessionOutput: false, + concurrentSyncOperations: false, + duplexCommandStream: false, + }; + const aliased: EffectiveSandboxCapabilities = snapshot; + expect(aliased).toEqual(snapshot); + }); +}); diff --git a/packages/adapter-utils/src/execution-target.ts b/packages/adapter-utils/src/execution-target.ts index a974f39453..d8f740a25d 100644 --- a/packages/adapter-utils/src/execution-target.ts +++ b/packages/adapter-utils/src/execution-target.ts @@ -137,13 +137,14 @@ export interface AdapterSshExecutionTarget extends AdapterExecutionTargetWorkspa } /** - * Read-only snapshot of the effective sandbox capabilities for one execution - * target. Each flag is the resolved result of the provider's declaration, the - * live worker's verified methods, and any narrowing from the config or lease. - * The host computes it once and attaches it to the target; a consumer reads it - * but never changes it, so every field is `readonly`. + * Read-only snapshot of the effective execution capabilities for one + * execution target — local, ssh, sandbox, or plugin. Each flag is the + * resolved result of the provider's declaration, the live worker's verified + * methods, and any narrowing from the config or lease. The host computes it + * once and attaches it to the target; a consumer reads it but never changes + * it, so every field is `readonly`. */ -export interface EffectiveSandboxCapabilities { +export interface EffectiveExecutionCapabilities { readonly reusableLeases: boolean; readonly nativeSyncIn: boolean; readonly nativeSyncOut: boolean; @@ -154,6 +155,12 @@ export interface EffectiveSandboxCapabilities { readonly duplexCommandStream: boolean; } +/** + * @deprecated Renamed to `EffectiveExecutionCapabilities`. This alias will + * be removed in a later major release. + */ +export interface EffectiveSandboxCapabilities extends EffectiveExecutionCapabilities {} + export interface AdapterSandboxExecutionTarget extends AdapterExecutionTargetWorkspaceMetadata { kind: "remote"; transport: "sandbox"; @@ -163,7 +170,7 @@ export interface AdapterSandboxExecutionTarget extends AdapterExecutionTargetWor * resolves it from the provider declaration ∩ the verified worker methods ∩ * narrowing, then attaches it here. Absent when no snapshot was resolved. */ - readonly effectiveCapabilities?: EffectiveSandboxCapabilities | null; + readonly effectiveCapabilities?: EffectiveExecutionCapabilities | null; /** * Per-run duplex bridge kill switch. The host stamps it on the same seam as * `effectiveCapabilities`. `true` selects the duplex transport only when the @@ -345,7 +352,7 @@ function readString(value: unknown): string | null { // missing or non-boolean field reads as `false`, so a round-tripped target // never grants a capability that the snapshot did not carry. Returns null when // there is no object to read. -function parseEffectiveSandboxCapabilities(value: unknown): EffectiveSandboxCapabilities | null { +function parseEffectiveExecutionCapabilities(value: unknown): EffectiveExecutionCapabilities | null { const parsed = parseObject(value); if (Object.keys(parsed).length === 0) return null; return { @@ -1311,7 +1318,7 @@ export function parseAdapterExecutionTarget(value: unknown): AdapterExecutionTar if (kind === "remote" && readStringMeta(parsed, "transport") === "sandbox") { const remoteCwd = readStringMeta(parsed, "remoteCwd"); if (!remoteCwd) return null; - const effectiveCapabilities = parseEffectiveSandboxCapabilities(parsed.effectiveCapabilities); + const effectiveCapabilities = parseEffectiveExecutionCapabilities(parsed.effectiveCapabilities); return { kind: "remote", transport: "sandbox", diff --git a/server/src/__tests__/environment-execution-target-capabilities.test.ts b/server/src/__tests__/environment-execution-target-capabilities.test.ts index 9a2785ad9d..b9cb1245cb 100644 --- a/server/src/__tests__/environment-execution-target-capabilities.test.ts +++ b/server/src/__tests__/environment-execution-target-capabilities.test.ts @@ -8,11 +8,11 @@ vi.mock("../services/environment-config.js", () => ({ resolveEnvironmentDriverConfigForRuntime: mockResolveEnvironmentDriverConfigForRuntime, })); -import type { EffectiveSandboxCapabilities } from "@paperclipai/adapter-utils/execution-target"; +import type { EffectiveExecutionCapabilities } from "@paperclipai/adapter-utils/execution-target"; import { resolveEnvironmentExecutionTarget } from "../services/environment-execution-target.js"; import type { EnvironmentRuntimeService } from "../services/environment-runtime.js"; -const SNAPSHOT: EffectiveSandboxCapabilities = { +const SNAPSHOT: EffectiveExecutionCapabilities = { reusableLeases: true, nativeSyncIn: true, nativeSyncOut: false, @@ -27,7 +27,7 @@ const SNAPSHOT: EffectiveSandboxCapabilities = { // A snapshot that grants every capability. A test overrides one flag to prove // that the removed capability alone changes the runtime decision. -const FULL_GRANT: EffectiveSandboxCapabilities = { +const FULL_GRANT: EffectiveExecutionCapabilities = { reusableLeases: true, nativeSyncIn: true, nativeSyncOut: true, @@ -42,7 +42,7 @@ const FULL_GRANT: EffectiveSandboxCapabilities = { // `supportsSync` result. The helper returns the sandbox target so a test reads // the runner and the streaming flag the snapshot gates. async function buildSandboxTarget(input: { - snapshot: EffectiveSandboxCapabilities | null; + snapshot: EffectiveExecutionCapabilities | null; supportsSync: boolean; config?: Record; // Reject the capability resolution to exercise the fail-closed error path. @@ -128,7 +128,7 @@ describe("resolveEnvironmentExecutionTarget effective capability snapshot", () = // The snapshot is read-only: it is frozen, so a write does not change it. expect(Object.isFrozen(target.effectiveCapabilities)).toBe(true); - const snapshot = target.effectiveCapabilities as EffectiveSandboxCapabilities; + const snapshot = target.effectiveCapabilities as EffectiveExecutionCapabilities; try { (snapshot as { reusableLeases: boolean }).reusableLeases = false; } catch { diff --git a/server/src/__tests__/environment-execution-target-duplex.test.ts b/server/src/__tests__/environment-execution-target-duplex.test.ts index 0343b7e254..a0d7b2f2d9 100644 --- a/server/src/__tests__/environment-execution-target-duplex.test.ts +++ b/server/src/__tests__/environment-execution-target-duplex.test.ts @@ -20,7 +20,7 @@ vi.mock("../services/plugin-environment-driver.js", async (importActual) => ({ resolvePluginSandboxProviderDriverById: mockResolvePluginSandboxProviderDriverById, })); -import type { EffectiveSandboxCapabilities } from "@paperclipai/adapter-utils/execution-target"; +import type { EffectiveExecutionCapabilities } from "@paperclipai/adapter-utils/execution-target"; import { adapterExecutionTargetDuplexAggregateByteLedger } from "@paperclipai/adapter-utils/execution-target"; import { DEFAULT_MAX_AGGREGATE_DUPLEX_ROUTE_BYTES, @@ -44,7 +44,7 @@ import type { // A snapshot that grants the opt-in duplex capability, plus the rest true so the // gate reads only the duplex flag. -const DUPLEX_GRANT: EffectiveSandboxCapabilities = { +const DUPLEX_GRANT: EffectiveExecutionCapabilities = { reusableLeases: true, nativeSyncIn: true, nativeSyncOut: true, @@ -54,7 +54,7 @@ const DUPLEX_GRANT: EffectiveSandboxCapabilities = { duplexCommandStream: true, }; -const DUPLEX_ABSENT: EffectiveSandboxCapabilities = { +const DUPLEX_ABSENT: EffectiveExecutionCapabilities = { ...DUPLEX_GRANT, duplexCommandStream: false, }; @@ -346,7 +346,7 @@ describe("sandbox execution target aggregate byte ledger stamp", () => { // environment runtime whose openDuplexChannel is a spy. The helper returns the // runner and the spy so a test reads the capability-gated member. async function buildSandboxRunner(input: { - snapshot: EffectiveSandboxCapabilities | null; + snapshot: EffectiveExecutionCapabilities | null; }) { mockResolveEnvironmentDriverConfigForRuntime.mockResolvedValue({ driver: "sandbox", diff --git a/server/src/services/environment-runtime.ts b/server/src/services/environment-runtime.ts index a87f517ff5..ebb90b6e87 100644 --- a/server/src/services/environment-runtime.ts +++ b/server/src/services/environment-runtime.ts @@ -14,7 +14,7 @@ import type { SandboxProviderCapabilities, } from "@paperclipai/shared"; import { resolveDeclaredSandboxCapabilities } from "@paperclipai/shared"; -import type { EffectiveSandboxCapabilities } from "@paperclipai/adapter-utils/execution-target"; +import type { EffectiveExecutionCapabilities } from "@paperclipai/adapter-utils/execution-target"; import type { CommandManagedDuplexChannel, } from "@paperclipai/adapter-utils/command-managed-runtime"; @@ -259,7 +259,7 @@ export function classifyEnvironmentCapabilities(input: { declared?: Partial | null; narrowing?: Partial> | null; supportedCapabilities?: ReadonlySet | null; -}): EffectiveSandboxCapabilities { +}): EffectiveExecutionCapabilities { const verifiedMethods = new Set(input.verifiedMethods ?? []); const declared = input.declared ?? {}; const narrowing = input.narrowing ?? {}; @@ -606,7 +606,7 @@ export interface EnvironmentRuntimeDriver { * "this driver has no capability model" apart from "this driver is not * registered" reads {@link ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT} directly. */ - resolveCapabilities(input: EnvironmentDriverLeaseInput): Promise; + resolveCapabilities(input: EnvironmentDriverLeaseInput): Promise; /** * Retry the provider teardown for an orphan sandbox that an earlier acquire * provisioned but could not tear down. The pending-cleanup lease row carries @@ -2592,7 +2592,7 @@ function createSandboxEnvironmentDriver( */ async function resolveSandboxCapabilitiesForLease( input: EnvironmentDriverLeaseInput, - ): Promise { + ): Promise { const metadata = input.lease.metadata ?? {}; const providerKey = readString(metadata.provider) ?? @@ -3719,7 +3719,7 @@ export function environmentRuntimeService( */ async resolveCapabilities( input: EnvironmentDriverLeaseInput, - ): Promise { + ): Promise { const driver = getDriver(getLeaseDriverKey(input.lease, input.environment)); if (!driver) return null; return await driver.resolveCapabilities(input);