paperclip/ui/src/api/agents.ts

234 lines
9 KiB
TypeScript
Raw Normal View History

import type {
Agent,
AgentDetail,
AgentInstructionsBundle,
AgentInstructionsFileDetail,
AgentSkillSnapshot,
AdapterEnvironmentTestResult,
AgentKeyCreated,
AgentRuntimeState,
AgentTaskSession,
AgentWakeupResponse,
HeartbeatRun,
Approval,
AgentConfigRevision,
} from "@paperclipai/shared";
Add cheap model profiles for local adapters (#4881) ## Thinking Path > - Paperclip is a control plane for autonomous AI companies, where adapters are the boundary between the board, agents, and execution runtimes. > - Local adapters currently expose a primary runtime configuration, but operators often need a cheaper model lane for routine or low-risk work. > - That cheap lane has to stay adapter-owned: runtime profile settings should not mutate the primary adapter config or bypass existing auth/secret mediation. > - Issue creation also needs an ergonomic way to request primary, cheap, or custom model behavior for a selected assignee. > - This pull request adds a first-class `cheap` model profile contract across adapter capabilities, heartbeat config resolution, agent configuration, and issue creation. > - The benefit is cheaper task execution can be configured and requested explicitly while preserving adapter boundaries, secret handling, and audit visibility. ## What Changed - Added adapter model-profile capability metadata and a `cheap` profile contract for supported local adapters. - Applied `runtimeConfig.modelProfiles.cheap.adapterConfig` during heartbeat config resolution, including requested/applied/fallback run metadata. - Added agent configuration UI for cheap model profile settings without writing those settings into primary `adapterConfig`. - Added New Issue assignee model lane controls for Primary / Cheap / Custom and request payload handling. - Added run ledger profile badges and Storybook stories for the new cheap-lane UI states. - Added tests for validators, heartbeat model profile application, permission/secret mediation, UI payload helpers, and run ledger rendering. - Added committed UI verification screenshots under `docs/pr-screenshots/pap-2837/`. - Addressed Greptile review feedback around cheap-profile defaults, shared profile types, and fallback test data. ## Verification Local: - `pnpm exec vitest run packages/shared/src/validators/issue.test.ts server/src/__tests__/adapter-registry.test.ts server/src/__tests__/agent-permissions-routes.test.ts server/src/__tests__/heartbeat-model-profile.test.ts ui/src/components/IssueRunLedger.test.tsx ui/src/lib/agent-config-patch.test.ts ui/src/lib/issue-assignee-overrides.test.ts ui/src/lib/new-agent-runtime-config.test.ts` — passed, 8 files / 103 tests. - `pnpm exec vitest run ui/src/lib/new-agent-runtime-config.test.ts ui/src/components/IssueRunLedger.test.tsx` — passed after Greptile/rebase follow-up, 2 files / 17 tests. - `pnpm --filter @paperclipai/ui typecheck` — passed after Greptile/rebase follow-up. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — did not complete successfully in this local worktree: it stopped in pre-existing `@paperclipai/adapter-utils` sandbox/SSH fixture suites outside this PR diff. Failures were 5s local timeouts plus `git init -b` unsupported by this machine's Git 2.21.0. The branch-specific targeted suites above passed. - Branch was fetched/rebased onto `public-gh/master`; `git rev-list --left-right --count public-gh/master...HEAD` reports `0 9`. Remote PR checks on latest head `e30bf399146451c86cee98ed528d51d33fa5af5a`: - `policy` — passed. - `verify` — passed. - `e2e` — passed. - `Greptile Review` — passed, confidence score 5/5; Greptile review threads resolved. - `security/snyk (cryppadotta)` — passed. Screenshots: - [New issue cheap lane desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-cheap-desktop.png) - [New issue custom lane desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-custom-desktop.png) - [New issue unsupported adapter desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-unsupported-desktop.png) - [Run ledger model profile badges desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/runledger-profile-badges-desktop.png) - Mobile variants are also in `docs/pr-screenshots/pap-2837/`. ## Risks - Medium: heartbeat config mediation now merges runtime model profiles into adapter configs, so adapter secret normalization and host-command restrictions must keep covering nested config paths. - Medium: the UI adds another issue creation choice; unsupported adapters must keep hiding the cheap lane and preserve primary behavior. - Low migration risk: no database migration is 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 coding agent using GPT-5-class reasoning with repo tool use and command execution. Exact served model/context window was not exposed by the runtime. ## 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 - [ ] 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> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 15:32:04 -05:00
import type {
AdapterModelProfileDefinition,
AdapterModelProfileKey,
} from "@paperclipai/adapter-utils";
import { isUuidLike, normalizeAgentUrlKey } from "@paperclipai/shared";
import { ApiError, api } from "./client";
export interface AgentKey {
id: string;
name: string;
createdAt: Date;
revokedAt: Date | null;
}
export interface AdapterModel {
id: string;
label: string;
}
Add cheap model profiles for local adapters (#4881) ## Thinking Path > - Paperclip is a control plane for autonomous AI companies, where adapters are the boundary between the board, agents, and execution runtimes. > - Local adapters currently expose a primary runtime configuration, but operators often need a cheaper model lane for routine or low-risk work. > - That cheap lane has to stay adapter-owned: runtime profile settings should not mutate the primary adapter config or bypass existing auth/secret mediation. > - Issue creation also needs an ergonomic way to request primary, cheap, or custom model behavior for a selected assignee. > - This pull request adds a first-class `cheap` model profile contract across adapter capabilities, heartbeat config resolution, agent configuration, and issue creation. > - The benefit is cheaper task execution can be configured and requested explicitly while preserving adapter boundaries, secret handling, and audit visibility. ## What Changed - Added adapter model-profile capability metadata and a `cheap` profile contract for supported local adapters. - Applied `runtimeConfig.modelProfiles.cheap.adapterConfig` during heartbeat config resolution, including requested/applied/fallback run metadata. - Added agent configuration UI for cheap model profile settings without writing those settings into primary `adapterConfig`. - Added New Issue assignee model lane controls for Primary / Cheap / Custom and request payload handling. - Added run ledger profile badges and Storybook stories for the new cheap-lane UI states. - Added tests for validators, heartbeat model profile application, permission/secret mediation, UI payload helpers, and run ledger rendering. - Added committed UI verification screenshots under `docs/pr-screenshots/pap-2837/`. - Addressed Greptile review feedback around cheap-profile defaults, shared profile types, and fallback test data. ## Verification Local: - `pnpm exec vitest run packages/shared/src/validators/issue.test.ts server/src/__tests__/adapter-registry.test.ts server/src/__tests__/agent-permissions-routes.test.ts server/src/__tests__/heartbeat-model-profile.test.ts ui/src/components/IssueRunLedger.test.tsx ui/src/lib/agent-config-patch.test.ts ui/src/lib/issue-assignee-overrides.test.ts ui/src/lib/new-agent-runtime-config.test.ts` — passed, 8 files / 103 tests. - `pnpm exec vitest run ui/src/lib/new-agent-runtime-config.test.ts ui/src/components/IssueRunLedger.test.tsx` — passed after Greptile/rebase follow-up, 2 files / 17 tests. - `pnpm --filter @paperclipai/ui typecheck` — passed after Greptile/rebase follow-up. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — did not complete successfully in this local worktree: it stopped in pre-existing `@paperclipai/adapter-utils` sandbox/SSH fixture suites outside this PR diff. Failures were 5s local timeouts plus `git init -b` unsupported by this machine's Git 2.21.0. The branch-specific targeted suites above passed. - Branch was fetched/rebased onto `public-gh/master`; `git rev-list --left-right --count public-gh/master...HEAD` reports `0 9`. Remote PR checks on latest head `e30bf399146451c86cee98ed528d51d33fa5af5a`: - `policy` — passed. - `verify` — passed. - `e2e` — passed. - `Greptile Review` — passed, confidence score 5/5; Greptile review threads resolved. - `security/snyk (cryppadotta)` — passed. Screenshots: - [New issue cheap lane desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-cheap-desktop.png) - [New issue custom lane desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-custom-desktop.png) - [New issue unsupported adapter desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-unsupported-desktop.png) - [Run ledger model profile badges desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/runledger-profile-badges-desktop.png) - Mobile variants are also in `docs/pr-screenshots/pap-2837/`. ## Risks - Medium: heartbeat config mediation now merges runtime model profiles into adapter configs, so adapter secret normalization and host-command restrictions must keep covering nested config paths. - Medium: the UI adds another issue creation choice; unsupported adapters must keep hiding the cheap lane and preserve primary behavior. - Low migration risk: no database migration is 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 coding agent using GPT-5-class reasoning with repo tool use and command execution. Exact served model/context window was not exposed by the runtime. ## 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 - [ ] 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> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 15:32:04 -05:00
export type { AdapterModelProfileKey };
export type AdapterModelProfile = AdapterModelProfileDefinition;
export interface DetectedAdapterModel {
model: string;
provider: string;
source: string;
candidates?: string[];
}
export interface ClaudeLoginResult {
exitCode: number | null;
signal: string | null;
timedOut: boolean;
loginUrl: string | null;
stdout: string;
stderr: string;
}
export interface OrgNode {
id: string;
name: string;
role: string;
status: string;
reports: OrgNode[];
}
export interface AgentHireResponse {
agent: Agent;
approval: Approval | null;
}
export interface AgentPermissionUpdate {
canCreateAgents: boolean;
canAssignTasks: boolean;
}
Preserve scope on manual heartbeat invokes (#5323) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The agent live-run route lets operators trigger a manual heartbeat invocation so an agent can pick up a specific issue or step out of band > - The current route flow drops the caller's scope (issue/run context) when forwarding the manual invoke into the heartbeat service, so the resulting run loses the targeting the operator specified > - This pull request threads the operator-supplied scope through the manual invoke path on both the server route and the UI client, with a regression test that confirms the scope round-trips > - The benefit is manual heartbeat invokes from the live-run UI actually pick up the scoped issue/run instead of falling through to the agent's default routine ## What Changed - `server/src/routes/agents.ts`: forward the operator-supplied scope into the manual invoke heartbeat service call - `server/src/__tests__/agent-live-run-routes.test.ts`: new test verifying the manual invoke path preserves scope - `ui/src/api/agents.ts`: pass scope through the live-run client API ## Verification - `pnpm vitest run --no-coverage server/src/__tests__/agent-live-run-routes.test.ts` - `pnpm typecheck` clean ## Risks Low. The change is purely additive on the route surface — handlers that did not previously pass scope continue to work; handlers that did pass it now have it preserved instead of dropped. ## Model Used Claude Opus 4.7 (1M context) ## 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 — new test covers the preserved-scope path - [x] If this change affects the UI, I have included before/after screenshots — N/A (internal API change, no visible UI shift) - [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-05-05 19:30:08 -07:00
export interface AgentWakeRequest {
source?: "timer" | "assignment" | "on_demand" | "automation";
triggerDetail?: "manual" | "ping" | "callback" | "system";
reason?: string | null;
payload?: Record<string, unknown> | null;
idempotencyKey?: string | null;
forceFreshSession?: boolean;
}
function withCompanyScope(path: string, companyId?: string) {
if (!companyId) return path;
const separator = path.includes("?") ? "&" : "?";
return `${path}${separator}companyId=${encodeURIComponent(companyId)}`;
}
function agentPath(id: string, companyId?: string, suffix = "") {
return withCompanyScope(`/agents/${encodeURIComponent(id)}${suffix}`, companyId);
}
export const agentsApi = {
list: (companyId: string) => api.get<Agent[]>(`/companies/${companyId}/agents`),
org: (companyId: string) => api.get<OrgNode[]>(`/companies/${companyId}/org`),
listConfigurations: (companyId: string) =>
api.get<Record<string, unknown>[]>(`/companies/${companyId}/agent-configurations`),
get: async (id: string, companyId?: string) => {
try {
return await api.get<AgentDetail>(agentPath(id, companyId));
} catch (error) {
// Backward-compat fallback: if backend shortname lookup reports ambiguity,
// resolve using company agent list while ignoring terminated agents.
if (
!(error instanceof ApiError) ||
error.status !== 409 ||
!companyId ||
isUuidLike(id)
) {
throw error;
}
const urlKey = normalizeAgentUrlKey(id);
if (!urlKey) throw error;
const agents = await api.get<Agent[]>(`/companies/${companyId}/agents`);
const matches = agents.filter(
(agent) => agent.status !== "terminated" && normalizeAgentUrlKey(agent.urlKey) === urlKey,
);
if (matches.length !== 1) throw error;
return api.get<AgentDetail>(agentPath(matches[0]!.id, companyId));
}
},
getConfiguration: (id: string, companyId?: string) =>
api.get<Record<string, unknown>>(agentPath(id, companyId, "/configuration")),
listConfigRevisions: (id: string, companyId?: string) =>
api.get<AgentConfigRevision[]>(agentPath(id, companyId, "/config-revisions")),
getConfigRevision: (id: string, revisionId: string, companyId?: string) =>
api.get<AgentConfigRevision>(agentPath(id, companyId, `/config-revisions/${revisionId}`)),
rollbackConfigRevision: (id: string, revisionId: string, companyId?: string) =>
api.post<Agent>(agentPath(id, companyId, `/config-revisions/${revisionId}/rollback`), {}),
create: (companyId: string, data: Record<string, unknown>) =>
api.post<Agent>(`/companies/${companyId}/agents`, data),
hire: (companyId: string, data: Record<string, unknown>) =>
api.post<AgentHireResponse>(`/companies/${companyId}/agent-hires`, data),
update: (id: string, data: Record<string, unknown>, companyId?: string) =>
api.patch<Agent>(agentPath(id, companyId), data),
updatePermissions: (id: string, data: AgentPermissionUpdate, companyId?: string) =>
api.patch<AgentDetail>(agentPath(id, companyId, "/permissions"), data),
instructionsBundle: (id: string, companyId?: string) =>
api.get<AgentInstructionsBundle>(agentPath(id, companyId, "/instructions-bundle")),
updateInstructionsBundle: (
id: string,
data: {
mode?: "managed" | "external";
rootPath?: string | null;
entryFile?: string;
clearLegacyPromptTemplate?: boolean;
},
companyId?: string,
) => api.patch<AgentInstructionsBundle>(agentPath(id, companyId, "/instructions-bundle"), data),
instructionsFile: (id: string, relativePath: string, companyId?: string) =>
api.get<AgentInstructionsFileDetail>(
agentPath(id, companyId, `/instructions-bundle/file?path=${encodeURIComponent(relativePath)}`),
),
saveInstructionsFile: (
id: string,
data: { path: string; content: string; clearLegacyPromptTemplate?: boolean },
companyId?: string,
) => api.put<AgentInstructionsFileDetail>(agentPath(id, companyId, "/instructions-bundle/file"), data),
deleteInstructionsFile: (id: string, relativePath: string, companyId?: string) =>
api.delete<AgentInstructionsBundle>(
agentPath(id, companyId, `/instructions-bundle/file?path=${encodeURIComponent(relativePath)}`),
),
pause: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/pause"), {}),
resume: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/resume"), {}),
[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
approve: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/approve"), {}),
terminate: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/terminate"), {}),
remove: (id: string, companyId?: string) => api.delete<{ ok: true }>(agentPath(id, companyId)),
listKeys: (id: string, companyId?: string) => api.get<AgentKey[]>(agentPath(id, companyId, "/keys")),
skills: (id: string, companyId?: string) =>
api.get<AgentSkillSnapshot>(agentPath(id, companyId, "/skills")),
syncSkills: (id: string, desiredSkills: string[], companyId?: string) =>
api.post<AgentSkillSnapshot>(agentPath(id, companyId, "/skills/sync"), { desiredSkills }),
createKey: (id: string, name: string, companyId?: string) =>
api.post<AgentKeyCreated>(agentPath(id, companyId, "/keys"), { name }),
revokeKey: (agentId: string, keyId: string, companyId?: string) =>
api.delete<{ ok: true }>(agentPath(agentId, companyId, `/keys/${encodeURIComponent(keyId)}`)),
runtimeState: (id: string, companyId?: string) =>
api.get<AgentRuntimeState>(agentPath(id, companyId, "/runtime-state")),
taskSessions: (id: string, companyId?: string) =>
api.get<AgentTaskSession[]>(agentPath(id, companyId, "/task-sessions")),
resetSession: (id: string, taskKey?: string | null, companyId?: string) =>
api.post<void>(agentPath(id, companyId, "/runtime-state/reset-session"), { taskKey: taskKey ?? null }),
Switch OpenCode to explicit static/local-aware model selection (#5117) > **Stacked PR (part 4 of 7).** Depends on: - PR #5114 - PR #5115 - PR #5116 > Diff against `master` includes commits from earlier PRs in the stack — the new commit in this PR is the topmost one. ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - When creating an OpenCode-local agent, Paperclip currently validates > `adapterConfig.model` against the *Paperclip host's* `opencode models` output > - SSH testing surfaced that this blocks creating an OpenCode agent for an SSH > environment: the model that exists on the SSH target isn't visible to the > host, so creation fails with "OpenCode requires `adapterConfig.model` in > provider/model format" even when the operator picked a real remote model > - The initial direction was environment-aware model discovery; the final > decision was to keep OpenCode on the same explicit-model pattern as other > adapters (default + curated list + manual override) and stop blocking > creation on host-side discovery > - This PR does both: the adapter-models endpoint now accepts `environmentId` and > probes against the target environment, and the create-time hard gate is > replaced by `requireOpenCodeModelId` which validates `provider/model` *format* > without requiring host-local discovery. Test/run-time still surfaces real > auth/availability problems > - The benefit is that operators can create OpenCode agents for remote > environments without out-of-band setup, and the model picker in the UI > reflects the actually-targeted environment ## What Changed - Added `requireOpenCodeModelId(input)` in `opencode-local/src/server/models.ts`, exported it from the adapter index - `ensureOpenCodeModelConfiguredAndAvailable` now delegates the format check to `requireOpenCodeModelId` - `agentsApi.adapterModels(companyId, adapterType, { environmentId })` now accepts an environment ID and passes it as a query parameter - `queryKeys.agents.adapterModels` now keys on `(companyId, adapterType, environmentId)` - `server/src/routes/agents.ts` reads and validates the new query parameter, forwarding it to the adapter's model probe - `AgentConfigForm.tsx` and `OnboardingWizard.tsx` build the model query key from the currently selected default environment ID and disable autodetect for `opencode_local` (model selection is explicit) - `NewAgent.tsx` simplified — no longer special-cases OpenCode autodetect - `company-portability.ts` no longer needs OpenCode-specific autodetect handling - Tests added/updated: `adapter-model-refresh-routes.test.ts`, `adapter-models.test.ts`, `agent-permissions-routes.test.ts`, `opencode-local/src/server/models.test.ts` ## Verification - `pnpm --filter @paperclipai/server test -- adapter-models adapter-model-refresh agent-permissions` - `pnpm --filter @paperclipai/adapter-opencode-local test` - `pnpm --filter @paperclipai/ui test -- AgentConfigForm OnboardingWizard NewAgent` - Manual QA in browser: 1. Boot Paperclip on Tailscale-bound port (so it's reachable from another machine), create an OpenCode-local agent, switch the default environment between two installed sandboxes, and confirm the model list refreshes per-environment 2. Submit with a malformed `provider/model` string and verify the new `requireOpenCodeModelId` error surfaces - Before/after screenshots attached for `AgentConfigForm` model picker ## Risks - Behavioural shift: switching default environment now triggers a model refetch. Should be cheap but introduces a new UI loading state for OpenCode users. - Removing dynamic autodetect for OpenCode: if any user configured an agent without specifying `model` and relied on autodetect populating it, that agent will now fail at submit time. Mitigation: validation error is explicit and actionable. - New query string parameter on `/api/companies/:id/adapter-models` — older clients that omit it still work (parameter is optional and defaults to null). ## Model Used - OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI - Provider: OpenAI - Used to author the code changes in this PR ## 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 - [ ] I have updated relevant documentation to reflect my changes — N/A - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge
2026-05-03 13:01:34 -07:00
adapterModels: (
companyId: string,
type: string,
options?: { refresh?: boolean; environmentId?: string | null },
) => {
const params = new URLSearchParams();
if (options?.refresh) params.set("refresh", "1");
if (options?.environmentId) params.set("environmentId", options.environmentId);
const query = params.size > 0 ? `?${params.toString()}` : "";
return api.get<AdapterModel[]>(
`/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/models${query}`,
);
},
detectModel: (companyId: string, type: string) =>
api.get<DetectedAdapterModel | null>(
`/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/detect-model`,
),
Add cheap model profiles for local adapters (#4881) ## Thinking Path > - Paperclip is a control plane for autonomous AI companies, where adapters are the boundary between the board, agents, and execution runtimes. > - Local adapters currently expose a primary runtime configuration, but operators often need a cheaper model lane for routine or low-risk work. > - That cheap lane has to stay adapter-owned: runtime profile settings should not mutate the primary adapter config or bypass existing auth/secret mediation. > - Issue creation also needs an ergonomic way to request primary, cheap, or custom model behavior for a selected assignee. > - This pull request adds a first-class `cheap` model profile contract across adapter capabilities, heartbeat config resolution, agent configuration, and issue creation. > - The benefit is cheaper task execution can be configured and requested explicitly while preserving adapter boundaries, secret handling, and audit visibility. ## What Changed - Added adapter model-profile capability metadata and a `cheap` profile contract for supported local adapters. - Applied `runtimeConfig.modelProfiles.cheap.adapterConfig` during heartbeat config resolution, including requested/applied/fallback run metadata. - Added agent configuration UI for cheap model profile settings without writing those settings into primary `adapterConfig`. - Added New Issue assignee model lane controls for Primary / Cheap / Custom and request payload handling. - Added run ledger profile badges and Storybook stories for the new cheap-lane UI states. - Added tests for validators, heartbeat model profile application, permission/secret mediation, UI payload helpers, and run ledger rendering. - Added committed UI verification screenshots under `docs/pr-screenshots/pap-2837/`. - Addressed Greptile review feedback around cheap-profile defaults, shared profile types, and fallback test data. ## Verification Local: - `pnpm exec vitest run packages/shared/src/validators/issue.test.ts server/src/__tests__/adapter-registry.test.ts server/src/__tests__/agent-permissions-routes.test.ts server/src/__tests__/heartbeat-model-profile.test.ts ui/src/components/IssueRunLedger.test.tsx ui/src/lib/agent-config-patch.test.ts ui/src/lib/issue-assignee-overrides.test.ts ui/src/lib/new-agent-runtime-config.test.ts` — passed, 8 files / 103 tests. - `pnpm exec vitest run ui/src/lib/new-agent-runtime-config.test.ts ui/src/components/IssueRunLedger.test.tsx` — passed after Greptile/rebase follow-up, 2 files / 17 tests. - `pnpm --filter @paperclipai/ui typecheck` — passed after Greptile/rebase follow-up. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — did not complete successfully in this local worktree: it stopped in pre-existing `@paperclipai/adapter-utils` sandbox/SSH fixture suites outside this PR diff. Failures were 5s local timeouts plus `git init -b` unsupported by this machine's Git 2.21.0. The branch-specific targeted suites above passed. - Branch was fetched/rebased onto `public-gh/master`; `git rev-list --left-right --count public-gh/master...HEAD` reports `0 9`. Remote PR checks on latest head `e30bf399146451c86cee98ed528d51d33fa5af5a`: - `policy` — passed. - `verify` — passed. - `e2e` — passed. - `Greptile Review` — passed, confidence score 5/5; Greptile review threads resolved. - `security/snyk (cryppadotta)` — passed. Screenshots: - [New issue cheap lane desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-cheap-desktop.png) - [New issue custom lane desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-custom-desktop.png) - [New issue unsupported adapter desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/newissue-unsupported-desktop.png) - [Run ledger model profile badges desktop](https://github.com/paperclipai/paperclip/blob/PAP-2837-plan-cheap-model-for-adapters-that-can-support-it/docs/pr-screenshots/pap-2837/runledger-profile-badges-desktop.png) - Mobile variants are also in `docs/pr-screenshots/pap-2837/`. ## Risks - Medium: heartbeat config mediation now merges runtime model profiles into adapter configs, so adapter secret normalization and host-command restrictions must keep covering nested config paths. - Medium: the UI adds another issue creation choice; unsupported adapters must keep hiding the cheap lane and preserve primary behavior. - Low migration risk: no database migration is 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 coding agent using GPT-5-class reasoning with repo tool use and command execution. Exact served model/context window was not exposed by the runtime. ## 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 - [ ] 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> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 15:32:04 -05:00
adapterModelProfiles: (companyId: string, type: string) =>
api.get<AdapterModelProfile[]>(
`/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/model-profiles`,
),
testEnvironment: (
companyId: string,
type: string,
Add dedicated environment settings page and test-in-environment (#4798) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - Agents run inside environments (local, SSH, E2B sandbox) > - Operators need to configure and manage these environments > - But environment settings were buried inside the general company settings page, making them hard to find > - Additionally, when testing an agent from the configuration form, the test always ran locally regardless of which environment was selected > - This PR moves environments into a dedicated top-level company settings section and wires the "Test Environment" button to run inside the selected environment > - The benefit is operators can find and manage environments more easily, and the test button now validates the actual environment the agent will use ## What Changed - Added a dedicated `CompanyEnvironments` settings page with its own route and sidebar entry - Updated `CompanySettingsSidebar` and `CompanySettingsNav` to include the new environments section - Modified the agent test route (`POST /agents/:id/test`) to accept an optional `environmentId` parameter - Updated all adapter `test.ts` handlers to resolve and use the specified execution target environment - Added `resolveTestExecutionTarget` to `execution-target.ts` for remote environment test resolution with cwd fallback - Moved the "Test Environment" button and its feedback display into the `NewAgent` page footer for better UX flow ## Verification - `pnpm test` — all existing and new tests pass - `pnpm typecheck` — clean - Manual: navigate to Company Settings, confirm "Environments" appears as a top-level section - Manual: configure an agent with a non-local environment, click "Test Environment", confirm the test runs inside that environment ## Risks - Low risk. UI-only routing change for the settings page. The test-in-environment change adds an optional parameter with a local fallback, so existing behavior is preserved when no environment is specified. ## Model Used Codex GPT 5.4 high via Paperclip. ## 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
2026-04-29 15:56:13 -07:00
data: {
adapterConfig: Record<string, unknown>;
environmentId?: string | null;
},
) =>
api.post<AdapterEnvironmentTestResult>(
`/companies/${companyId}/adapters/${type}/test-environment`,
data,
),
Preserve scope on manual heartbeat invokes (#5323) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The agent live-run route lets operators trigger a manual heartbeat invocation so an agent can pick up a specific issue or step out of band > - The current route flow drops the caller's scope (issue/run context) when forwarding the manual invoke into the heartbeat service, so the resulting run loses the targeting the operator specified > - This pull request threads the operator-supplied scope through the manual invoke path on both the server route and the UI client, with a regression test that confirms the scope round-trips > - The benefit is manual heartbeat invokes from the live-run UI actually pick up the scoped issue/run instead of falling through to the agent's default routine ## What Changed - `server/src/routes/agents.ts`: forward the operator-supplied scope into the manual invoke heartbeat service call - `server/src/__tests__/agent-live-run-routes.test.ts`: new test verifying the manual invoke path preserves scope - `ui/src/api/agents.ts`: pass scope through the live-run client API ## Verification - `pnpm vitest run --no-coverage server/src/__tests__/agent-live-run-routes.test.ts` - `pnpm typecheck` clean ## Risks Low. The change is purely additive on the route surface — handlers that did not previously pass scope continue to work; handlers that did pass it now have it preserved instead of dropped. ## Model Used Claude Opus 4.7 (1M context) ## 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 — new test covers the preserved-scope path - [x] If this change affects the UI, I have included before/after screenshots — N/A (internal API change, no visible UI shift) - [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-05-05 19:30:08 -07:00
invoke: (id: string, companyId?: string, data: AgentWakeRequest = {}) =>
api.post<HeartbeatRun>(agentPath(id, companyId, "/heartbeat/invoke"), data),
wakeup: (
id: string,
Preserve scope on manual heartbeat invokes (#5323) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The agent live-run route lets operators trigger a manual heartbeat invocation so an agent can pick up a specific issue or step out of band > - The current route flow drops the caller's scope (issue/run context) when forwarding the manual invoke into the heartbeat service, so the resulting run loses the targeting the operator specified > - This pull request threads the operator-supplied scope through the manual invoke path on both the server route and the UI client, with a regression test that confirms the scope round-trips > - The benefit is manual heartbeat invokes from the live-run UI actually pick up the scoped issue/run instead of falling through to the agent's default routine ## What Changed - `server/src/routes/agents.ts`: forward the operator-supplied scope into the manual invoke heartbeat service call - `server/src/__tests__/agent-live-run-routes.test.ts`: new test verifying the manual invoke path preserves scope - `ui/src/api/agents.ts`: pass scope through the live-run client API ## Verification - `pnpm vitest run --no-coverage server/src/__tests__/agent-live-run-routes.test.ts` - `pnpm typecheck` clean ## Risks Low. The change is purely additive on the route surface — handlers that did not previously pass scope continue to work; handlers that did pass it now have it preserved instead of dropped. ## Model Used Claude Opus 4.7 (1M context) ## 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 — new test covers the preserved-scope path - [x] If this change affects the UI, I have included before/after screenshots — N/A (internal API change, no visible UI shift) - [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-05-05 19:30:08 -07:00
data: AgentWakeRequest,
companyId?: string,
) => api.post<AgentWakeupResponse>(agentPath(id, companyId, "/wakeup"), data),
loginWithClaude: (id: string, companyId?: string) =>
api.post<ClaudeLoginResult>(agentPath(id, companyId, "/claude-login"), {}),
availableSkills: () =>
api.get<{ skills: AvailableSkill[] }>("/skills/available"),
};
export interface AvailableSkill {
name: string;
description: string;
isPaperclipManaged: boolean;
}