2026-03-10 20:58:18 -05:00
import { useState , useEffect , useRef , useMemo , useCallback } from "react" ;
2026-02-19 15:44:05 -06:00
import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
2026-02-20 12:28:42 -06:00
import type {
Agent ,
AdapterEnvironmentTestResult ,
CompanySecret ,
EnvBinding ,
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
Environment ,
2026-03-03 08:45:26 -06:00
} from "@paperclipai/shared" ;
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
import { AGENT_DEFAULT_MAX_CONCURRENT_RUNS , supportedEnvironmentDriversForAdapter } from "@paperclipai/shared" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
import type { AdapterModel } from "../api/agents" ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
import { agentsApi } from "../api/agents" ;
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
import { environmentsApi } from "../api/environments" ;
import { instanceSettingsApi } from "../api/instanceSettings" ;
2026-02-19 15:44:05 -06:00
import { secretsApi } from "../api/secrets" ;
2026-02-20 12:28:42 -06:00
import { assetsApi } from "../api/assets" ;
2026-03-03 12:41:50 -06:00
import {
DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX ,
DEFAULT_CODEX_LOCAL_MODEL ,
} from "@paperclipai/adapter-codex-local" ;
2026-03-05 06:31:22 -06:00
import { DEFAULT_CURSOR_LOCAL_MODEL } from "@paperclipai/adapter-cursor-local" ;
2026-03-08 16:43:34 +05:30
import { DEFAULT_GEMINI_LOCAL_MODEL } from "@paperclipai/adapter-gemini-local" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
import {
Popover ,
PopoverContent ,
PopoverTrigger ,
} from "@/components/ui/popover" ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
import { Button } from "@/components/ui/button" ;
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
import { FolderOpen , Heart , ChevronDown , X } from "lucide-react" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
import { cn } from "../lib/utils" ;
2026-03-05 15:52:59 +01:00
import { extractModelName , extractProviderId } from "../lib/model-utils" ;
2026-02-19 15:44:05 -06:00
import { queryKeys } from "../lib/queryKeys" ;
import { useCompany } from "../context/CompanyContext" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
import {
Field ,
ToggleField ,
ToggleWithNumber ,
CollapsibleSection ,
DraftInput ,
DraftNumberInput ,
help ,
adapterLabels ,
} from "./agent-config-primitives" ;
2026-02-26 16:33:48 -06:00
import { defaultCreateValues } from "./agent-config-defaults" ;
2026-02-18 13:53:03 -06:00
import { getUIAdapter } from "../adapters" ;
import { ClaudeLocalAdvancedFields } from "../adapters/claude-local/config-fields" ;
2026-02-20 12:28:42 -06:00
import { MarkdownEditor } from "./MarkdownEditor" ;
2026-03-02 16:08:59 -06:00
import { ChoosePathButton } from "./PathInstructionsModal" ;
2026-03-05 15:24:20 +01:00
import { OpenCodeLogoIcon } from "./OpenCodeLogoIcon" ;
2026-03-20 20:06:19 +00:00
import { ReportsToPicker } from "./ReportsToPicker" ;
2026-04-06 09:34:15 -05:00
import { EnvVarEditor } from "./EnvVarEditor" ;
2026-03-20 07:04:41 -05:00
import { shouldShowLegacyWorkingDirectoryField } from "../lib/legacy-agent-config" ;
2026-03-31 20:21:13 +01:00
import { listAdapterOptions , listVisibleAdapterTypes } from "../adapters/metadata" ;
import { getAdapterLabel } from "../adapters/adapter-display-registry" ;
import { useDisabledAdaptersSync } from "../adapters/use-disabled-adapters" ;
2026-04-09 17:50:14 +00:00
import { buildAgentUpdatePatch , type AgentConfigOverlay } from "../lib/agent-config-patch" ;
feat(adapters): add capability flags to ServerAdapterModule (#3540)
## Thinking Path
> - Paperclip orchestrates AI agents via adapters (`claude_local`,
`codex_local`, etc.)
> - Each adapter type has different capabilities — instructions bundles,
skill materialization, local JWT — but these were gated by 5 hardcoded
type lists scattered across server routes and UI components
> - External adapter plugins (e.g. a future `opencode_k8s`) cannot add
themselves to those hardcoded lists without patching Paperclip source
> - The existing `supportsLocalAgentJwt` field on `ServerAdapterModule`
proves the right pattern already exists; it just wasn't applied to the
other capability gates
> - This pull request replaces the 4 remaining hardcoded lists with
declarative capability flags on `ServerAdapterModule`, exposed through
the adapter listing API
> - The benefit is that external adapter plugins can now declare their
own capabilities without any changes to Paperclip source code
## What Changed
- **`packages/adapter-utils/src/types.ts`** — added optional capability
fields to `ServerAdapterModule`: `supportsInstructionsBundle`,
`instructionsPathKey`, `requiresMaterializedRuntimeSkills`
- **`server/src/routes/agents.ts`** — replaced
`DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES` and
`ADAPTERS_REQUIRING_MATERIALIZED_RUNTIME_SKILLS` hardcoded sets with
capability-aware helper functions that fall back to the legacy sets for
adapters that don't set flags
- **`server/src/routes/adapters.ts`** — `GET /api/adapters` now includes
a `capabilities` object per adapter (all four flags + derived
`supportsSkills`)
- **`server/src/adapters/registry.ts`** — all built-in adapters
(`claude_local`, `codex_local`, `process`, `cursor`) now declare flags
explicitly
- **`ui/src/adapters/use-adapter-capabilities.ts`** — new hook that
fetches adapter capabilities from the API
- **`ui/src/pages/AgentDetail.tsx`** — replaced hardcoded `isLocal`
allowlist with `capabilities.supportsInstructionsBundle` from the API
- **`ui/src/components/AgentConfigForm.tsx`** /
**`OnboardingWizard.tsx`** — replaced `NONLOCAL_TYPES` denylist with
capability-based checks
- **`server/src/__tests__/adapter-registry.test.ts`** /
**`adapter-routes.test.ts`** — tests covering flag exposure,
undefined-when-unset, and per-adapter values
- **`docs/adapters/creating-an-adapter.md`** — new "Capability Flags"
section documenting all flags and an example for external plugin authors
## Verification
- Run `pnpm test --filter=@paperclip/server -- adapter-registry
adapter-routes` — all new tests pass
- Run `pnpm test --filter=@paperclip/adapter-utils` — existing tests
still pass
- Spin up dev server, open an agent with `claude_local` type —
instructions bundle tab still visible
- Create/open an agent with a non-local type — instructions bundle tab
still hidden
- Call `GET /api/adapters` and verify each adapter includes a
`capabilities` object with the correct flags
## Risks
- **Low risk overall** — all new flags are optional with
backwards-compatible fallbacks to the existing hardcoded sets; no
adapter behaviour changes unless a flag is explicitly set
- Adapters that do not declare flags continue to use the legacy lists,
so there is no regression risk for built-in adapters
- The UI capability hook adds one API call to AgentDetail mount; this is
a pre-existing endpoint, so no new latency path is introduced
## Model Used
- Provider: Anthropic
- Model: Claude Sonnet 4.6 (`claude-sonnet-4-6`)
- Context: 200k token context window
- Mode: Agentic tool use (code editing, bash, grep, file reads)
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Pawla Abdul (Bot) <pawla@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 08:10:52 -04:00
import { useAdapterCapabilities } from "../adapters/use-adapter-capabilities" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Create mode values ---- */
2026-03-03 08:45:26 -06:00
// Canonical type lives in @paperclipai/adapter-utils; re-exported here
2026-02-18 14:23:16 -06:00
// so existing imports from this file keep working.
2026-03-03 08:45:26 -06:00
export type { CreateConfigValues } from "@paperclipai/adapter-utils" ;
import type { CreateConfigValues } from "@paperclipai/adapter-utils" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Props ---- */
type AgentConfigFormProps = {
adapterModels? : AdapterModel [ ] ;
2026-02-18 13:02:23 -06:00
onDirtyChange ? : ( dirty : boolean ) = > void ;
onSaveActionChange ? : ( save : ( ( ) = > void ) | null ) = > void ;
onCancelActionChange ? : ( cancel : ( ( ) = > void ) | null ) = > void ;
hideInlineSave? : boolean ;
2026-03-16 15:41:06 -05:00
showAdapterTypeField? : boolean ;
showAdapterTestEnvironmentButton? : boolean ;
showCreateRunPolicySection? : boolean ;
hideInstructionsFile? : boolean ;
2026-03-17 10:32:50 -05:00
/** Hide the prompt template field from the Identity section (used when it's shown in a separate Prompts tab). */
hidePromptTemplate? : boolean ;
2026-02-23 14:41:21 -06:00
/** "cards" renders each section as heading + bordered card (for settings pages). Default: "inline" (border-b dividers). */
sectionLayout ? : "inline" | "cards" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} & (
| {
mode : "create" ;
values : CreateConfigValues ;
onChange : ( patch : Partial < CreateConfigValues > ) = > void ;
}
| {
mode : "edit" ;
agent : Agent ;
onSave : ( patch : Record < string , unknown > ) = > void ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
isSaving? : boolean ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
}
) ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
/* ---- Edit mode overlay (dirty tracking) ---- */
2026-04-09 17:50:14 +00:00
const emptyOverlay : AgentConfigOverlay = {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
identity : { } ,
adapterConfig : { } ,
heartbeat : { } ,
runtime : { } ,
} ;
2026-03-03 09:36:49 -06:00
/** Stable empty object used as fallback for missing env config to avoid new-object-per-render. */
const EMPTY_ENV : Record < string , EnvBinding > = { } ;
2026-04-09 17:50:14 +00:00
function isOverlayDirty ( o : AgentConfigOverlay ) : boolean {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
return (
Object . keys ( o . identity ) . length > 0 ||
o . adapterType !== undefined ||
Object . keys ( o . adapterConfig ) . length > 0 ||
Object . keys ( o . heartbeat ) . length > 0 ||
Object . keys ( o . runtime ) . length > 0
) ;
}
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Shared input class ---- */
const inputClass =
"w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40" ;
2026-02-18 13:02:23 -06:00
function parseCommaArgs ( value : string ) : string [ ] {
return value
. split ( "," )
. map ( ( item ) = > item . trim ( ) )
. filter ( Boolean ) ;
}
function formatArgList ( value : unknown ) : string {
if ( Array . isArray ( value ) ) {
return value
. filter ( ( item ) : item is string = > typeof item === "string" )
. join ( ", " ) ;
}
return typeof value === "string" ? value : "" ;
}
2026-02-20 10:32:32 -06:00
const codexThinkingEffortOptions = [
{ id : "" , label : "Auto" } ,
{ id : "minimal" , label : "Minimal" } ,
{ id : "low" , label : "Low" } ,
{ id : "medium" , label : "Medium" } ,
{ id : "high" , label : "High" } ,
2026-03-05 18:59:42 -06:00
{ id : "xhigh" , label : "X-High" } ,
2026-02-20 10:32:32 -06:00
] as const ;
2026-03-05 15:24:20 +01:00
const openCodeThinkingEffortOptions = [
2026-03-04 16:48:54 -06:00
{ id : "" , label : "Auto" } ,
{ id : "minimal" , label : "Minimal" } ,
{ id : "low" , label : "Low" } ,
{ id : "medium" , label : "Medium" } ,
{ id : "high" , label : "High" } ,
2026-03-05 18:59:42 -06:00
{ id : "xhigh" , label : "X-High" } ,
2026-03-04 16:48:54 -06:00
{ id : "max" , label : "Max" } ,
] as const ;
2026-03-05 06:31:22 -06:00
const cursorModeOptions = [
{ id : "" , label : "Auto" } ,
{ id : "plan" , label : "Plan" } ,
{ id : "ask" , label : "Ask" } ,
] as const ;
2026-02-20 10:32:32 -06:00
const claudeThinkingEffortOptions = [
{ id : "" , label : "Auto" } ,
{ id : "low" , label : "Low" } ,
{ id : "medium" , label : "Medium" } ,
{ id : "high" , label : "High" } ,
] as const ;
2026-02-18 13:02:23 -06:00
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Form ---- */
export function AgentConfigForm ( props : AgentConfigFormProps ) {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
const { mode , adapterModels : externalModels } = props ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
const isCreate = mode === "create" ;
2026-02-23 14:41:21 -06:00
const cards = props . sectionLayout === "cards" ;
2026-03-16 15:41:06 -05:00
const showAdapterTypeField = props . showAdapterTypeField ? ? true ;
const showAdapterTestEnvironmentButton = props . showAdapterTestEnvironmentButton ? ? true ;
const showCreateRunPolicySection = props . showCreateRunPolicySection ? ? true ;
const hideInstructionsFile = props . hideInstructionsFile ? ? false ;
2026-02-19 15:44:05 -06:00
const { selectedCompanyId } = useCompany ( ) ;
const queryClient = useQueryClient ( ) ;
2026-03-31 20:21:13 +01:00
// Sync disabled adapter types from server so dropdown filters them out
const disabledTypes = useDisabledAdaptersSync ( ) ;
2026-02-19 15:44:05 -06:00
const { data : availableSecrets = [ ] } = useQuery ( {
queryKey : selectedCompanyId ? queryKeys . secrets . list ( selectedCompanyId ) : [ "secrets" , "none" ] ,
queryFn : ( ) = > secretsApi . list ( selectedCompanyId ! ) ,
enabled : Boolean ( selectedCompanyId ) ,
} ) ;
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
const { data : experimentalSettings } = useQuery ( {
queryKey : queryKeys.instance.experimentalSettings ,
queryFn : ( ) = > instanceSettingsApi . getExperimental ( ) ,
retry : false ,
} ) ;
const environmentsEnabled = experimentalSettings ? . enableEnvironments === true ;
2026-02-19 15:44:05 -06:00
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
const { data : environments = [ ] } = useQuery < Environment [ ] > ( {
queryKey : selectedCompanyId ? queryKeys . environments . list ( selectedCompanyId ) : [ "environments" , "none" ] ,
queryFn : ( ) = > environmentsApi . list ( selectedCompanyId ! ) ,
enabled : Boolean ( selectedCompanyId ) && environmentsEnabled ,
} ) ;
2026-02-19 15:44:05 -06:00
const createSecret = useMutation ( {
mutationFn : ( input : { name : string ; value : string } ) = > {
if ( ! selectedCompanyId ) throw new Error ( "Select a company to create secrets" ) ;
return secretsApi . create ( selectedCompanyId , input ) ;
} ,
onSuccess : ( ) = > {
if ( ! selectedCompanyId ) return ;
queryClient . invalidateQueries ( { queryKey : queryKeys.secrets.list ( selectedCompanyId ) } ) ;
} ,
} ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
2026-02-20 12:28:42 -06:00
const uploadMarkdownImage = useMutation ( {
mutationFn : async ( { file , namespace } : { file : File ; namespace : string } ) = > {
if ( ! selectedCompanyId ) throw new Error ( "Select a company to upload images" ) ;
return assetsApi . uploadImage ( selectedCompanyId , file , namespace ) ;
} ,
} ) ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
// ---- Edit mode: overlay for dirty tracking ----
2026-04-09 17:50:14 +00:00
const [ overlay , setOverlay ] = useState < AgentConfigOverlay > ( emptyOverlay ) ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
const agentRef = useRef < Agent | null > ( null ) ;
// Clear overlay when agent data refreshes (after save)
useEffect ( ( ) = > {
if ( ! isCreate ) {
if ( agentRef . current !== null && props . agent !== agentRef . current ) {
setOverlay ( { . . . emptyOverlay } ) ;
}
agentRef . current = props . agent ;
}
} , [ isCreate , ! isCreate ? props.agent : undefined ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
const isDirty = ! isCreate && isOverlayDirty ( overlay ) ;
/** Read effective value: overlay if dirty, else original */
2026-04-09 17:50:14 +00:00
function eff < T > ( group : keyof Omit < AgentConfigOverlay , "adapterType" > , field : string , original : T ) : T {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
const o = overlay [ group ] ;
if ( field in o ) return o [ field ] as T ;
return original ;
}
/** Mark field dirty in overlay */
2026-04-09 17:50:14 +00:00
function mark ( group : keyof Omit < AgentConfigOverlay , "adapterType" > , field : string , value : unknown ) {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
setOverlay ( ( prev ) = > ( {
. . . prev ,
[ group ] : { . . . prev [ group ] , [ field ] : value } ,
} ) ) ;
}
/** Build accumulated patch and send to parent */
2026-03-10 20:58:18 -05:00
const handleCancel = useCallback ( ( ) = > {
setOverlay ( { . . . emptyOverlay } ) ;
} , [ ] ) ;
const handleSave = useCallback ( ( ) = > {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
if ( isCreate || ! isDirty ) return ;
2026-04-09 17:50:14 +00:00
props . onSave ( buildAgentUpdatePatch ( props . agent , overlay ) ) ;
2026-03-10 20:58:18 -05:00
} , [ isCreate , isDirty , overlay , props ] ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
2026-02-18 13:02:23 -06:00
useEffect ( ( ) = > {
if ( ! isCreate ) {
props . onDirtyChange ? . ( isDirty ) ;
2026-03-10 20:58:18 -05:00
props . onSaveActionChange ? . ( handleSave ) ;
props . onCancelActionChange ? . ( handleCancel ) ;
2026-02-18 13:02:23 -06:00
}
2026-03-10 20:58:18 -05:00
} , [ isCreate , isDirty , props . onDirtyChange , props . onSaveActionChange , props . onCancelActionChange , handleSave , handleCancel ] ) ;
useEffect ( ( ) = > {
if ( isCreate ) return ;
return ( ) = > {
props . onSaveActionChange ? . ( null ) ;
props . onCancelActionChange ? . ( null ) ;
props . onDirtyChange ? . ( false ) ;
} ;
} , [ isCreate , props . onDirtyChange , props . onSaveActionChange , props . onCancelActionChange ] ) ;
2026-02-18 13:02:23 -06:00
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
// ---- Resolve values ----
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
const config = ! isCreate ? ( ( props . agent . adapterConfig ? ? { } ) as Record < string , unknown > ) : { } ;
const runtimeConfig = ! isCreate ? ( ( props . agent . runtimeConfig ? ? { } ) as Record < string , unknown > ) : { } ;
const heartbeat = ! isCreate ? ( ( runtimeConfig . heartbeat ? ? { } ) as Record < string , unknown > ) : { } ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
const adapterType = isCreate
? props . values . adapterType
: overlay . adapterType ? ? props . agent . adapterType ;
feat(adapters): add capability flags to ServerAdapterModule (#3540)
## Thinking Path
> - Paperclip orchestrates AI agents via adapters (`claude_local`,
`codex_local`, etc.)
> - Each adapter type has different capabilities — instructions bundles,
skill materialization, local JWT — but these were gated by 5 hardcoded
type lists scattered across server routes and UI components
> - External adapter plugins (e.g. a future `opencode_k8s`) cannot add
themselves to those hardcoded lists without patching Paperclip source
> - The existing `supportsLocalAgentJwt` field on `ServerAdapterModule`
proves the right pattern already exists; it just wasn't applied to the
other capability gates
> - This pull request replaces the 4 remaining hardcoded lists with
declarative capability flags on `ServerAdapterModule`, exposed through
the adapter listing API
> - The benefit is that external adapter plugins can now declare their
own capabilities without any changes to Paperclip source code
## What Changed
- **`packages/adapter-utils/src/types.ts`** — added optional capability
fields to `ServerAdapterModule`: `supportsInstructionsBundle`,
`instructionsPathKey`, `requiresMaterializedRuntimeSkills`
- **`server/src/routes/agents.ts`** — replaced
`DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES` and
`ADAPTERS_REQUIRING_MATERIALIZED_RUNTIME_SKILLS` hardcoded sets with
capability-aware helper functions that fall back to the legacy sets for
adapters that don't set flags
- **`server/src/routes/adapters.ts`** — `GET /api/adapters` now includes
a `capabilities` object per adapter (all four flags + derived
`supportsSkills`)
- **`server/src/adapters/registry.ts`** — all built-in adapters
(`claude_local`, `codex_local`, `process`, `cursor`) now declare flags
explicitly
- **`ui/src/adapters/use-adapter-capabilities.ts`** — new hook that
fetches adapter capabilities from the API
- **`ui/src/pages/AgentDetail.tsx`** — replaced hardcoded `isLocal`
allowlist with `capabilities.supportsInstructionsBundle` from the API
- **`ui/src/components/AgentConfigForm.tsx`** /
**`OnboardingWizard.tsx`** — replaced `NONLOCAL_TYPES` denylist with
capability-based checks
- **`server/src/__tests__/adapter-registry.test.ts`** /
**`adapter-routes.test.ts`** — tests covering flag exposure,
undefined-when-unset, and per-adapter values
- **`docs/adapters/creating-an-adapter.md`** — new "Capability Flags"
section documenting all flags and an example for external plugin authors
## Verification
- Run `pnpm test --filter=@paperclip/server -- adapter-registry
adapter-routes` — all new tests pass
- Run `pnpm test --filter=@paperclip/adapter-utils` — existing tests
still pass
- Spin up dev server, open an agent with `claude_local` type —
instructions bundle tab still visible
- Create/open an agent with a non-local type — instructions bundle tab
still hidden
- Call `GET /api/adapters` and verify each adapter includes a
`capabilities` object with the correct flags
## Risks
- **Low risk overall** — all new flags are optional with
backwards-compatible fallbacks to the existing hardcoded sets; no
adapter behaviour changes unless a flag is explicitly set
- Adapters that do not declare flags continue to use the legacy lists,
so there is no regression risk for built-in adapters
- The UI capability hook adds one API call to AgentDetail mount; this is
a pre-existing endpoint, so no new latency path is introduced
## Model Used
- Provider: Anthropic
- Model: Claude Sonnet 4.6 (`claude-sonnet-4-6`)
- Context: 200k token context window
- Mode: Agentic tool use (code editing, bash, grep, file reads)
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Pawla Abdul (Bot) <pawla@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 08:10:52 -04:00
const getCapabilities = useAdapterCapabilities ( ) ;
const adapterCaps = getCapabilities ( adapterType ) ;
const isLocal = adapterCaps . supportsInstructionsBundle || adapterCaps . supportsSkills || adapterCaps . supportsLocalAgentJwt ;
2026-03-31 20:21:13 +01:00
2026-03-20 07:04:41 -05:00
const showLegacyWorkingDirectoryField =
isLocal && shouldShowLegacyWorkingDirectoryField ( { isCreate , adapterConfig : config } ) ;
2026-02-18 13:53:03 -06:00
const uiAdapter = useMemo ( ( ) = > getUIAdapter ( adapterType ) , [ adapterType ] ) ;
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
const supportedEnvironmentDrivers = useMemo (
( ) = > new Set ( supportedEnvironmentDriversForAdapter ( adapterType ) ) ,
[ adapterType ] ,
) ;
const runnableEnvironments = useMemo (
( ) = > environments . filter ( ( environment ) = > supportedEnvironmentDrivers . has ( environment . driver ) ) ,
[ environments , supportedEnvironmentDrivers ] ,
) ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
// Fetch adapter models for the effective adapter type
2026-03-05 15:24:20 +01:00
const {
data : fetchedModels ,
error : fetchedModelsError ,
} = useQuery ( {
queryKey : selectedCompanyId
? queryKeys . agents . adapterModels ( selectedCompanyId , adapterType )
: [ "agents" , "none" , "adapter-models" , adapterType ] ,
queryFn : ( ) = > agentsApi . adapterModels ( selectedCompanyId ! , adapterType ) ,
enabled : Boolean ( selectedCompanyId ) ,
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
} ) ;
const models = fetchedModels ? ? externalModels ? ? [ ] ;
2026-04-20 17:55:08 -03:00
const adapterCommandField =
adapterType === "hermes_local" ? "hermesCommand" : "command" ;
2026-03-28 01:34:48 +01:00
const {
data : detectedModelData ,
refetch : refetchDetectedModel ,
} = useQuery ( {
queryKey : selectedCompanyId
? queryKeys . agents . detectModel ( selectedCompanyId , adapterType )
: [ "agents" , "none" , "detect-model" , adapterType ] ,
queryFn : ( ) = > {
if ( ! selectedCompanyId ) {
2026-03-31 20:21:13 +01:00
throw new Error ( "Select a company to detect the model" ) ;
2026-03-28 01:34:48 +01:00
}
return agentsApi . detectModel ( selectedCompanyId , adapterType ) ;
} ,
2026-03-31 20:21:13 +01:00
enabled : Boolean ( selectedCompanyId && isLocal ) ,
2026-03-28 01:34:48 +01:00
} ) ;
const detectedModel = detectedModelData ? . model ? ? null ;
2026-03-31 20:21:13 +01:00
const detectedModelCandidates = detectedModelData ? . candidates ? ? [ ] ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
2026-03-20 20:06:19 +00:00
const { data : companyAgents = [ ] } = useQuery ( {
queryKey : selectedCompanyId ? queryKeys . agents . list ( selectedCompanyId ) : [ "agents" , "none" , "list" ] ,
queryFn : ( ) = > agentsApi . list ( selectedCompanyId ! ) ,
enabled : Boolean ( ! isCreate && selectedCompanyId ) ,
} ) ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
2026-02-18 13:53:03 -06:00
/** Props passed to adapter-specific config field components */
const adapterFieldProps = {
mode ,
isCreate ,
adapterType ,
values : isCreate ? props.values : null ,
set : isCreate ? ( patch : Partial < CreateConfigValues > ) = > props . onChange ( patch ) : null ,
config ,
eff : eff as < T > ( group : "adapterConfig" , field : string , original : T ) = > T ,
mark : mark as ( group : "adapterConfig" , field : string , value : unknown ) = > void ,
models ,
2026-03-16 15:41:06 -05:00
hideInstructionsFile ,
2026-02-18 13:53:03 -06:00
} ;
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
// Section toggle state — advanced always starts collapsed
2026-02-20 14:11:30 -06:00
const [ runPolicyAdvancedOpen , setRunPolicyAdvancedOpen ] = useState ( false ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
// Popover states
const [ modelOpen , setModelOpen ] = useState ( false ) ;
2026-02-20 10:32:32 -06:00
const [ thinkingEffortOpen , setThinkingEffortOpen ] = useState ( false ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
// Create mode helpers
const val = isCreate ? props.values : null ;
const set = isCreate
? ( patch : Partial < CreateConfigValues > ) = > props . onChange ( patch )
: null ;
2026-02-20 12:28:42 -06:00
function buildAdapterConfigForTest ( ) : Record < string , unknown > {
if ( isCreate ) {
return uiAdapter . buildAdapterConfig ( val ! ) ;
}
const base = config as Record < string , unknown > ;
2026-04-20 17:55:08 -03:00
const next = { . . . base , . . . overlay . adapterConfig } ;
if ( adapterType === "hermes_local" ) {
const hermesCommand =
typeof next . hermesCommand === "string" && next . hermesCommand . length > 0
? next . hermesCommand
: typeof next . command === "string" && next . command . length > 0
? next . command
: undefined ;
if ( hermesCommand ) {
next . hermesCommand = hermesCommand ;
}
}
return next ;
2026-02-20 12:28:42 -06:00
}
const testEnvironment = useMutation ( {
mutationFn : async ( ) = > {
if ( ! selectedCompanyId ) {
throw new Error ( "Select a company to test adapter environment" ) ;
}
return agentsApi . testEnvironment ( selectedCompanyId , adapterType , {
adapterConfig : buildAdapterConfigForTest ( ) ,
} ) ;
} ,
} ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
// Current model for display
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
const currentModelId = isCreate
? val ! . model
: eff ( "adapterConfig" , "model" , String ( config . model ? ? "" ) ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
2026-03-04 16:48:54 -06:00
const thinkingEffortKey =
adapterType === "codex_local"
? "modelReasoningEffort"
2026-03-05 06:31:22 -06:00
: adapterType === "cursor"
? "mode"
2026-03-06 15:23:55 +00:00
: adapterType === "opencode_local"
? "variant"
: "effort" ;
2026-02-20 10:32:32 -06:00
const thinkingEffortOptions =
2026-03-04 16:48:54 -06:00
adapterType === "codex_local"
? codexThinkingEffortOptions
2026-03-05 06:31:22 -06:00
: adapterType === "cursor"
? cursorModeOptions
2026-03-06 15:23:55 +00:00
: adapterType === "opencode_local"
? openCodeThinkingEffortOptions
: claudeThinkingEffortOptions ;
2026-02-20 10:32:32 -06:00
const currentThinkingEffort = isCreate
? val ! . thinkingEffort
: adapterType === "codex_local"
? eff (
"adapterConfig" ,
"modelReasoningEffort" ,
String ( config . modelReasoningEffort ? ? config . reasoningEffort ? ? "" ) ,
)
2026-03-05 06:31:22 -06:00
: adapterType === "cursor"
? eff ( "adapterConfig" , "mode" , String ( config . mode ? ? "" ) )
2026-03-08 16:43:34 +05:30
: adapterType === "opencode_local"
? eff ( "adapterConfig" , "variant" , String ( config . variant ? ? "" ) )
2026-02-20 10:32:32 -06:00
: eff ( "adapterConfig" , "effort" , String ( config . effort ? ? "" ) ) ;
2026-03-08 16:43:34 +05:30
const showThinkingEffort = adapterType !== "gemini_local" ;
2026-02-20 10:32:32 -06:00
const codexSearchEnabled = adapterType === "codex_local"
? ( isCreate ? Boolean ( val ! . search ) : eff ( "adapterConfig" , "search" , Boolean ( config . search ) ) )
: false ;
2026-03-16 19:35:11 -05:00
const effectiveRuntimeConfig = useMemo ( ( ) = > {
if ( isCreate ) {
return {
heartbeat : {
enabled : val ! . heartbeatEnabled ,
intervalSec : val ! . intervalSec ,
} ,
} ;
}
const mergedHeartbeat = {
. . . ( runtimeConfig . heartbeat && typeof runtimeConfig . heartbeat === "object"
? runtimeConfig . heartbeat as Record < string , unknown >
: { } ) ,
. . . overlay . heartbeat ,
} ;
return {
. . . runtimeConfig ,
heartbeat : mergedHeartbeat ,
} ;
} , [ isCreate , overlay . heartbeat , runtimeConfig , val ] ) ;
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
const currentDefaultEnvironmentId = isCreate
? val ! . defaultEnvironmentId ? ? ""
: eff ( "identity" , "defaultEnvironmentId" , props . agent . defaultEnvironmentId ? ? "" ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
return (
2026-02-23 14:41:21 -06:00
< div className = { cn ( "relative" , cards && "space-y-6" ) } >
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
{ /* ---- Floating Save button (edit mode, when dirty) ---- */ }
2026-02-18 13:02:23 -06:00
{ isDirty && ! props . hideInlineSave && (
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
< div className = "sticky top-0 z-10 flex items-center justify-end px-4 py-2 bg-background/90 backdrop-blur-sm border-b border-primary/20" >
< div className = "flex items-center gap-3" >
< span className = "text-xs text-muted-foreground" > Unsaved changes < / span >
< Button
size = "sm"
onClick = { handleSave }
disabled = { ! isCreate && props . isSaving }
>
{ ! isCreate && props . isSaving ? "Saving..." : "Save" }
< / Button >
< / div >
< / div >
) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
{ /* ---- Identity (edit only) ---- */ }
{ ! isCreate && (
2026-02-23 14:41:21 -06:00
< div className = { cn ( ! cards && "border-b border-border" ) } >
{ cards
? < h3 className = "text-sm font-medium mb-3" > Identity < / h3 >
: < div className = "px-4 py-2 text-xs font-medium text-muted-foreground" > Identity < / div >
}
< div className = { cn ( cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3" ) } >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< Field label = "Name" hint = { help . name } >
< DraftInput
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
value = { eff ( "identity" , "name" , props . agent . name ) }
onCommit = { ( v ) = > mark ( "identity" , "name" , v ) }
2026-02-18 13:02:23 -06:00
immediate
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = { inputClass }
placeholder = "Agent name"
/ >
< / Field >
< Field label = "Title" hint = { help . title } >
< DraftInput
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
value = { eff ( "identity" , "title" , props . agent . title ? ? "" ) }
onCommit = { ( v ) = > mark ( "identity" , "title" , v || null ) }
2026-02-18 13:02:23 -06:00
immediate
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = { inputClass }
placeholder = "e.g. VP of Engineering"
/ >
< / Field >
2026-03-20 20:06:19 +00:00
< Field label = "Reports to" hint = { help . reportsTo } >
< ReportsToPicker
agents = { companyAgents }
value = { eff ( "identity" , "reportsTo" , props . agent . reportsTo ? ? null ) }
onChange = { ( id ) = > mark ( "identity" , "reportsTo" , id ) }
excludeAgentIds = { [ props . agent . id ] }
chooseLabel = "Choose manager…"
/ >
< / Field >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< Field label = "Capabilities" hint = { help . capabilities } >
2026-02-20 12:28:42 -06:00
< MarkdownEditor
2026-04-01 12:24:15 -05:00
value = { eff ( "identity" , "capabilities" , props . agent . capabilities ? ? "" ) ? ? "" }
2026-02-20 12:28:42 -06:00
onChange = { ( v ) = > mark ( "identity" , "capabilities" , v || null ) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
placeholder = "Describe what this agent can do..."
2026-02-20 12:50:36 -06:00
contentClassName = "min-h-[44px] text-sm font-mono"
2026-02-20 12:28:42 -06:00
imageUploadHandler = { async ( file ) = > {
const asset = await uploadMarkdownImage . mutateAsync ( {
file ,
namespace : ` agents/ ${ props . agent . id } /capabilities ` ,
} ) ;
return asset . contentPath ;
} }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/ >
< / Field >
2026-03-17 10:32:50 -05:00
{ isLocal && ! props . hidePromptTemplate && (
2026-03-13 08:49:11 -05:00
< >
< Field label = "Prompt Template" hint = { help . promptTemplate } >
< MarkdownEditor
value = { eff (
"adapterConfig" ,
"promptTemplate" ,
String ( config . promptTemplate ? ? "" ) ,
) }
onChange = { ( v ) = > mark ( "adapterConfig" , "promptTemplate" , v ? ? "" ) }
placeholder = "You are agent {{ agent.name }}. Your role is {{ agent.role }}..."
contentClassName = "min-h-[88px] text-sm font-mono"
imageUploadHandler = { async ( file ) = > {
const namespace = ` agents/ ${ props . agent . id } /prompt-template ` ;
const asset = await uploadMarkdownImage . mutateAsync ( { file , namespace } ) ;
return asset . contentPath ;
} }
/ >
< / Field >
< div className = "rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-100" >
Prompt template is replayed on every heartbeat . Keep it compact and dynamic to avoid recurring token cost and cache churn .
< / div >
< / >
2026-02-20 14:11:30 -06:00
) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / div >
< / div >
) }
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
{ /* ---- Execution ---- */ }
{ environmentsEnabled ? (
< div className = { cn ( ! cards && ( isCreate ? "border-t border-border" : "border-b border-border" ) ) } >
{ cards
? < h3 className = "text-sm font-medium mb-3" > Execution < / h3 >
: < div className = "px-4 py-2 text-xs font-medium text-muted-foreground" > Execution < / div >
}
< div className = { cn ( cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3" ) } >
< Field
label = "Default environment"
hint = "Agent-level default execution target. Project and issue settings can still override this."
>
< select
className = { inputClass }
value = { currentDefaultEnvironmentId }
onChange = { ( event ) = > {
const nextValue = event . target . value ;
if ( isCreate ) {
set ! ( { defaultEnvironmentId : nextValue } ) ;
return ;
}
mark ( "identity" , "defaultEnvironmentId" , nextValue || null ) ;
} }
>
< option value = "" > Company default ( Local ) < / option >
{ runnableEnvironments . map ( ( environment ) = > (
< option key = { environment . id } value = { environment . id } >
{ environment . name } · { environment . driver }
< / option >
) ) }
< / select >
< / Field >
< / div >
< / div >
) : null }
2026-02-20 14:11:30 -06:00
{ /* ---- Adapter ---- */ }
2026-02-23 14:41:21 -06:00
< div className = { cn ( ! cards && ( isCreate ? "border-t border-border" : "border-b border-border" ) ) } >
< div className = { cn ( cards ? "flex items-center justify-between mb-3" : "px-4 py-2 flex items-center justify-between gap-2" ) } >
{ cards
? < h3 className = "text-sm font-medium" > Adapter < / h3 >
: < span className = "text-xs font-medium text-muted-foreground" > Adapter < / span >
}
2026-03-16 15:41:06 -05:00
{ showAdapterTestEnvironmentButton && (
< Button
type = "button"
variant = "outline"
size = "sm"
className = "h-7 px-2.5 text-xs"
onClick = { ( ) = > testEnvironment . mutate ( ) }
disabled = { testEnvironment . isPending || ! selectedCompanyId }
>
{ testEnvironment . isPending ? "Testing..." : "Test environment" }
< / Button >
) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / div >
2026-02-23 14:41:21 -06:00
< div className = { cn ( cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3" ) } >
2026-03-16 15:41:06 -05:00
{ showAdapterTypeField && (
< Field label = "Adapter type" hint = { help . adapterType } >
< AdapterTypeDropdown
value = { adapterType }
2026-03-31 20:21:13 +01:00
disabledTypes = { disabledTypes }
2026-03-16 15:41:06 -05:00
onChange = { ( t ) = > {
if ( isCreate ) {
// Reset all adapter-specific fields to defaults when switching adapter type
const { adapterType : _at , . . . defaults } = defaultCreateValues ;
const nextValues : CreateConfigValues = { . . . defaults , adapterType : t } ;
if ( t === "codex_local" ) {
nextValues . model = DEFAULT_CODEX_LOCAL_MODEL ;
nextValues . dangerouslyBypassSandbox =
DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX ;
} else if ( t === "gemini_local" ) {
nextValues . model = DEFAULT_GEMINI_LOCAL_MODEL ;
} else if ( t === "cursor" ) {
nextValues . model = DEFAULT_CURSOR_LOCAL_MODEL ;
} else if ( t === "opencode_local" ) {
nextValues . model = "" ;
}
set ! ( nextValues ) ;
} else {
// Clear all adapter config and explicitly blank out model + effort/mode keys
// so the old adapter's values don't bleed through via eff()
setOverlay ( ( prev ) = > ( {
. . . prev ,
adapterType : t ,
adapterConfig : {
model :
t === "codex_local"
? DEFAULT_CODEX_LOCAL_MODEL
: t === "gemini_local"
? DEFAULT_GEMINI_LOCAL_MODEL
: t === "cursor"
? DEFAULT_CURSOR_LOCAL_MODEL
: "" ,
effort : "" ,
modelReasoningEffort : "" ,
variant : "" ,
mode : "" ,
. . . ( t === "codex_local"
? {
dangerouslyBypassApprovalsAndSandbox :
DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX ,
}
: { } ) ,
} ,
} ) ) ;
2026-03-03 12:41:50 -06:00
}
2026-03-16 15:41:06 -05:00
} }
/ >
< / Field >
) }
2026-02-20 14:11:30 -06:00
2026-02-20 12:28:42 -06:00
{ testEnvironment . error && (
< div className = "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive" >
{ testEnvironment . error instanceof Error
? testEnvironment . error . message
: "Environment test failed" }
< / div >
) }
{ testEnvironment . data && (
< AdapterEnvironmentResult result = { testEnvironment . data } / >
) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
{ /* Working directory */ }
2026-03-20 07:04:41 -05:00
{ showLegacyWorkingDirectoryField && (
< Field label = "Working directory (deprecated)" hint = { help . cwd } >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< div className = "flex items-center gap-2 rounded-md border border-border px-2.5 py-1.5" >
< FolderOpen className = "h-3.5 w-3.5 text-muted-foreground shrink-0" / >
< DraftInput
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
value = {
isCreate
? val ! . cwd
: eff ( "adapterConfig" , "cwd" , String ( config . cwd ? ? "" ) )
}
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
onCommit = { ( v ) = >
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
isCreate
? set ! ( { cwd : v } )
: mark ( "adapterConfig" , "cwd" , v || undefined )
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
}
2026-02-18 13:02:23 -06:00
immediate
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = "w-full bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40"
placeholder = "/path/to/project"
/ >
2026-03-02 16:08:59 -06:00
< ChoosePathButton / >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / div >
< / Field >
) }
2026-02-20 14:11:30 -06:00
{ /* Prompt template (create mode only — edit mode shows this in Identity) */ }
{ isLocal && isCreate && (
2026-03-13 08:49:11 -05:00
< >
< Field label = "Prompt Template" hint = { help . promptTemplate } >
< MarkdownEditor
value = { val ! . promptTemplate }
onChange = { ( v ) = > set ! ( { promptTemplate : v } ) }
placeholder = "You are agent {{ agent.name }}. Your role is {{ agent.role }}..."
contentClassName = "min-h-[88px] text-sm font-mono"
imageUploadHandler = { async ( file ) = > {
const namespace = "agents/drafts/prompt-template" ;
const asset = await uploadMarkdownImage . mutateAsync ( { file , namespace } ) ;
return asset . contentPath ;
} }
/ >
< / Field >
< div className = "rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-100" >
Prompt template is replayed on every heartbeat . Prefer small task framing and variables like < code > { "{{ context.* }}" } < / code > or < code > { "{{ run.* }}" } < / code > ; avoid repeating stable instructions here .
< / div >
< / >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
) }
2026-04-01 21:56:19 +01:00
{ /* Adapter-specific fields are rendered inside Permissions & Configuration */ }
2026-02-20 12:50:36 -06:00
< / div >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
2026-02-20 14:16:21 -06:00
< / div >
{ /* ---- Permissions & Configuration ---- */ }
{ isLocal && (
2026-02-23 14:41:21 -06:00
< div className = { cn ( ! cards && "border-b border-border" ) } >
{ cards
? < h3 className = "text-sm font-medium mb-3" > Permissions & amp ; Configuration < / h3 >
: < div className = "px-4 py-2 text-xs font-medium text-muted-foreground" > Permissions & amp ; Configuration < / div >
}
< div className = { cn ( cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3" ) } >
2026-02-20 12:50:36 -06:00
< Field label = "Command" hint = { help . localCommand } >
< DraftInput
value = {
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
isCreate
2026-02-20 12:50:36 -06:00
? val ! . command
2026-04-20 17:55:08 -03:00
: eff (
"adapterConfig" ,
adapterCommandField ,
String (
( adapterType === "hermes_local"
? config . hermesCommand ? ? config . command
: config . command ) ? ? "" ,
) ,
)
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
}
2026-02-20 12:50:36 -06:00
onCommit = { ( v ) = >
isCreate
? set ! ( { command : v } )
2026-04-20 17:55:08 -03:00
: mark ( "adapterConfig" , adapterCommandField , v || null )
2026-02-20 12:50:36 -06:00
}
immediate
className = { inputClass }
2026-03-04 16:48:54 -06:00
placeholder = {
2026-03-31 20:21:13 +01:00
( {
claude_local : "claude" ,
codex_local : "codex" ,
gemini_local : "gemini" ,
pi_local : "pi" ,
cursor : "agent" ,
opencode_local : "opencode" ,
} as Record < string , string > ) [ adapterType ] ? ? adapterType . replace ( /_local$/ , "" )
2026-03-04 16:48:54 -06:00
}
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
/ >
2026-02-20 12:50:36 -06:00
< / Field >
< ModelDropdown
models = { models }
value = { currentModelId }
onChange = { ( v ) = >
isCreate
? set ! ( { model : v } )
: mark ( "adapterConfig" , "model" , v || undefined )
}
open = { modelOpen }
onOpenChange = { setModelOpen }
2026-03-31 20:21:13 +01:00
allowDefault = { adapterType !== "opencode_local" }
required = { adapterType === "opencode_local" }
2026-03-05 15:24:20 +01:00
groupByProvider = { adapterType === "opencode_local" }
2026-03-31 20:21:13 +01:00
creatable
detectedModel = { detectedModel }
detectedModelCandidates = { [ ] }
onDetectModel = { async ( ) = > {
const result = await refetchDetectedModel ( ) ;
return result . data ? . model ? ? null ;
} }
detectModelLabel = "Detect model"
emptyDetectHint = "No model detected. Select or enter one manually."
2026-02-20 12:50:36 -06:00
/ >
2026-03-05 15:24:20 +01:00
{ fetchedModelsError && (
< p className = "text-xs text-destructive" >
{ fetchedModelsError instanceof Error
? fetchedModelsError . message
: "Failed to load adapter models." }
< / p >
) }
2026-02-20 10:32:32 -06:00
2026-03-08 16:43:34 +05:30
{ showThinkingEffort && (
< >
< ThinkingEffortDropdown
value = { currentThinkingEffort }
options = { thinkingEffortOptions }
onChange = { ( v ) = >
isCreate
? set ! ( { thinkingEffort : v } )
: mark ( "adapterConfig" , thinkingEffortKey , v || undefined )
}
open = { thinkingEffortOpen }
onOpenChange = { setThinkingEffortOpen }
/ >
{ adapterType === "codex_local" &&
codexSearchEnabled &&
currentThinkingEffort === "minimal" && (
< p className = "text-xs text-amber-400" >
Codex may reject ` minimal ` thinking when search is enabled .
< / p >
) }
< / >
) }
2026-03-18 06:37:24 -05:00
{ ! isCreate && typeof config . bootstrapPromptTemplate === "string" && config . bootstrapPromptTemplate && (
< >
< Field label = "Bootstrap prompt (legacy)" hint = { help . bootstrapPrompt } >
< MarkdownEditor
value = { eff (
"adapterConfig" ,
"bootstrapPromptTemplate" ,
String ( config . bootstrapPromptTemplate ? ? "" ) ,
) }
onChange = { ( v ) = >
mark ( "adapterConfig" , "bootstrapPromptTemplate" , v || undefined )
}
placeholder = "Optional initial setup prompt for the first run"
contentClassName = "min-h-[44px] text-sm font-mono"
imageUploadHandler = { async ( file ) = > {
const namespace = ` agents/ ${ props . agent . id } /bootstrap-prompt ` ;
const asset = await uploadMarkdownImage . mutateAsync ( { file , namespace } ) ;
return asset . contentPath ;
} }
/ >
< / Field >
< div className = "rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-200" >
Bootstrap prompt is legacy and will be removed in a future release . Consider moving this content into the agent & apos ; s prompt template or instructions file instead .
< / div >
< / >
) }
2026-02-20 12:50:36 -06:00
{ adapterType === "claude_local" && (
< ClaudeLocalAdvancedFields { ...adapterFieldProps } / >
) }
2026-04-01 21:56:19 +01:00
< uiAdapter.ConfigFields { ...adapterFieldProps } / >
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
2026-02-20 12:50:36 -06:00
< Field label = "Extra args (comma-separated)" hint = { help . extraArgs } >
< DraftInput
value = {
isCreate
? val ! . extraArgs
: eff ( "adapterConfig" , "extraArgs" , formatArgList ( config . extraArgs ) )
}
onCommit = { ( v ) = >
isCreate
? set ! ( { extraArgs : v } )
2026-04-03 23:15:10 +09:00
: mark ( "adapterConfig" , "extraArgs" , v ? . trim ( ) ? parseCommaArgs ( v ) : null )
2026-02-20 12:50:36 -06:00
}
immediate
className = { inputClass }
placeholder = "e.g. --verbose, --foo=bar"
/ >
< / Field >
< Field label = "Environment variables" hint = { help . envVars } >
< EnvVarEditor
value = {
isCreate
2026-03-03 09:36:49 -06:00
? ( ( val ! . envBindings ? ? EMPTY_ENV ) as Record < string , EnvBinding > )
: ( ( eff ( "adapterConfig" , "env" , ( config . env ? ? EMPTY_ENV ) as Record < string , EnvBinding > ) )
2026-02-20 12:50:36 -06:00
)
}
secrets = { availableSecrets }
onCreateSecret = { async ( name , value ) = > {
const created = await createSecret . mutateAsync ( { name , value } ) ;
return created ;
} }
onChange = { ( env ) = >
isCreate
? set ! ( { envBindings : env ? ? { } , envVars : "" } )
: mark ( "adapterConfig" , "env" , env )
}
/ >
< / Field >
{ /* Edit-only: timeout + grace period */ }
{ ! isCreate && (
< >
< Field label = "Timeout (sec)" hint = { help . timeoutSec } >
< DraftNumberInput
value = { eff (
"adapterConfig" ,
"timeoutSec" ,
Number ( config . timeoutSec ? ? 0 ) ,
) }
onCommit = { ( v ) = > mark ( "adapterConfig" , "timeoutSec" , v ) }
immediate
className = { inputClass }
/ >
< / Field >
< Field label = "Interrupt grace period (sec)" hint = { help . graceSec } >
< DraftNumberInput
value = { eff (
"adapterConfig" ,
"graceSec" ,
Number ( config . graceSec ? ? 15 ) ,
) }
onCommit = { ( v ) = > mark ( "adapterConfig" , "graceSec" , v ) }
immediate
className = { inputClass }
/ >
< / Field >
< / >
) }
2026-02-20 14:16:21 -06:00
< / div >
< / div >
) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
2026-02-20 14:11:30 -06:00
{ /* ---- Run Policy ---- */ }
2026-03-16 15:41:06 -05:00
{ isCreate && showCreateRunPolicySection ? (
2026-02-23 14:41:21 -06:00
< div className = { cn ( ! cards && "border-b border-border" ) } >
{ cards
? < h3 className = "text-sm font-medium flex items-center gap-2 mb-3" > < Heart className = "h-3 w-3" / > Run Policy < / h3 >
: < div className = "px-4 py-2 text-xs font-medium text-muted-foreground flex items-center gap-2" > < Heart className = "h-3 w-3" / > Run Policy < / div >
}
< div className = { cn ( cards ? "border border-border rounded-lg p-4 space-y-3" : "px-4 pb-3 space-y-3" ) } >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< ToggleWithNumber
label = "Heartbeat on interval"
hint = { help . heartbeatInterval }
checked = { val ! . heartbeatEnabled }
onCheckedChange = { ( v ) = > set ! ( { heartbeatEnabled : v } ) }
number = { val ! . intervalSec }
onNumberChange = { ( v ) = > set ! ( { intervalSec : v } ) }
numberLabel = "sec"
numberPrefix = "Run heartbeat every"
numberHint = { help . intervalSec }
showNumber = { val ! . heartbeatEnabled }
/ >
< / div >
2026-02-20 14:16:21 -06:00
< / div >
2026-03-16 15:41:06 -05:00
) : ! isCreate ? (
2026-02-23 14:41:21 -06:00
< div className = { cn ( ! cards && "border-b border-border" ) } >
{ cards
? < h3 className = "text-sm font-medium flex items-center gap-2 mb-3" > < Heart className = "h-3 w-3" / > Run Policy < / h3 >
: < div className = "px-4 py-2 text-xs font-medium text-muted-foreground flex items-center gap-2" > < Heart className = "h-3 w-3" / > Run Policy < / div >
}
< div className = { cn ( cards ? "border border-border rounded-lg overflow-hidden" : "" ) } >
< div className = { cn ( cards ? "p-4 space-y-3" : "px-4 pb-3 space-y-3" ) } >
< ToggleWithNumber
label = "Heartbeat on interval"
hint = { help . heartbeatInterval }
2026-04-08 07:26:34 -05:00
checked = { eff ( "heartbeat" , "enabled" , heartbeat . enabled === true ) }
2026-02-23 14:41:21 -06:00
onCheckedChange = { ( v ) = > mark ( "heartbeat" , "enabled" , v ) }
number = { eff ( "heartbeat" , "intervalSec" , Number ( heartbeat . intervalSec ? ? 300 ) ) }
onNumberChange = { ( v ) = > mark ( "heartbeat" , "intervalSec" , v ) }
numberLabel = "sec"
numberPrefix = "Run heartbeat every"
numberHint = { help . intervalSec }
2026-04-08 07:26:34 -05:00
showNumber = { eff ( "heartbeat" , "enabled" , heartbeat . enabled === true ) }
2026-02-23 14:41:21 -06:00
/ >
< / div >
< CollapsibleSection
title = "Advanced Run Policy"
bordered = { cards }
open = { runPolicyAdvancedOpen }
onToggle = { ( ) = > setRunPolicyAdvancedOpen ( ! runPolicyAdvancedOpen ) }
>
2026-02-20 14:11:30 -06:00
< div className = "space-y-3" >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< ToggleField
2026-02-18 16:46:29 -06:00
label = "Wake on demand"
hint = { help . wakeOnDemand }
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
checked = { eff (
"heartbeat" ,
2026-02-18 16:46:29 -06:00
"wakeOnDemand" ,
heartbeat . wakeOnDemand !== false ,
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
) }
2026-02-18 16:46:29 -06:00
onChange = { ( v ) = > mark ( "heartbeat" , "wakeOnDemand" , v ) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/ >
< Field label = "Cooldown (sec)" hint = { help . cooldownSec } >
< DraftNumberInput
Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration
fields and layout. Refine sidebar, breadcrumbs, and card/tab
components for visual consistency. Clean up page layouts across
Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox,
Issues, Org, and Projects pages. Minor heartbeat-run CLI fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:43:25 -06:00
value = { eff (
"heartbeat" ,
"cooldownSec" ,
Number ( heartbeat . cooldownSec ? ? 10 ) ,
) }
onCommit = { ( v ) = > mark ( "heartbeat" , "cooldownSec" , v ) }
2026-02-18 13:02:23 -06:00
immediate
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = { inputClass }
/ >
< / Field >
2026-02-20 12:50:36 -06:00
< Field label = "Max concurrent runs" hint = { help . maxConcurrentRuns } >
< DraftNumberInput
value = { eff (
"heartbeat" ,
"maxConcurrentRuns" ,
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.4 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
Number ( heartbeat . maxConcurrentRuns ? ? AGENT_DEFAULT_MAX_CONCURRENT_RUNS ) ,
2026-02-20 12:50:36 -06:00
) }
onCommit = { ( v ) = > mark ( "heartbeat" , "maxConcurrentRuns" , v ) }
immediate
className = { inputClass }
/ >
< / Field >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / div >
2026-02-20 14:11:30 -06:00
< / CollapsibleSection >
2026-02-23 14:41:21 -06:00
< / div >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / div >
2026-03-16 15:41:06 -05:00
) : null }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / div >
) ;
}
2026-02-20 12:28:42 -06:00
function AdapterEnvironmentResult ( { result } : { result : AdapterEnvironmentTestResult } ) {
const statusLabel =
result . status === "pass" ? "Passed" : result . status === "warn" ? "Warnings" : "Failed" ;
const statusClass =
result . status === "pass"
2026-02-26 16:33:48 -06:00
? "text-green-700 dark:text-green-300 border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10"
2026-02-20 12:28:42 -06:00
: result . status === "warn"
2026-02-26 16:33:48 -06:00
? "text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10"
: "text-red-700 dark:text-red-300 border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10" ;
2026-02-20 12:28:42 -06:00
return (
< div className = { ` rounded-md border px-3 py-2 text-xs ${ statusClass } ` } >
< div className = "flex items-center justify-between gap-2" >
< span className = "font-medium" > { statusLabel } < / span >
< span className = "text-[11px] opacity-80" >
{ new Date ( result . testedAt ) . toLocaleTimeString ( ) }
< / span >
< / div >
< div className = "mt-2 space-y-1.5" >
{ result . checks . map ( ( check , idx ) = > (
2026-03-03 13:02:08 -06:00
< div key = { ` ${ check . code } - ${ idx } ` } className = "text-[11px] leading-relaxed break-words" >
2026-02-20 12:28:42 -06:00
< span className = "font-medium uppercase tracking-wide opacity-80" >
{ check . level }
< / span >
< span className = "mx-1 opacity-60" > · < / span >
< span > { check . message } < / span >
2026-03-03 13:02:08 -06:00
{ check . detail && < span className = "block opacity-75 break-all" > ( { check . detail } ) < / span > }
{ check . hint && < span className = "block opacity-90 break-words" > Hint : { check . hint } < / span > }
2026-02-20 12:28:42 -06:00
< / div >
) ) }
< / div >
< / div >
) ;
}
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Internal sub-components ---- */
function AdapterTypeDropdown ( {
value ,
onChange ,
2026-03-31 20:21:13 +01:00
disabledTypes ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} : {
value : string ;
onChange : ( type : string ) = > void ;
2026-03-31 20:21:13 +01:00
disabledTypes : Set < string > ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} ) {
2026-03-31 20:21:13 +01:00
const [ open , setOpen ] = useState ( false ) ;
const adapterList = useMemo (
( ) = >
listAdapterOptions ( ( type ) = > adapterLabels [ type ] ? ? getAdapterLabel ( type ) ) . filter (
( item ) = > ! disabledTypes . has ( item . value ) ,
) ,
[ disabledTypes ] ,
) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
return (
2026-03-31 20:21:13 +01:00
< Popover open = { open } onOpenChange = { setOpen } >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< PopoverTrigger asChild >
< button className = "inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-sm hover:bg-accent/50 transition-colors w-full justify-between" >
2026-03-05 15:24:20 +01:00
< span className = "inline-flex items-center gap-1.5" >
{ value === "opencode_local" ? < OpenCodeLogoIcon className = "h-3.5 w-3.5" / > : null }
2026-03-31 20:21:13 +01:00
< span > { adapterLabels [ value ] ? ? getAdapterLabel ( value ) } < / span >
2026-03-05 15:24:20 +01:00
< / span >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< ChevronDown className = "h-3 w-3 text-muted-foreground" / >
< / button >
< / PopoverTrigger >
< PopoverContent className = "w-[var(--radix-popover-trigger-width)] p-1" align = "start" >
2026-03-31 20:21:13 +01:00
{ adapterList . map ( ( item ) = > (
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< button
2026-03-03 11:29:34 -06:00
key = { item . value }
disabled = { item . comingSoon }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = { cn (
2026-03-03 11:29:34 -06:00
"flex items-center justify-between w-full px-2 py-1.5 text-sm rounded" ,
item . comingSoon
? "opacity-40 cursor-not-allowed"
: "hover:bg-accent/50" ,
item . value === value && ! item . comingSoon && "bg-accent" ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
) }
2026-03-03 11:29:34 -06:00
onClick = { ( ) = > {
2026-03-31 20:21:13 +01:00
if ( ! item . comingSoon ) {
onChange ( item . value ) ;
setOpen ( false ) ;
}
2026-03-03 11:29:34 -06:00
} }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
>
2026-03-05 15:24:20 +01:00
< span className = "inline-flex items-center gap-1.5" >
{ item . value === "opencode_local" ? < OpenCodeLogoIcon className = "h-3.5 w-3.5" / > : null }
< span > { item . label } < / span >
< / span >
2026-03-03 11:29:34 -06:00
{ item . comingSoon && (
< span className = "text-[10px] text-muted-foreground" > Coming soon < / span >
) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / button >
) ) }
< / PopoverContent >
< / Popover >
) ;
}
function ModelDropdown ( {
models ,
value ,
onChange ,
open ,
onOpenChange ,
2026-03-05 15:24:20 +01:00
allowDefault ,
required ,
groupByProvider ,
2026-03-28 01:34:48 +01:00
creatable ,
detectedModel ,
2026-03-31 20:21:13 +01:00
detectedModelCandidates ,
2026-03-28 01:34:48 +01:00
onDetectModel ,
2026-03-28 11:35:58 +01:00
detectModelLabel ,
2026-03-31 20:21:13 +01:00
emptyDetectHint ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} : {
models : AdapterModel [ ] ;
value : string ;
onChange : ( id : string ) = > void ;
open : boolean ;
onOpenChange : ( open : boolean ) = > void ;
2026-03-05 15:24:20 +01:00
allowDefault : boolean ;
required : boolean ;
groupByProvider : boolean ;
2026-03-28 01:34:48 +01:00
creatable? : boolean ;
detectedModel? : string | null ;
2026-03-31 20:21:13 +01:00
detectedModelCandidates? : string [ ] ;
2026-03-28 01:34:48 +01:00
onDetectModel ? : ( ) = > Promise < string | null > ;
2026-03-28 11:35:58 +01:00
detectModelLabel? : string ;
2026-03-31 20:21:13 +01:00
emptyDetectHint? : string ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} ) {
2026-02-20 10:32:32 -06:00
const [ modelSearch , setModelSearch ] = useState ( "" ) ;
2026-03-28 01:34:48 +01:00
const [ detectingModel , setDetectingModel ] = useState ( false ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
const selected = models . find ( ( m ) = > m . id === value ) ;
2026-03-28 01:34:48 +01:00
const manualModel = modelSearch . trim ( ) ;
const canCreateManualModel = Boolean (
creatable &&
manualModel &&
! models . some ( ( m ) = > m . id . toLowerCase ( ) === manualModel . toLowerCase ( ) ) ,
) ;
2026-03-31 20:21:13 +01:00
// Model IDs already shown as detected/candidate badges — exclude from regular list
const promotedModelIds = useMemo ( ( ) = > {
const set = new Set < string > ( ) ;
if ( detectedModel ) set . add ( detectedModel ) ;
for ( const c of detectedModelCandidates ? ? [ ] ) {
if ( c ) set . add ( c ) ;
}
return set ;
} , [ detectedModel , detectedModelCandidates ] ) ;
2026-03-05 15:24:20 +01:00
const filteredModels = useMemo ( ( ) = > {
return models . filter ( ( m ) = > {
2026-03-31 20:21:13 +01:00
if ( promotedModelIds . has ( m . id ) ) return false ;
2026-03-05 15:24:20 +01:00
if ( ! modelSearch . trim ( ) ) return true ;
const q = modelSearch . toLowerCase ( ) ;
const provider = extractProviderId ( m . id ) ? ? "" ;
return (
m . id . toLowerCase ( ) . includes ( q ) ||
m . label . toLowerCase ( ) . includes ( q ) ||
provider . toLowerCase ( ) . includes ( q )
) ;
} ) ;
2026-03-31 20:21:13 +01:00
} , [ models , modelSearch , promotedModelIds ] ) ;
2026-03-05 15:24:20 +01:00
const groupedModels = useMemo ( ( ) = > {
if ( ! groupByProvider ) {
return [
{
provider : "models" ,
entries : [ . . . filteredModels ] . sort ( ( a , b ) = > a . id . localeCompare ( b . id ) ) ,
} ,
] ;
}
const map = new Map < string , AdapterModel [ ] > ( ) ;
for ( const model of filteredModels ) {
const provider = extractProviderId ( model . id ) ? ? "other" ;
const group = map . get ( provider ) ? ? [ ] ;
group . push ( model ) ;
map . set ( provider , group ) ;
}
return Array . from ( map . entries ( ) )
. sort ( ( [ a ] , [ b ] ) = > a . localeCompare ( b ) )
. map ( ( [ provider , entries ] ) = > ( {
provider ,
entries : [ . . . entries ] . sort ( ( a , b ) = > a . id . localeCompare ( b . id ) ) ,
} ) ) ;
} , [ filteredModels , groupByProvider ] ) ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
2026-03-28 01:34:48 +01:00
async function handleDetectModel() {
if ( ! onDetectModel ) return ;
setDetectingModel ( true ) ;
try {
const nextModel = await onDetectModel ( ) ;
if ( nextModel ) {
onChange ( nextModel ) ;
onOpenChange ( false ) ;
setModelSearch ( "" ) ;
}
} finally {
setDetectingModel ( false ) ;
}
}
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
return (
< Field label = "Model" hint = { help . model } >
2026-02-20 10:32:32 -06:00
< Popover
open = { open }
onOpenChange = { ( nextOpen ) = > {
onOpenChange ( nextOpen ) ;
if ( ! nextOpen ) setModelSearch ( "" ) ;
} }
>
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< PopoverTrigger asChild >
2026-03-28 01:34:48 +01:00
< button type = "button" className = "inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-sm hover:bg-accent/50 transition-colors w-full justify-between" >
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< span className = { cn ( ! value && "text-muted-foreground" ) } >
2026-03-05 15:24:20 +01:00
{ selected
? selected . label
: value || ( allowDefault ? "Default" : required ? "Select model (required)" : "Select model" ) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / span >
< ChevronDown className = "h-3 w-3 text-muted-foreground" / >
< / button >
< / PopoverTrigger >
< PopoverContent className = "w-[var(--radix-popover-trigger-width)] p-1" align = "start" >
2026-03-28 01:34:48 +01:00
< div className = "relative mb-1" >
< input
className = "w-full px-2 py-1.5 pr-6 text-xs bg-transparent outline-none border-b border-border placeholder:text-muted-foreground/50"
placeholder = { creatable ? "Search models... (type to create)" : "Search models..." }
value = { modelSearch }
onChange = { ( e ) = > setModelSearch ( e . target . value ) }
2026-03-28 11:35:58 +01:00
autoFocus
2026-03-28 01:34:48 +01:00
/ >
{ modelSearch && (
< button
type = "button"
className = "absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
onClick = { ( ) = > setModelSearch ( "" ) }
>
< svg aria-hidden = "true" focusable = "false" className = "h-3 w-3" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
< line x1 = "18" y1 = "6" x2 = "6" y2 = "18" / >
< line x1 = "6" y1 = "6" x2 = "18" y2 = "18" / >
< / svg >
< / button >
) }
< / div >
2026-03-31 20:21:13 +01:00
{ onDetectModel && ! modelSearch . trim ( ) && (
2026-03-28 01:34:48 +01:00
< button
type = "button"
className = "flex items-center gap-1.5 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50 text-muted-foreground"
onClick = { ( ) = > {
void handleDetectModel ( ) ;
} }
disabled = { detectingModel }
>
< svg aria-hidden = "true" focusable = "false" className = "h-3 w-3" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
< path d = "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" / >
< path d = "M3 3v5h5" / >
< / svg >
2026-03-31 20:21:13 +01:00
{ detectingModel ? "Detecting..." : detectedModel ? ( detectModelLabel ? . replace ( /^Detect\b/ , "Re-detect" ) ? ? "Re-detect from config" ) : ( detectModelLabel ? ? "Detect from config" ) }
2026-03-28 01:34:48 +01:00
< / button >
) }
2026-03-31 20:21:13 +01:00
{ value && ( ! models . some ( ( m ) = > m . id === value ) || promotedModelIds . has ( value ) ) && (
2026-03-28 01:34:48 +01:00
< button
type = "button"
className = { cn (
"flex items-center w-full px-2 py-1.5 text-sm rounded bg-accent/50" ,
) }
onClick = { ( ) = > {
onOpenChange ( false ) ;
} }
>
< span className = "block w-full text-left truncate font-mono text-xs" title = { value } >
2026-03-31 20:21:13 +01:00
{ models . find ( ( m ) = > m . id === value ) ? . label ? ? value }
2026-03-28 01:34:48 +01:00
< / span >
< span className = "shrink-0 ml-auto text-[9px] font-medium px-1.5 py-0.5 rounded-full bg-green-500/15 text-green-400 border border-green-500/20" >
current
< / span >
< / button >
) }
{ detectedModel && detectedModel !== value && (
< button
type = "button"
className = { cn (
"flex items-center w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50" ,
) }
onClick = { ( ) = > {
onChange ( detectedModel ) ;
onOpenChange ( false ) ;
} }
>
< span className = "block w-full text-left truncate font-mono text-xs" title = { detectedModel } >
2026-03-31 20:21:13 +01:00
{ models . find ( ( m ) = > m . id === detectedModel ) ? . label ? ? detectedModel }
2026-03-28 01:34:48 +01:00
< / span >
< span className = "shrink-0 ml-auto text-[9px] font-medium px-1.5 py-0.5 rounded-full bg-blue-500/15 text-blue-400 border border-blue-500/20" >
detected
< / span >
< / button >
) }
2026-03-31 20:21:13 +01:00
{ detectedModelCandidates
? . filter ( ( candidate ) = > candidate && candidate !== detectedModel && candidate !== value )
. map ( ( candidate ) = > {
const entry = models . find ( ( m ) = > m . id === candidate ) ;
return (
< button
key = { ` detected- ${ candidate } ` }
type = "button"
className = { cn (
"flex items-center w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50" ,
) }
onClick = { ( ) = > {
onChange ( candidate ) ;
onOpenChange ( false ) ;
} }
>
< span className = "block w-full text-left truncate font-mono text-xs" title = { candidate } >
{ entry ? . label ? ? candidate }
< / span >
< span className = "shrink-0 ml-auto text-[9px] font-medium px-1.5 py-0.5 rounded-full bg-sky-500/15 text-sky-400 border border-sky-500/20" >
config
< / span >
< / button >
) ;
} ) }
2026-02-26 16:33:48 -06:00
< div className = "max-h-[240px] overflow-y-auto" >
2026-03-05 15:24:20 +01:00
{ allowDefault && (
2026-02-26 16:33:48 -06:00
< button
2026-03-28 01:34:48 +01:00
type = "button"
2026-02-26 16:33:48 -06:00
className = { cn (
2026-03-05 15:24:20 +01:00
"flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50" ,
! value && "bg-accent" ,
2026-02-26 16:33:48 -06:00
) }
onClick = { ( ) = > {
2026-03-05 15:24:20 +01:00
onChange ( "" ) ;
2026-02-26 16:33:48 -06:00
onOpenChange ( false ) ;
} }
>
2026-03-05 15:24:20 +01:00
Default
2026-02-26 16:33:48 -06:00
< / button >
2026-03-05 15:24:20 +01:00
) }
2026-03-28 01:34:48 +01:00
{ canCreateManualModel && (
< button
type = "button"
className = "flex items-center justify-between gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50"
onClick = { ( ) = > {
onChange ( manualModel ) ;
onOpenChange ( false ) ;
setModelSearch ( "" ) ;
} }
>
< span > Use manual model < / span >
< span className = "text-xs font-mono text-muted-foreground" > { manualModel } < / span >
< / button >
) }
2026-03-05 15:24:20 +01:00
{ groupedModels . map ( ( group ) = > (
< div key = { group . provider } className = "mb-1 last:mb-0" >
{ groupByProvider && (
< div className = "px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground" >
{ group . provider } ( { group . entries . length } )
< / div >
) }
{ group . entries . map ( ( m ) = > (
< button
2026-03-28 01:34:48 +01:00
type = "button"
2026-03-05 15:24:20 +01:00
key = { m . id }
className = { cn (
"flex items-center w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50" ,
m . id === value && "bg-accent" ,
) }
onClick = { ( ) = > {
onChange ( m . id ) ;
onOpenChange ( false ) ;
} }
>
< span className = "block w-full text-left truncate" title = { m . id } >
{ groupByProvider ? extractModelName ( m . id ) : m . label }
< / span >
< / button >
) ) }
< / div >
2026-02-26 16:33:48 -06:00
) ) }
2026-03-31 20:21:13 +01:00
{ filteredModels . length === 0 && ! canCreateManualModel && promotedModelIds . size === 0 && (
2026-03-28 01:34:48 +01:00
< div className = "px-2 py-2 space-y-2" >
< p className = "text-xs text-muted-foreground" >
{ onDetectModel
2026-03-31 20:21:13 +01:00
? ( emptyDetectHint ? ? "No model detected yet. Enter a provider/model manually." )
2026-03-28 01:34:48 +01:00
: "No models found." }
< / p >
< / div >
2026-02-26 16:33:48 -06:00
) }
< / div >
2026-02-20 10:32:32 -06:00
< / PopoverContent >
< / Popover >
< / Field >
) ;
}
function ThinkingEffortDropdown ( {
value ,
options ,
onChange ,
open ,
onOpenChange ,
} : {
value : string ;
options : ReadonlyArray < { id : string ; label : string } > ;
onChange : ( id : string ) = > void ;
open : boolean ;
onOpenChange : ( open : boolean ) = > void ;
} ) {
const selected = options . find ( ( option ) = > option . id === value ) ? ? options [ 0 ] ;
return (
< Field label = "Thinking effort" hint = { help . thinkingEffort } >
< Popover open = { open } onOpenChange = { onOpenChange } >
< PopoverTrigger asChild >
< button className = "inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-sm hover:bg-accent/50 transition-colors w-full justify-between" >
< span className = { cn ( ! value && "text-muted-foreground" ) } > { selected ? . label ? ? "Auto" } < / span >
< ChevronDown className = "h-3 w-3 text-muted-foreground" / >
< / button >
< / PopoverTrigger >
< PopoverContent className = "w-[var(--radix-popover-trigger-width)] p-1" align = "start" >
{ options . map ( ( option ) = > (
< button
key = { option . id || "auto" }
className = { cn (
"flex items-center justify-between w-full px-2 py-1.5 text-sm rounded hover:bg-accent/50" ,
option . id === value && "bg-accent" ,
) }
onClick = { ( ) = > {
onChange ( option . id ) ;
onOpenChange ( false ) ;
} }
>
< span > { option . label } < / span >
{ option . id ? < span className = "text-xs text-muted-foreground font-mono" > { option . id } < / span > : null }
< / button >
) ) }
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
< / PopoverContent >
< / Popover >
< / Field >
) ;
}