refactor(adapter-utils): rename EffectiveSandboxCapabilities to EffectiveExecutionCapabilities (#12119)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The adapter utilities package defines shared types for agent
execution targets
> - The type name EffectiveSandboxCapabilities describes only one
transport
> - All execution target drivers return the same resolved capability
snapshot
> - This pull request gives the snapshot a general name and keeps the
old type as a deprecated alias
> - The benefit is clearer public vocabulary with source compatibility
for current consumers

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The exported capability snapshot type uses the name
`EffectiveSandboxCapabilities`, although local, SSH, sandbox, and plugin
drivers return it.

**Subsystem affected**

The change affects `packages/adapter-utils` and its server consumers.

**Current behavior**

The public type name points to the sandbox transport. The private parser
also uses the sandbox-only name.

**Proposed behavior**

Use `EffectiveExecutionCapabilities` for the public type and
`parseEffectiveExecutionCapabilities` for the private parser. Keep a
deprecated alias for the old public type.

**Reason and benefit**

The new name matches the established execution-target vocabulary. The
alias keeps existing type imports working during the migration.

**Breaking changes**

None. The runtime field, capability flags, parsed shape, and package
versions do not change.

**Additional context**

GitHub search found no duplicate or related open issue or pull request.

## What Changed

- Rename the exported interface to `EffectiveExecutionCapabilities`.
- Keep `EffectiveSandboxCapabilities` as a deprecated type alias.
- Rename the private parser and update its call site and references.
- Add a type-level test for the deprecated alias.

## Verification

- `npx tsc --noEmit -p packages/adapter-utils`
- `npx vitest run
packages/adapter-utils/src/execution-target-sandbox.test.ts`
- `npx vitest run
server/src/__tests__/environment-execution-target-capabilities.test.ts
server/src/__tests__/environment-execution-target-duplex.test.ts`
- The local checks passed with 133 adapter-utils tests and 31 server
tests.
- Reviewers can confirm that the runtime field and capability flags stay
unchanged.

## Risks

Low risk. The alias protects existing type imports. The change does not
alter runtime behavior or serialized data.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The runtime does not
expose the context window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Nicky Leach 2026-08-25 07:13:55 -07:00 committed by GitHub
parent 8d714c2d84
commit b6854e61c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 24 deletions

View File

@ -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);
});
});

View File

@ -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",

View File

@ -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<string, unknown>;
// 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 {

View File

@ -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",

View File

@ -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<SandboxProviderCapabilities> | null;
narrowing?: Partial<Record<SandboxCapabilityKey, boolean>> | null;
supportedCapabilities?: ReadonlySet<SandboxCapabilityKey> | 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<EffectiveSandboxCapabilities>;
resolveCapabilities(input: EnvironmentDriverLeaseInput): Promise<EffectiveExecutionCapabilities>;
/**
* 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<EffectiveSandboxCapabilities> {
): Promise<EffectiveExecutionCapabilities> {
const metadata = input.lease.metadata ?? {};
const providerKey =
readString(metadata.provider) ??
@ -3719,7 +3719,7 @@ export function environmentRuntimeService(
*/
async resolveCapabilities(
input: EnvironmentDriverLeaseInput,
): Promise<EffectiveSandboxCapabilities | null> {
): Promise<EffectiveExecutionCapabilities | null> {
const driver = getDriver(getLeaseDriverKey(input.lease, input.environment));
if (!driver) return null;
return await driver.resolveCapabilities(input);