## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environments are configured through a JSON-schema-driven form (`JsonSchemaForm`), and each sandbox provider (here, Daytona) supplies a manifest describing its fields > - In the Daytona environment config form, the "memory" field rendered as a free-text input; on save the value round-tripped to `0`, producing a server-side "number must be greater than zero" error even when the user typed a valid number like `2` > - Rather than patch the free-text input, memory is now a true dropdown of the sandbox sizes Daytona actually supports, so an invalid value can no longer be typed or coerced > - The dropdown leads with a blank "None" row that is selected by default (meaning "not configured — use Daytona's defaults"); `0` is never offered because it is not a valid configuration > - The benefit is that operators pick a valid memory size from a constrained list, it saves correctly as an integer, and leaving it unset cleanly omits the field instead of submitting `0` ## Linked Issues or Issue Description No public GitHub issue exists; describing the bug inline per the bug report template. ### What happened? In the Daytona environment configuration form, the memory field is a free-text input labelled "gigabytes of RAM". Entering a value such as `2` and saving coerced the value to `0`, and the server rejected the save with "the number must be greater than zero". There was no way to enter a valid memory size through the form. ### Expected behavior Selecting a valid memory size (e.g. `2`) keeps that value and saves cleanly as an integer. Leaving memory unset is valid and submits no value (Daytona defaults apply). `0` is never selectable. ### Steps to reproduce 1. Open the environment configuration form for a Daytona sandbox provider. 2. In the memory field, type `2`. 3. Click Save. 4. Observe the value becomes `0` and the form errors with "the number must be greater than zero". ## What Changed - Daytona manifest: `memory` is now an `enum` of the supported sandbox sizes `[1, 2, 4, 8]` (GiB). It stays optional, so "not configured" remains valid. `0` is not in the list. - `JsonSchemaForm` `EnumField`: optional enums now render a leading blank **None** row that is selected by default when no value is set, letting the user express "not configured" and clear a previous selection (Radix `Select` forbids an empty-string item value, so the unset state maps to a sentinel that translates back to `undefined`). - `JsonSchemaForm` `EnumField`: when every enum option is numeric, the selected value is coerced back to a number on change so the payload keeps the schema's integer type (a stringified `"2"` would otherwise fail server-side integer validation — this is what fixes the original bug for the dropdown path). - Tests: added `EnumField` coverage in `JsonSchemaForm.test.tsx` (blank row present, no `0`, blank selected by default, numeric coercion, blank → unset) and Daytona manifest coverage in the plugin test (memory enum is `[1,2,4,8]`, excludes `0`, stays optional). ## Verification - `cd ui && npx vitest run src/components/JsonSchemaForm.test.tsx src/pages/CompanyEnvironments.test.tsx` → 16/16 passing (14 + 2). - `vitest run` in `packages/plugins/sandbox-providers/daytona` → 19/19 passing (incl. 3 new manifest tests). - `cd ui && npx tsc --noEmit` → clean. - Behavior: the memory field now renders as a dropdown showing **None / 1 / 2 / 4 / 8**, with **None** selected by default. Picking `2` stores the integer `2`; picking **None** clears the field so it is omitted from the payload (no `0`, no "must be greater than zero" error). ## Risks - Low risk. The blank-row + numeric-coercion changes live in `EnumField`. The blank row is only added for **optional** enums (required enums are unaffected); numeric coercion only triggers when every option is numeric, so existing string enums (`egressMode`, `backend`, `sessionStrategy`) are unchanged. The manifest change is scoped to the Daytona provider. ## Model Used Claude (Anthropic), Opus-class model, used via the Paperclip agent workflow (author + reviewer agents) with tool use and code execution. ## 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 (none found) - [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 - [x] My branch name describes the change and contains no internal Paperclip ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge
@paperclipai/plugin-daytona
Published Daytona sandbox provider plugin for Paperclip.
This package lives in the Paperclip monorepo, but it is intentionally excluded from the root pnpm workspace and shaped to publish and install like a standalone npm package. That lets operators install it from the Plugins page by package name without introducing root lockfile churn for Daytona's SDK dependencies.
Install
From a Paperclip instance, install:
@paperclipai/plugin-daytona
The host plugin installer runs npm install into the managed plugin directory, so transitive dependencies such as @daytonaio/sdk are pulled in during installation.
Configuration
Configure Daytona from Instance Settings -> Environments, not from the plugin's plugin page.
- Put the Daytona API key on the sandbox environment itself.
- When you save an environment, Paperclip stores pasted API keys as company secrets.
DAYTONA_API_KEYremains an optional host-level fallback when an environment omits the key.- Optional
apiUrlandtargetsettings map directly to the Daytona SDK/client configuration. IfapiUrlis omitted, the Daytona SDK uses its default endpoint.
Notes:
- The current published Daytona SDK package is
@daytonaio/sdk. - The driver supports both
snapshot-based andimage-based sandbox creation. If both are set, validation rejects the config as ambiguous. - Reusable leases map to Daytona stop/start semantics. Non-reusable leases are deleted on release.
Local development
cd packages/plugins/sandbox-providers/daytona
pnpm install --ignore-workspace --no-lockfile
pnpm build
pnpm test
pnpm typecheck
These commands assume the repo root has already been installed once so the local @paperclipai/plugin-sdk workspace package is available to the compiler during development.
Package layout
src/manifest.tsdeclares the sandbox-provider driver metadatasrc/plugin.tsimplements the environment lifecycle hookspaperclipPlugin.manifestandpaperclipPlugin.workerpoint the host at the built plugin entrypoints indist/