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:
parent
8d714c2d84
commit
b6854e61c7
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue