mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 11:20:42 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Sandbox agents need a safe login path for each supported adapter > - Codex device login and Claude setup-token login used separate session stores and route logic > - Separate stores made session lookup, expiry, and login capability checks harder to keep consistent > - This pull request unifies both flows on one session table and one capability contract > - The benefit is one company-scoped login model with public session identifiers and shared lifecycle rules ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting (multiple of the above) **Problem or motivation** Codex and Claude sandbox login used separate session stores and different route paths. This split increased the risk of inconsistent company scoping, session lookup, and cleanup. **Proposed solution** Use `adapter_auth_sessions` for both login flows. Use public session identifiers for API access. Select login behavior from projected adapter capability data. Share the route spine, lease arguments, runner lifecycle, and reaper rules. **Alternatives considered** Keep two session tables and add matching fixes to both routes. This keeps duplicate logic and does not provide one capability contract, so this pull request uses shared infrastructure. **Roadmap alignment** This change supports the shipped Cloud / Sandbox agents milestone in `ROADMAP.md`. ## What Changed - Unify Codex device login and Claude setup-token login on `adapter_auth_sessions`. - Return and look up sessions with company-scoped public session identifiers. - Enforce one active session for each company, owner, and adapter. - Share the login route spine, sandbox lease arguments, runner lifecycle, and missing-auth check. - Add a standalone setup-token reaper with adapter-specific row selection. - Add optional login capability projection for adapters and drive route and UI selection from that data. - Rename the provider flag to `supportsLoginPty` and validate its deprecated alias. - Remove the old Claude setup-token session table and add the required migrations. ## Verification - Server typecheck passed with `tsc`. - Database typecheck passed. - UI typecheck passed with `tsc -b`. - Codex login service and route suites passed. - Setup-token session, route, and reaper suites passed. - Adapter session schema, plugin validator, capability projection, UI render, and Daytona suites passed. - GitHub Actions must confirm the complete CI gate after pull request creation. ## Risks - The migrations remove short-lived in-flight login rows during deployment. A login that spans the migration can continue until its provider lease expires. - The Codex credential store remains company-scoped. A cross-owner credential race remains a documented, board-accepted risk. - API clients that use internal session row identifiers no longer work. The API accepts only public session identifiers. ## Model Used Codex, GPT-5, exact runtime model ID not exposed in this handoff, large context window, reasoning, and repository tool use. The implementing engineer produced the code with AI assistance. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes: #` / `Refs: #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
/**
|
|
* @fileoverview Frontend API client for external adapter management.
|
|
*/
|
|
|
|
import { api } from "./client";
|
|
|
|
/**
|
|
* The safe scalar login fields the server projects for an adapter that declares
|
|
* an interactive login capability. The projection carries no function member and
|
|
* no secret. The form reads it to pick the login flow and the login panel.
|
|
*/
|
|
export interface AdapterLoginProjection {
|
|
panelMode: "displayed_code" | "submitted_browser_code";
|
|
sandboxTransport: "streamed_exec" | "pseudo_terminal";
|
|
timeoutPolicy: "caller_bounded" | "fixed";
|
|
}
|
|
|
|
export interface AdapterCapabilities {
|
|
supportsInstructionsBundle: boolean;
|
|
supportsSkills: boolean;
|
|
supportsLocalAgentJwt: boolean;
|
|
requiresMaterializedRuntimeSkills: boolean;
|
|
supportsModelProfiles: boolean;
|
|
supportsAcp: boolean;
|
|
/** Present only when the adapter declares an interactive login capability. */
|
|
login?: AdapterLoginProjection;
|
|
}
|
|
|
|
export interface AcpTargetDescriptor {
|
|
agentId: string;
|
|
skillsMode: "ephemeral" | "unsupported";
|
|
prerequisites: {
|
|
nodeRange?: string;
|
|
packages?: string[];
|
|
};
|
|
}
|
|
|
|
export interface AdapterInfo {
|
|
type: string;
|
|
label: string;
|
|
source: "builtin" | "external";
|
|
modelsCount: number;
|
|
loaded: boolean;
|
|
disabled: boolean;
|
|
capabilities: AdapterCapabilities;
|
|
acp?: AcpTargetDescriptor;
|
|
/** Installed version (for external npm adapters) */
|
|
version?: string;
|
|
/** Package name (for external adapters) */
|
|
packageName?: string;
|
|
/** Whether the adapter was installed from a local path (vs npm). */
|
|
isLocalPath?: boolean;
|
|
/** True when an external plugin has replaced a built-in adapter of the same type. */
|
|
overriddenBuiltin?: boolean;
|
|
/** True when the external override for a builtin type is currently paused. */
|
|
overridePaused?: boolean;
|
|
}
|
|
|
|
export interface AdapterInstallResult {
|
|
type: string;
|
|
packageName: string;
|
|
version?: string;
|
|
installedAt: string;
|
|
}
|
|
|
|
export const adaptersApi = {
|
|
/** List all registered adapters (built-in + external). */
|
|
list: () => api.get<AdapterInfo[]>("/adapters"),
|
|
|
|
/** Install an external adapter from npm or a local path. */
|
|
install: (params: { packageName: string; version?: string; isLocalPath?: boolean }) =>
|
|
api.post<AdapterInstallResult>("/adapters/install", params),
|
|
|
|
/** Remove an external adapter by type. */
|
|
remove: (type: string) => api.delete<{ type: string; removed: boolean }>(`/adapters/${type}`),
|
|
|
|
/** Enable or disable an adapter (disabled adapters hidden from agent menus). */
|
|
setDisabled: (type: string, disabled: boolean) =>
|
|
api.patch<{ type: string; disabled: boolean; changed: boolean }>(`/adapters/${type}`, { disabled }),
|
|
|
|
/** Pause or resume an external override of a builtin type. */
|
|
setOverridePaused: (type: string, paused: boolean) =>
|
|
api.patch<{ type: string; paused: boolean; changed: boolean }>(`/adapters/${type}/override`, { paused }),
|
|
|
|
/** Reload an external adapter (bust server + client caches). */
|
|
reload: (type: string) =>
|
|
api.post<{ type: string; version?: string; reloaded: boolean }>(`/adapters/${type}/reload`, {}),
|
|
|
|
/** Reinstall an npm-sourced adapter (pulls latest from registry, then reloads). */
|
|
reinstall: (type: string) =>
|
|
api.post<{ type: string; version?: string; reinstalled: boolean }>(`/adapters/${type}/reinstall`, {}),
|
|
};
|