mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 04:25:52 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environments are now managed as instance-level runtime resources rather than per-company rows > - The custom environment image setup tables were introduced with their own `company_id` columns and route query parameters > - That split made one saved environment image state depend on an extra company context even though the environment itself is the durable owner > - It also made saved-environment probes harder because applying the active custom image template could require a company context when no secret-backed config needed one > - This pull request scopes custom image templates and setup sessions directly to the saved environment > - The benefit is that reusable environment images follow the same instance-scoped model as environments while secret resolution still uses company context only when secrets require it ## Linked Issues or Issue Description No matching public GitHub issue was found. Bug report: ### What happened? saved environment custom-image routes and persistence required a `companyId` even though environments are instance-scoped, and saved sandbox probes did not opt into active custom-image template application unless a company context was present. ### Expected behavior custom-image templates and setup sessions should be owned by the saved environment, and saved sandbox probes should apply the active template while still requiring a company context only for secret-backed runtime config. ### Steps to reproduce 1. Configure an instance-scoped sandbox environment with custom-image setup support. 2. Start or inspect a custom-image session or template for that saved environment. 3. Probe the saved environment without a custom-image-specific `companyId` query parameter. ### Paperclip version or commit current `master` after the environment custom-image template migration. ### Deployment mode Local dev (pnpm dev) or authenticated local Paperclip instance. ### Installation method Built from source (pnpm dev / pnpm build). ### Agent adapter(s) involved Not adapter-specific (core bug). ### Database mode Embedded PGlite/Postgres dev database. ### Access context Board human operator. ### Privacy checklist No logs, secrets, tokens, private URLs, or local machine paths are included. Duplicate search performed: - `gh search prs "environment custom image companyId repo:paperclipai/paperclip" --state open --limit 20` - `gh search prs "custom image environment scoped repo:paperclipai/paperclip" --state open --limit 20` - `gh search issues "environment custom image repo:paperclipai/paperclip" --state open --limit 20` The returned results were unrelated adapter, Docker, auth, or stale-workspace items. ## What Changed - Removed redundant `company_id` columns from environment custom-image templates and setup sessions. - Added migration `0127_environment_custom_images_instance_scoped` to collapse duplicate active rows per environment before dropping the old company-scoped indexes/columns. - Updated custom-image services, route handlers, shared validators, and UI API/query keys to use environment-scoped custom-image state. - Kept runtime secret resolution company-aware only when secret refs or bindings require a company context. - Made saved sandbox environment probes opt into active custom-image template application. - Updated DB, shared, server, and UI tests for the new environment-scoped contract. ## Verification - `pnpm --filter @paperclipai/db run check:migrations` - `pnpm exec vitest run packages/db/src/environment-custom-images-schema.test.ts packages/shared/src/environment-custom-images.test.ts server/src/__tests__/environment-custom-image-routes.test.ts server/src/__tests__/environment-custom-images-service.test.ts server/src/__tests__/environment-routes.test.ts ui/src/pages/CompanyEnvironments.test.tsx` - `pnpm -r typecheck` - `pnpm test:run` before rebasing onto latest `master`; after the rebase only the migration number changed, and the migration check plus focused suite, typecheck, and build were rerun. - `pnpm build` ## Risks - Migration safety: the migration supersedes duplicate active templates per environment and fails duplicate active setup sessions before adding environment-only unique indexes. Operators with duplicate historical active rows should review which active template is kept. - Behavior shift: plugin custom-image setup calls now receive `companyId: "instance"` when no secret binding determines a concrete company context. - Secret-backed configs still require an explicit or uniquely inferable company context; environments with secret bindings spread across multiple companies continue to fail fast. > 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 via the `codex_local` adapter, GPT-5-based coding model with tool-enabled repository inspection, editing, testing, git, and GitHub CLI access. Exact context-window metadata 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 - [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 - [ ] All Paperclip CI gates are green - [ ] 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>
54 lines
3.0 KiB
TypeScript
54 lines
3.0 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
import type {
|
|
EnvironmentCustomImageSetupConnectionSummary,
|
|
EnvironmentCustomImageSetupSessionStatus,
|
|
} from "@paperclipai/shared";
|
|
import { agents } from "./agents.js";
|
|
import { environmentLeases } from "./environment_leases.js";
|
|
import { environmentCustomImageTemplates } from "./environment_custom_image_templates.js";
|
|
import { environments } from "./environments.js";
|
|
|
|
export const environmentCustomImageSetupSessions = pgTable(
|
|
"environment_custom_image_setup_sessions",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
environmentId: uuid("environment_id").notNull().references(() => environments.id, { onDelete: "cascade" }),
|
|
templateId: uuid("template_id").references(() => environmentCustomImageTemplates.id, { onDelete: "set null" }),
|
|
promotedTemplateId: uuid("promoted_template_id")
|
|
.references(() => environmentCustomImageTemplates.id, { onDelete: "set null" }),
|
|
provider: text("provider").notNull(),
|
|
providerLeaseId: text("provider_lease_id"),
|
|
environmentLeaseId: uuid("environment_lease_id").references(() => environmentLeases.id, { onDelete: "set null" }),
|
|
status: text("status").$type<EnvironmentCustomImageSetupSessionStatus>().notNull().default("starting"),
|
|
startedByUserId: text("started_by_user_id"),
|
|
startedByAgentId: uuid("started_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
baseTemplateRef: text("base_template_ref"),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
|
failureReason: text("failure_reason"),
|
|
connectionSummary: jsonb("connection_summary").$type<EnvironmentCustomImageSetupConnectionSummary | null>(),
|
|
connectionSecretRef: text("connection_secret_ref"),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => ({
|
|
environmentStatusIdx: index("environment_custom_image_setup_sessions_environment_status_idx").on(
|
|
table.environmentId,
|
|
table.status,
|
|
),
|
|
environmentActiveUq: uniqueIndex("environment_custom_image_setup_sessions_environment_active_uq")
|
|
.on(table.environmentId)
|
|
.where(sql`${table.status} IN ('starting', 'waiting_for_user', 'capturing')`),
|
|
templateIdx: index("environment_custom_image_setup_sessions_template_idx").on(table.templateId),
|
|
promotedTemplateIdx: index("environment_custom_image_setup_sessions_promoted_template_idx")
|
|
.on(table.promotedTemplateId),
|
|
expiresIdx: index("environment_custom_image_setup_sessions_expires_idx").on(table.expiresAt),
|
|
providerLeaseIdx: index("environment_custom_image_setup_sessions_provider_lease_idx").on(
|
|
table.provider,
|
|
table.providerLeaseId,
|
|
),
|
|
}),
|
|
);
|